Merge branch 'master' into mathmerge
This commit is contained in:
@@ -40,7 +40,12 @@ namespace Content.Client.Atmos
|
|||||||
|
|
||||||
foreach (var mapGrid in _mapManager.FindGridsIntersecting(mapId, worldBounds))
|
foreach (var mapGrid in _mapManager.FindGridsIntersecting(mapId, worldBounds))
|
||||||
{
|
{
|
||||||
foreach (var tile in mapGrid.GetTilesIntersecting(worldBounds))
|
if (!_gasTileOverlaySystem.HasData(mapGrid.Index))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var gridBounds = new Box2(mapGrid.WorldToLocal(worldBounds.BottomLeft), mapGrid.WorldToLocal(worldBounds.TopRight));
|
||||||
|
|
||||||
|
foreach (var tile in mapGrid.GetTilesIntersecting(gridBounds))
|
||||||
{
|
{
|
||||||
foreach (var (texture, color) in _gasTileOverlaySystem.GetOverlays(mapGrid.Index, tile.GridIndices))
|
foreach (var (texture, color) in _gasTileOverlaySystem.GetOverlays(mapGrid.Index, tile.GridIndices))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ namespace Content.Client.Chat
|
|||||||
|
|
||||||
public bool ReleaseFocusOnEnter { get; set; } = true;
|
public bool ReleaseFocusOnEnter { get; set; } = true;
|
||||||
|
|
||||||
|
public bool ClearOnEnter { get; set; } = true;
|
||||||
|
|
||||||
public ChatBox()
|
public ChatBox()
|
||||||
{
|
{
|
||||||
/*MarginLeft = -475.0f;
|
/*MarginLeft = -475.0f;
|
||||||
@@ -166,12 +168,18 @@ namespace Content.Client.Chat
|
|||||||
|
|
||||||
private void Input_OnTextEntered(LineEdit.LineEditEventArgs args)
|
private void Input_OnTextEntered(LineEdit.LineEditEventArgs args)
|
||||||
{
|
{
|
||||||
|
// We set it there to true so it's set to false by TextSubmitted.Invoke if necessary
|
||||||
|
ClearOnEnter = true;
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(args.Text))
|
if (!string.IsNullOrWhiteSpace(args.Text))
|
||||||
{
|
{
|
||||||
TextSubmitted?.Invoke(this, args.Text);
|
TextSubmitted?.Invoke(this, args.Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
Input.Clear();
|
if (ClearOnEnter)
|
||||||
|
{
|
||||||
|
Input.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
if (ReleaseFocusOnEnter)
|
if (ReleaseFocusOnEnter)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,17 +1,21 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Content.Client.Interfaces.Chat;
|
using Content.Client.Interfaces.Chat;
|
||||||
using Content.Shared.Chat;
|
using Content.Shared.Chat;
|
||||||
using Robust.Client.Console;
|
using Robust.Client.Console;
|
||||||
using Robust.Client.Interfaces.Graphics.ClientEye;
|
using Robust.Client.Interfaces.Graphics.ClientEye;
|
||||||
using Robust.Client.Interfaces.UserInterface;
|
using Robust.Client.Interfaces.UserInterface;
|
||||||
|
using Robust.Client.Player;
|
||||||
using Robust.Client.UserInterface;
|
using Robust.Client.UserInterface;
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.GameObjects;
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
using Robust.Shared.Interfaces.Network;
|
using Robust.Shared.Interfaces.Network;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
using Robust.Shared.Log;
|
using Robust.Shared.Log;
|
||||||
using Robust.Shared.Maths;
|
using Robust.Shared.Maths;
|
||||||
|
using Robust.Shared.Network;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
@@ -45,6 +49,11 @@ namespace Content.Client.Chat
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private const int SpeechBubbleCap = 4;
|
private const int SpeechBubbleCap = 4;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The max amount of characters an entity can send in one message
|
||||||
|
/// </summary>
|
||||||
|
private int _maxMessageLength = 1000;
|
||||||
|
|
||||||
private const char ConCmdSlash = '/';
|
private const char ConCmdSlash = '/';
|
||||||
private const char OOCAlias = '[';
|
private const char OOCAlias = '[';
|
||||||
private const char MeAlias = '@';
|
private const char MeAlias = '@';
|
||||||
@@ -89,11 +98,15 @@ namespace Content.Client.Chat
|
|||||||
public void Initialize()
|
public void Initialize()
|
||||||
{
|
{
|
||||||
_netManager.RegisterNetMessage<MsgChatMessage>(MsgChatMessage.NAME, _onChatMessage);
|
_netManager.RegisterNetMessage<MsgChatMessage>(MsgChatMessage.NAME, _onChatMessage);
|
||||||
|
_netManager.RegisterNetMessage<ChatMaxMsgLengthMessage>(ChatMaxMsgLengthMessage.NAME, _onMaxLengthReceived);
|
||||||
|
|
||||||
_speechBubbleRoot = new LayoutContainer();
|
_speechBubbleRoot = new LayoutContainer();
|
||||||
LayoutContainer.SetAnchorPreset(_speechBubbleRoot, LayoutContainer.LayoutPreset.Wide);
|
LayoutContainer.SetAnchorPreset(_speechBubbleRoot, LayoutContainer.LayoutPreset.Wide);
|
||||||
_userInterfaceManager.StateRoot.AddChild(_speechBubbleRoot);
|
_userInterfaceManager.StateRoot.AddChild(_speechBubbleRoot);
|
||||||
_speechBubbleRoot.SetPositionFirst();
|
_speechBubbleRoot.SetPositionFirst();
|
||||||
|
|
||||||
|
// When connexion is achieved, request the max chat message length
|
||||||
|
_netManager.Connected += new EventHandler<NetChannelArgs>(RequestMaxLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void FrameUpdate(FrameEventArgs delta)
|
public void FrameUpdate(FrameEventArgs delta)
|
||||||
@@ -213,6 +226,15 @@ namespace Content.Client.Chat
|
|||||||
if (string.IsNullOrWhiteSpace(text))
|
if (string.IsNullOrWhiteSpace(text))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// Check if message is longer than the character limit
|
||||||
|
if (text.Length > _maxMessageLength)
|
||||||
|
{
|
||||||
|
string locWarning = Loc.GetString("Your message exceeds {0} character limit", _maxMessageLength);
|
||||||
|
_currentChatBox?.AddLine(locWarning, ChatChannel.Server, Color.Orange);
|
||||||
|
_currentChatBox.ClearOnEnter = false; // The text shouldn't be cleared if it hasn't been sent
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (text[0])
|
switch (text[0])
|
||||||
{
|
{
|
||||||
case ConCmdSlash:
|
case ConCmdSlash:
|
||||||
@@ -225,13 +247,17 @@ namespace Content.Client.Chat
|
|||||||
case OOCAlias:
|
case OOCAlias:
|
||||||
{
|
{
|
||||||
var conInput = text.Substring(1);
|
var conInput = text.Substring(1);
|
||||||
|
if (string.IsNullOrWhiteSpace(conInput))
|
||||||
|
return;
|
||||||
_console.ProcessCommand($"ooc \"{CommandParsing.Escape(conInput)}\"");
|
_console.ProcessCommand($"ooc \"{CommandParsing.Escape(conInput)}\"");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case AdminChatAlias:
|
case AdminChatAlias:
|
||||||
{
|
{
|
||||||
var conInput = text.Substring(1);
|
var conInput = text.Substring(1);
|
||||||
if(_groupController.CanCommand("asay")){
|
if (string.IsNullOrWhiteSpace(conInput))
|
||||||
|
return;
|
||||||
|
if (_groupController.CanCommand("asay")){
|
||||||
_console.ProcessCommand($"asay \"{CommandParsing.Escape(conInput)}\"");
|
_console.ProcessCommand($"asay \"{CommandParsing.Escape(conInput)}\"");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -243,6 +269,8 @@ namespace Content.Client.Chat
|
|||||||
case MeAlias:
|
case MeAlias:
|
||||||
{
|
{
|
||||||
var conInput = text.Substring(1);
|
var conInput = text.Substring(1);
|
||||||
|
if (string.IsNullOrWhiteSpace(conInput))
|
||||||
|
return;
|
||||||
_console.ProcessCommand($"me \"{CommandParsing.Escape(conInput)}\"");
|
_console.ProcessCommand($"me \"{CommandParsing.Escape(conInput)}\"");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -323,8 +351,6 @@ namespace Content.Client.Chat
|
|||||||
|
|
||||||
private void _onChatMessage(MsgChatMessage msg)
|
private void _onChatMessage(MsgChatMessage msg)
|
||||||
{
|
{
|
||||||
Logger.Debug($"{msg.Channel}: {msg.Message}");
|
|
||||||
|
|
||||||
// Log all incoming chat to repopulate when filter is un-toggled
|
// Log all incoming chat to repopulate when filter is un-toggled
|
||||||
var storedMessage = new StoredChatMessage(msg);
|
var storedMessage = new StoredChatMessage(msg);
|
||||||
filteredHistory.Add(storedMessage);
|
filteredHistory.Add(storedMessage);
|
||||||
@@ -347,6 +373,17 @@ namespace Content.Client.Chat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void _onMaxLengthReceived(ChatMaxMsgLengthMessage msg)
|
||||||
|
{
|
||||||
|
_maxMessageLength = msg.MaxMessageLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RequestMaxLength(object sender, NetChannelArgs args)
|
||||||
|
{
|
||||||
|
ChatMaxMsgLengthMessage msg = _netManager.CreateNetMessage<ChatMaxMsgLengthMessage>();
|
||||||
|
_netManager.ClientSendMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
private void AddSpeechBubble(MsgChatMessage msg, SpeechBubble.SpeechType speechType)
|
private void AddSpeechBubble(MsgChatMessage msg, SpeechBubble.SpeechType speechType)
|
||||||
{
|
{
|
||||||
if (!_entityManager.TryGetEntity(msg.SenderEntity, out var entity))
|
if (!_entityManager.TryGetEntity(msg.SenderEntity, out var entity))
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Content.Client.Interfaces.Chat;
|
|||||||
using Content.Client.Interfaces.Parallax;
|
using Content.Client.Interfaces.Parallax;
|
||||||
using Content.Client.Parallax;
|
using Content.Client.Parallax;
|
||||||
using Content.Client.Sandbox;
|
using Content.Client.Sandbox;
|
||||||
|
using Content.Client.StationEvents;
|
||||||
using Content.Client.UserInterface;
|
using Content.Client.UserInterface;
|
||||||
using Content.Client.UserInterface.Stylesheets;
|
using Content.Client.UserInterface.Stylesheets;
|
||||||
using Content.Client.Utility;
|
using Content.Client.Utility;
|
||||||
@@ -31,6 +32,7 @@ namespace Content.Client
|
|||||||
IoCManager.Register<IStylesheetManager, StylesheetManager>();
|
IoCManager.Register<IStylesheetManager, StylesheetManager>();
|
||||||
IoCManager.Register<IScreenshotHook, ScreenshotHook>();
|
IoCManager.Register<IScreenshotHook, ScreenshotHook>();
|
||||||
IoCManager.Register<IClickMapManager, ClickMapManager>();
|
IoCManager.Register<IClickMapManager, ClickMapManager>();
|
||||||
|
IoCManager.Register<IStationEventManager, StationEventManager>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using Content.Client.Interfaces.Parallax;
|
|||||||
using Content.Client.Parallax;
|
using Content.Client.Parallax;
|
||||||
using Content.Client.Sandbox;
|
using Content.Client.Sandbox;
|
||||||
using Content.Client.State;
|
using Content.Client.State;
|
||||||
|
using Content.Client.StationEvents;
|
||||||
using Content.Client.UserInterface;
|
using Content.Client.UserInterface;
|
||||||
using Content.Client.UserInterface.Stylesheets;
|
using Content.Client.UserInterface.Stylesheets;
|
||||||
using Content.Shared.GameObjects.Components;
|
using Content.Shared.GameObjects.Components;
|
||||||
@@ -150,6 +151,7 @@ namespace Content.Client
|
|||||||
IoCManager.Resolve<IChatManager>().Initialize();
|
IoCManager.Resolve<IChatManager>().Initialize();
|
||||||
IoCManager.Resolve<ISandboxManager>().Initialize();
|
IoCManager.Resolve<ISandboxManager>().Initialize();
|
||||||
IoCManager.Resolve<IClientPreferencesManager>().Initialize();
|
IoCManager.Resolve<IClientPreferencesManager>().Initialize();
|
||||||
|
IoCManager.Resolve<IStationEventManager>().Initialize();
|
||||||
|
|
||||||
_baseClient.RunLevelChanged += (sender, args) =>
|
_baseClient.RunLevelChanged += (sender, args) =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
using Content.Client.Atmos;
|
|
||||||
using Robust.Client.GameObjects;
|
|
||||||
using Robust.Client.Interfaces.Graphics.Overlays;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
|
||||||
using Robust.Shared.IoC;
|
|
||||||
|
|
||||||
namespace Content.Client.GameObjects.Components.Atmos
|
|
||||||
{
|
|
||||||
[RegisterComponent]
|
|
||||||
public class CanSeeGasesComponent : Component
|
|
||||||
{
|
|
||||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
|
||||||
|
|
||||||
public override string Name => "CanSeeGases";
|
|
||||||
|
|
||||||
public override void HandleMessage(ComponentMessage message, IComponent component)
|
|
||||||
{
|
|
||||||
base.HandleMessage(message, component);
|
|
||||||
|
|
||||||
switch (message)
|
|
||||||
{
|
|
||||||
case PlayerAttachedMsg _:
|
|
||||||
if(!_overlayManager.HasOverlay(nameof(GasTileOverlay)))
|
|
||||||
_overlayManager.AddOverlay(new GasTileOverlay());
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PlayerDetachedMsg _:
|
|
||||||
if(!_overlayManager.HasOverlay(nameof(GasTileOverlay)))
|
|
||||||
_overlayManager.RemoveOverlay(nameof(GasTileOverlay));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
using System;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Client.Animations;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.GameObjects.Components.Animations;
|
||||||
|
using Robust.Client.Interfaces.GameObjects.Components;
|
||||||
|
using Robust.Shared.Animations;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using Content.Shared.GameObjects.Components;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Atmos
|
||||||
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
|
public class ExtinguisherVisualizer : AppearanceVisualizer
|
||||||
|
{
|
||||||
|
|
||||||
|
public override void OnChangeData(AppearanceComponent component)
|
||||||
|
{
|
||||||
|
base.OnChangeData(component);
|
||||||
|
|
||||||
|
if (component.Deleted)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (component.TryGetData<double>(ExtinguisherVisuals.Rotation, out var degrees))
|
||||||
|
{
|
||||||
|
SetRotation(component, Angle.FromDegrees(degrees));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetRotation(AppearanceComponent component, Angle rotation)
|
||||||
|
{
|
||||||
|
var sprite = component.Owner.GetComponent<ISpriteComponent>();
|
||||||
|
|
||||||
|
if (!sprite.Owner.TryGetComponent(out AnimationPlayerComponent animation))
|
||||||
|
{
|
||||||
|
sprite.Rotation = rotation;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (animation.HasRunningAnimation("rotate"))
|
||||||
|
{
|
||||||
|
animation.Stop("rotate");
|
||||||
|
}
|
||||||
|
|
||||||
|
animation.Play(new Animation
|
||||||
|
{
|
||||||
|
Length = TimeSpan.FromSeconds(0.125),
|
||||||
|
AnimationTracks =
|
||||||
|
{
|
||||||
|
new AnimationTrackComponentProperty
|
||||||
|
{
|
||||||
|
ComponentType = typeof(ISpriteComponent),
|
||||||
|
Property = nameof(ISpriteComponent.Rotation),
|
||||||
|
InterpolationMode = AnimationInterpolationMode.Linear,
|
||||||
|
KeyFrames =
|
||||||
|
{
|
||||||
|
new AnimationTrackProperty.KeyFrame(sprite.Rotation, 0),
|
||||||
|
new AnimationTrackProperty.KeyFrame(rotation, 0.125f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, "rotate");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
#nullable enable
|
||||||
|
using Content.Client.GameObjects.Components.Disposal;
|
||||||
|
using Content.Client.Interfaces.GameObjects.Components.Interaction;
|
||||||
|
using Content.Shared.GameObjects.Components.Body;
|
||||||
|
using Content.Shared.GameObjects.Components.Damage;
|
||||||
|
using Robust.Client.Interfaces.GameObjects.Components;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Network;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Players;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Body
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
[ComponentReference(typeof(IDamageableComponent))]
|
||||||
|
[ComponentReference(typeof(IBodyManagerComponent))]
|
||||||
|
public class BodyManagerComponent : SharedBodyManagerComponent, IClientDraggable
|
||||||
|
{
|
||||||
|
#pragma warning disable 649
|
||||||
|
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||||
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
public bool ClientCanDropOn(CanDropEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
return eventArgs.Target.HasComponent<DisposalUnitComponent>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ClientCanDrag(CanDragEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void HandleNetworkMessage(ComponentMessage message, INetChannel netChannel, ICommonSession? session = null)
|
||||||
|
{
|
||||||
|
if (!Owner.TryGetComponent(out ISpriteComponent? sprite))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (message)
|
||||||
|
{
|
||||||
|
case BodyPartAddedMessage partAdded:
|
||||||
|
sprite.LayerSetVisible(partAdded.RSIMap, true);
|
||||||
|
sprite.LayerSetRSI(partAdded.RSIMap, partAdded.RSIPath);
|
||||||
|
sprite.LayerSetState(partAdded.RSIMap, partAdded.RSIState);
|
||||||
|
break;
|
||||||
|
case BodyPartRemovedMessage partRemoved:
|
||||||
|
sprite.LayerSetVisible(partRemoved.RSIMap, false);
|
||||||
|
|
||||||
|
if (!partRemoved.Dropped.HasValue ||
|
||||||
|
!_entityManager.TryGetEntity(partRemoved.Dropped.Value, out var entity) ||
|
||||||
|
!entity.TryGetComponent(out ISpriteComponent? droppedSprite))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var color = sprite[partRemoved.RSIMap].Color;
|
||||||
|
|
||||||
|
droppedSprite.LayerSetColor(0, color);
|
||||||
|
break;
|
||||||
|
case MechanismSpriteAddedMessage mechanismAdded:
|
||||||
|
sprite.LayerSetVisible(mechanismAdded.RSIMap, true);
|
||||||
|
break;
|
||||||
|
case MechanismSpriteRemovedMessage mechanismRemoved:
|
||||||
|
sprite.LayerSetVisible(mechanismRemoved.RSIMap, false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Content.Shared.Health.BodySystem.BodyScanner;
|
using Content.Shared.Body.Scanner;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using Robust.Client.GameObjects.Components.UserInterface;
|
using Robust.Client.GameObjects.Components.UserInterface;
|
||||||
using Robust.Shared.GameObjects.Components.UserInterface;
|
using Robust.Shared.GameObjects.Components.UserInterface;
|
||||||
using Robust.Shared.ViewVariables;
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
namespace Content.Client.Health.BodySystem.BodyScanner
|
namespace Content.Client.GameObjects.Components.Body.Scanner
|
||||||
{
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
public class BodyScannerBoundUserInterface : BoundUserInterface
|
public class BodyScannerBoundUserInterface : BoundUserInterface
|
||||||
{
|
{
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
@@ -17,9 +19,7 @@ namespace Content.Client.Health.BodySystem.BodyScanner
|
|||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
private Dictionary<string, BodyScannerBodyPartData> _parts;
|
private Dictionary<string, BodyScannerBodyPartData> _parts;
|
||||||
|
|
||||||
public BodyScannerBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public BodyScannerBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) { }
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Open()
|
protected override void Open()
|
||||||
{
|
{
|
||||||
@@ -34,7 +34,9 @@ namespace Content.Client.Health.BodySystem.BodyScanner
|
|||||||
base.UpdateState(state);
|
base.UpdateState(state);
|
||||||
|
|
||||||
if (!(state is BodyScannerInterfaceState scannerState))
|
if (!(state is BodyScannerInterfaceState scannerState))
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_template = scannerState.Template;
|
_template = scannerState.Template;
|
||||||
_parts = scannerState.Parts;
|
_parts = scannerState.Parts;
|
||||||
@@ -45,7 +47,13 @@ namespace Content.Client.Health.BodySystem.BodyScanner
|
|||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
base.Dispose(disposing);
|
base.Dispose(disposing);
|
||||||
}
|
|
||||||
|
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_display.Dispose();
|
||||||
|
_template = null;
|
||||||
|
_parts.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using Content.Shared.Body.Scanner;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using static Robust.Client.UserInterface.Controls.ItemList;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Body.Scanner
|
||||||
|
{
|
||||||
|
public sealed class BodyScannerDisplay : SS14Window
|
||||||
|
{
|
||||||
|
private BodyScannerTemplateData _template;
|
||||||
|
|
||||||
|
private Dictionary<string, BodyScannerBodyPartData> _parts;
|
||||||
|
|
||||||
|
private List<string> _slots;
|
||||||
|
|
||||||
|
private BodyScannerBodyPartData _currentBodyPart;
|
||||||
|
|
||||||
|
public BodyScannerDisplay(BodyScannerBoundUserInterface owner)
|
||||||
|
{
|
||||||
|
IoCManager.InjectDependencies(this);
|
||||||
|
Owner = owner;
|
||||||
|
Title = Loc.GetString("Body Scanner");
|
||||||
|
|
||||||
|
var hSplit = new HBoxContainer
|
||||||
|
{
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
// Left half
|
||||||
|
new ScrollContainer
|
||||||
|
{
|
||||||
|
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
(BodyPartList = new ItemList())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Right half
|
||||||
|
new VBoxContainer
|
||||||
|
{
|
||||||
|
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
// Top half of the right half
|
||||||
|
new VBoxContainer
|
||||||
|
{
|
||||||
|
SizeFlagsVertical = SizeFlags.FillExpand,
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
(BodyPartLabel = new Label()),
|
||||||
|
new HBoxContainer
|
||||||
|
{
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new Label
|
||||||
|
{
|
||||||
|
Text = "Health: "
|
||||||
|
},
|
||||||
|
(BodyPartHealth = new Label())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new ScrollContainer
|
||||||
|
{
|
||||||
|
SizeFlagsVertical = SizeFlags.FillExpand,
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
(MechanismList = new ItemList())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Bottom half of the right half
|
||||||
|
(MechanismInfoLabel = new RichTextLabel
|
||||||
|
{
|
||||||
|
SizeFlagsVertical = SizeFlags.FillExpand
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Contents.AddChild(hSplit);
|
||||||
|
|
||||||
|
BodyPartList.OnItemSelected += BodyPartOnItemSelected;
|
||||||
|
MechanismList.OnItemSelected += MechanismOnItemSelected;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BodyScannerBoundUserInterface Owner { get; }
|
||||||
|
|
||||||
|
protected override Vector2? CustomSize => (800, 600);
|
||||||
|
|
||||||
|
private ItemList BodyPartList { get; }
|
||||||
|
|
||||||
|
private Label BodyPartLabel { get; }
|
||||||
|
|
||||||
|
private Label BodyPartHealth { get; }
|
||||||
|
|
||||||
|
private ItemList MechanismList { get; }
|
||||||
|
|
||||||
|
private RichTextLabel MechanismInfoLabel { get; }
|
||||||
|
|
||||||
|
public void UpdateDisplay(BodyScannerTemplateData template, Dictionary<string, BodyScannerBodyPartData> parts)
|
||||||
|
{
|
||||||
|
_template = template;
|
||||||
|
_parts = parts;
|
||||||
|
_slots = new List<string>();
|
||||||
|
BodyPartList.Clear();
|
||||||
|
|
||||||
|
foreach (var slotName in _parts.Keys)
|
||||||
|
{
|
||||||
|
// We have to do this since ItemLists only return the index of what item is
|
||||||
|
// selected and dictionaries don't allow you to explicitly grab things by index.
|
||||||
|
// So we put the contents of the dictionary into a list so
|
||||||
|
// that we can grab the list by index. I don't know either.
|
||||||
|
_slots.Add(slotName);
|
||||||
|
|
||||||
|
BodyPartList.AddItem(Loc.GetString(slotName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void BodyPartOnItemSelected(ItemListSelectedEventArgs args)
|
||||||
|
{
|
||||||
|
if (_parts.TryGetValue(_slots[args.ItemIndex], out _currentBodyPart)) {
|
||||||
|
UpdateBodyPartBox(_currentBodyPart, _slots[args.ItemIndex]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateBodyPartBox(BodyScannerBodyPartData part, string slotName)
|
||||||
|
{
|
||||||
|
BodyPartLabel.Text = $"{Loc.GetString(slotName)}: {Loc.GetString(part.Name)}";
|
||||||
|
BodyPartHealth.Text = $"{part.CurrentDurability}/{part.MaxDurability}";
|
||||||
|
|
||||||
|
MechanismList.Clear();
|
||||||
|
foreach (var mechanism in part.Mechanisms) {
|
||||||
|
MechanismList.AddItem(mechanism.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MechanismOnItemSelected(ItemListSelectedEventArgs args)
|
||||||
|
{
|
||||||
|
UpdateMechanismBox(_currentBodyPart.Mechanisms[args.ItemIndex]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateMechanismBox(BodyScannerMechanismData mechanism)
|
||||||
|
{
|
||||||
|
// TODO: Improve UI
|
||||||
|
if (mechanism == null)
|
||||||
|
{
|
||||||
|
MechanismInfoLabel.SetMessage("");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var message =
|
||||||
|
Loc.GetString(
|
||||||
|
$"{mechanism.Name}\nHealth: {mechanism.CurrentDurability}/{mechanism.MaxDurability}\n{mechanism.Description}");
|
||||||
|
|
||||||
|
MechanismInfoLabel.SetMessage(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,29 +1,30 @@
|
|||||||
using Content.Shared.Health.BodySystem.Surgery;
|
#nullable enable
|
||||||
|
using Content.Shared.Body.Surgery;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using Robust.Client.GameObjects.Components.UserInterface;
|
using Robust.Client.GameObjects.Components.UserInterface;
|
||||||
using Robust.Shared.GameObjects.Components.UserInterface;
|
using Robust.Shared.GameObjects.Components.UserInterface;
|
||||||
|
|
||||||
namespace Content.Client.Health.BodySystem.Surgery
|
namespace Content.Client.GameObjects.Components.Body.Surgery
|
||||||
{
|
{
|
||||||
|
// TODO : Make window close if target or surgery tool gets too far away from user.
|
||||||
//TODO : Make window close if target or surgery tool gets too far away from user.
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generic client-side UI list popup that allows users to choose from an option of limbs or organs to operate on.
|
/// Generic client-side UI list popup that allows users to choose from an option
|
||||||
|
/// of limbs or organs to operate on.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[UsedImplicitly]
|
||||||
public class GenericSurgeryBoundUserInterface : BoundUserInterface
|
public class GenericSurgeryBoundUserInterface : BoundUserInterface
|
||||||
{
|
{
|
||||||
|
private GenericSurgeryWindow? _window;
|
||||||
|
|
||||||
private GenericSurgeryWindow _window;
|
public GenericSurgeryBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) { }
|
||||||
|
|
||||||
public GenericSurgeryBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Open()
|
protected override void Open()
|
||||||
{
|
{
|
||||||
_window = new GenericSurgeryWindow();
|
_window = new GenericSurgeryWindow();
|
||||||
|
|
||||||
_window.OpenCentered();
|
_window.OpenCentered();
|
||||||
|
_window.OnClose += Close;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
|
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
|
||||||
@@ -44,40 +45,42 @@ namespace Content.Client.Health.BodySystem.Surgery
|
|||||||
|
|
||||||
private void HandleBodyPartRequest(RequestBodyPartSurgeryUIMessage msg)
|
private void HandleBodyPartRequest(RequestBodyPartSurgeryUIMessage msg)
|
||||||
{
|
{
|
||||||
_window.BuildDisplay(msg.Targets, BodyPartSelectedCallback);
|
_window?.BuildDisplay(msg.Targets, BodyPartSelectedCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleMechanismRequest(RequestMechanismSurgeryUIMessage msg)
|
private void HandleMechanismRequest(RequestMechanismSurgeryUIMessage msg)
|
||||||
{
|
{
|
||||||
_window.BuildDisplay(msg.Targets, MechanismSelectedCallback);
|
_window?.BuildDisplay(msg.Targets, MechanismSelectedCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleBodyPartSlotRequest(RequestBodyPartSlotSurgeryUIMessage msg)
|
private void HandleBodyPartSlotRequest(RequestBodyPartSlotSurgeryUIMessage msg)
|
||||||
{
|
{
|
||||||
_window.BuildDisplay(msg.Targets, BodyPartSlotSelectedCallback);
|
_window?.BuildDisplay(msg.Targets, BodyPartSlotSelectedCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void BodyPartSelectedCallback(int selectedOptionData)
|
private void BodyPartSelectedCallback(int selectedOptionData)
|
||||||
{
|
{
|
||||||
SendMessage(new ReceiveBodyPartSurgeryUIMessage(selectedOptionData));
|
SendMessage(new ReceiveBodyPartSurgeryUIMessage(selectedOptionData));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MechanismSelectedCallback(int selectedOptionData)
|
private void MechanismSelectedCallback(int selectedOptionData)
|
||||||
{
|
{
|
||||||
SendMessage(new ReceiveMechanismSurgeryUIMessage(selectedOptionData));
|
SendMessage(new ReceiveMechanismSurgeryUIMessage(selectedOptionData));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BodyPartSlotSelectedCallback(int selectedOptionData)
|
private void BodyPartSlotSelectedCallback(int selectedOptionData)
|
||||||
{
|
{
|
||||||
SendMessage(new ReceiveBodyPartSlotSurgeryUIMessage(selectedOptionData));
|
SendMessage(new ReceiveBodyPartSlotSurgeryUIMessage(selectedOptionData));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
base.Dispose(disposing);
|
base.Dispose(disposing);
|
||||||
if (!disposing)
|
|
||||||
return;
|
if (disposing)
|
||||||
_window.Dispose();
|
{
|
||||||
|
_window?.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,58 +1,62 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
|
||||||
using Robust.Client.UserInterface;
|
using Robust.Client.UserInterface;
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
using Robust.Client.UserInterface.CustomControls;
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
using Robust.Shared.Localization;
|
using Robust.Shared.Localization;
|
||||||
using Robust.Shared.Maths;
|
using Robust.Shared.Maths;
|
||||||
|
|
||||||
namespace Content.Client.Health.BodySystem.Surgery
|
namespace Content.Client.GameObjects.Components.Body.Surgery
|
||||||
{
|
{
|
||||||
public class GenericSurgeryWindow : SS14Window
|
public class GenericSurgeryWindow : SS14Window
|
||||||
{
|
{
|
||||||
public delegate void CloseCallback();
|
|
||||||
public delegate void OptionSelectedCallback(int selectedOptionData);
|
public delegate void OptionSelectedCallback(int selectedOptionData);
|
||||||
|
|
||||||
private Control _vSplitContainer;
|
private readonly VBoxContainer _optionsBox;
|
||||||
private VBoxContainer _optionsBox;
|
|
||||||
private OptionSelectedCallback _optionSelectedCallback;
|
private OptionSelectedCallback _optionSelectedCallback;
|
||||||
|
|
||||||
|
|
||||||
protected override Vector2? CustomSize => (300, 400);
|
protected override Vector2? CustomSize => (300, 400);
|
||||||
|
|
||||||
public GenericSurgeryWindow()
|
public GenericSurgeryWindow()
|
||||||
{
|
{
|
||||||
Title = Loc.GetString("Select surgery target...");
|
Title = Loc.GetString("Select surgery target...");
|
||||||
RectClipContent = true;
|
RectClipContent = true;
|
||||||
_vSplitContainer = new VBoxContainer();
|
|
||||||
var listScrollContainer = new ScrollContainer
|
|
||||||
{
|
|
||||||
SizeFlagsVertical = SizeFlags.FillExpand,
|
|
||||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
|
||||||
HScrollEnabled = true,
|
|
||||||
VScrollEnabled = true
|
|
||||||
};
|
|
||||||
_optionsBox = new VBoxContainer
|
|
||||||
{
|
|
||||||
SizeFlagsHorizontal = SizeFlags.FillExpand
|
|
||||||
};
|
|
||||||
listScrollContainer.AddChild(_optionsBox);
|
|
||||||
_vSplitContainer.AddChild(listScrollContainer);
|
|
||||||
Contents.AddChild(_vSplitContainer);
|
|
||||||
|
|
||||||
|
var vSplitContainer = new VBoxContainer
|
||||||
|
{
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new ScrollContainer
|
||||||
|
{
|
||||||
|
SizeFlagsVertical = SizeFlags.FillExpand,
|
||||||
|
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||||
|
HScrollEnabled = true,
|
||||||
|
VScrollEnabled = true,
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
(_optionsBox = new VBoxContainer
|
||||||
|
{
|
||||||
|
SizeFlagsHorizontal = SizeFlags.FillExpand
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Contents.AddChild(vSplitContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void BuildDisplay(Dictionary<string, int> data, OptionSelectedCallback callback)
|
public void BuildDisplay(Dictionary<string, int> data, OptionSelectedCallback callback)
|
||||||
{
|
{
|
||||||
_optionsBox.DisposeAllChildren();
|
_optionsBox.DisposeAllChildren();
|
||||||
_optionSelectedCallback = callback;
|
_optionSelectedCallback = callback;
|
||||||
|
|
||||||
foreach (var (displayText, callbackData) in data)
|
foreach (var (displayText, callbackData) in data)
|
||||||
{
|
{
|
||||||
var button = new SurgeryButton(callbackData);
|
var button = new SurgeryButton(callbackData);
|
||||||
button.SetOnToggleBehavior(OnButtonPressed);
|
|
||||||
button.SetDisplayText(CultureInfo.CurrentCulture.TextInfo.ToTitleCase(displayText));
|
|
||||||
|
|
||||||
|
button.SetOnToggleBehavior(OnButtonPressed);
|
||||||
|
button.SetDisplayText(Loc.GetString(displayText));
|
||||||
|
|
||||||
_optionsBox.AddChild(button);
|
_optionsBox.AddChild(button);
|
||||||
}
|
}
|
||||||
@@ -60,17 +64,23 @@ namespace Content.Client.Health.BodySystem.Surgery
|
|||||||
|
|
||||||
private void OnButtonPressed(BaseButton.ButtonEventArgs args)
|
private void OnButtonPressed(BaseButton.ButtonEventArgs args)
|
||||||
{
|
{
|
||||||
var pressedButton = (SurgeryButton)args.Button.Parent;
|
if (args.Button.Parent is SurgeryButton surgery)
|
||||||
_optionSelectedCallback(pressedButton.CallbackData);
|
{
|
||||||
|
_optionSelectedCallback(surgery.CallbackData);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SurgeryButton : PanelContainer
|
class SurgeryButton : PanelContainer
|
||||||
{
|
{
|
||||||
public Button Button { get; }
|
public Button Button { get; }
|
||||||
|
|
||||||
private SpriteView SpriteView { get; }
|
private SpriteView SpriteView { get; }
|
||||||
|
|
||||||
private Control EntityControl { get; }
|
private Control EntityControl { get; }
|
||||||
|
|
||||||
private Label DisplayText { get; }
|
private Label DisplayText { get; }
|
||||||
|
|
||||||
public int CallbackData { get; }
|
public int CallbackData { get; }
|
||||||
|
|
||||||
public SurgeryButton(int callbackData)
|
public SurgeryButton(int callbackData)
|
||||||
@@ -84,25 +94,28 @@ namespace Content.Client.Health.BodySystem.Surgery
|
|||||||
ToggleMode = true,
|
ToggleMode = true,
|
||||||
MouseFilter = MouseFilterMode.Stop
|
MouseFilter = MouseFilterMode.Stop
|
||||||
};
|
};
|
||||||
|
|
||||||
AddChild(Button);
|
AddChild(Button);
|
||||||
var hBoxContainer = new HBoxContainer();
|
|
||||||
SpriteView = new SpriteView
|
AddChild(new HBoxContainer
|
||||||
{
|
{
|
||||||
CustomMinimumSize = new Vector2(32.0f, 32.0f)
|
Children =
|
||||||
};
|
{
|
||||||
DisplayText = new Label
|
(SpriteView = new SpriteView
|
||||||
{
|
{
|
||||||
SizeFlagsVertical = SizeFlags.ShrinkCenter,
|
CustomMinimumSize = new Vector2(32.0f, 32.0f)
|
||||||
Text = "N/A",
|
}),
|
||||||
};
|
(DisplayText = new Label
|
||||||
hBoxContainer.AddChild(SpriteView);
|
{
|
||||||
hBoxContainer.AddChild(DisplayText);
|
SizeFlagsVertical = SizeFlags.ShrinkCenter,
|
||||||
EntityControl = new Control
|
Text = "N/A",
|
||||||
{
|
}),
|
||||||
SizeFlagsHorizontal = SizeFlags.FillExpand
|
(new Control
|
||||||
};
|
{
|
||||||
hBoxContainer.AddChild(EntityControl);
|
SizeFlagsHorizontal = SizeFlags.FillExpand
|
||||||
AddChild(hBoxContainer);
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetDisplayText(string text)
|
public void SetDisplayText(string text)
|
||||||
@@ -37,7 +37,7 @@ namespace Content.Client.GameObjects.Components
|
|||||||
/// <returns>True if the click worked, false otherwise.</returns>
|
/// <returns>True if the click worked, false otherwise.</returns>
|
||||||
public bool CheckClick(Vector2 worldPos, out int drawDepth, out uint renderOrder)
|
public bool CheckClick(Vector2 worldPos, out int drawDepth, out uint renderOrder)
|
||||||
{
|
{
|
||||||
if (!Owner.TryGetComponent(out ISpriteComponent sprite) || !sprite.Visible)
|
if (!Owner.TryGetComponent(out ISpriteComponent? sprite) || !sprite.Visible)
|
||||||
{
|
{
|
||||||
drawDepth = default;
|
drawDepth = default;
|
||||||
renderOrder = default;
|
renderOrder = default;
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using Content.Shared.GameObjects.Components.Damage;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Client.GameObjects.Components
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Fuck I really hate doing this
|
|
||||||
/// TODO: make sure the client only gets damageable component on the clientside entity for its player mob
|
|
||||||
/// </summary>
|
|
||||||
[RegisterComponent]
|
|
||||||
public class DamageableComponent : SharedDamageableComponent
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override string Name => "Damageable";
|
|
||||||
|
|
||||||
public Dictionary<DamageType, int> CurrentDamage = new Dictionary<DamageType, int>();
|
|
||||||
|
|
||||||
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
|
|
||||||
{
|
|
||||||
base.HandleComponentState(curState, nextState);
|
|
||||||
|
|
||||||
if(curState is DamageComponentState damagestate)
|
|
||||||
{
|
|
||||||
CurrentDamage = damagestate.CurrentDamage;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
#nullable enable
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Client.GameObjects.Components.UserInterface;
|
||||||
|
using Robust.Shared.GameObjects.Components.UserInterface;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
using static Content.Shared.GameObjects.Components.Disposal.SharedDisposalRouterComponent;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Disposal
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a <see cref="DisposalRouterWindow"/> and updates it when new server messages are received.
|
||||||
|
/// </summary>
|
||||||
|
[UsedImplicitly]
|
||||||
|
public class DisposalRouterBoundUserInterface : BoundUserInterface
|
||||||
|
{
|
||||||
|
private DisposalRouterWindow? _window;
|
||||||
|
|
||||||
|
public DisposalRouterBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Open()
|
||||||
|
{
|
||||||
|
base.Open();
|
||||||
|
|
||||||
|
_window = new DisposalRouterWindow();
|
||||||
|
|
||||||
|
_window.OpenCentered();
|
||||||
|
_window.OnClose += Close;
|
||||||
|
|
||||||
|
_window.Confirm.OnPressed += _ => ButtonPressed(UiAction.Ok, _window.TagInput.Text);
|
||||||
|
_window.TagInput.OnTextEntered += args => ButtonPressed(UiAction.Ok, args.Text);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ButtonPressed(UiAction action, string tag)
|
||||||
|
{
|
||||||
|
SendMessage(new UiActionMessage(action, tag));
|
||||||
|
_window?.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateState(BoundUserInterfaceState state)
|
||||||
|
{
|
||||||
|
base.UpdateState(state);
|
||||||
|
|
||||||
|
if (!(state is DisposalRouterUserInterfaceState cast))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_window?.UpdateState(cast);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_window?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
using Content.Shared.GameObjects.Components.Disposal;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using static Content.Shared.GameObjects.Components.Disposal.SharedDisposalRouterComponent;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Disposal
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Client-side UI used to control a <see cref="SharedDisposalRouterComponent"/>
|
||||||
|
/// </summary>
|
||||||
|
public class DisposalRouterWindow : SS14Window
|
||||||
|
{
|
||||||
|
public readonly LineEdit TagInput;
|
||||||
|
public readonly Button Confirm;
|
||||||
|
|
||||||
|
protected override Vector2? CustomSize => (400, 80);
|
||||||
|
|
||||||
|
public DisposalRouterWindow()
|
||||||
|
{
|
||||||
|
Title = Loc.GetString("Disposal Router");
|
||||||
|
|
||||||
|
Contents.AddChild(new VBoxContainer
|
||||||
|
{
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new Label {Text = Loc.GetString("Tags:")},
|
||||||
|
new Control {CustomMinimumSize = (0, 10)},
|
||||||
|
new HBoxContainer
|
||||||
|
{
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
(TagInput = new LineEdit {SizeFlagsHorizontal = SizeFlags.Expand, CustomMinimumSize = (320, 0),
|
||||||
|
ToolTip = Loc.GetString("A comma separated list of tags"), IsValid = tags => TagRegex.IsMatch(tags)}),
|
||||||
|
new Control {CustomMinimumSize = (10, 0)},
|
||||||
|
(Confirm = new Button {Text = Loc.GetString("Confirm")})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void UpdateState(DisposalRouterUserInterfaceState state)
|
||||||
|
{
|
||||||
|
TagInput.Text = state.Tags;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
#nullable enable
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Client.GameObjects.Components.UserInterface;
|
||||||
|
using Robust.Shared.GameObjects.Components.UserInterface;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
using static Content.Shared.GameObjects.Components.Disposal.SharedDisposalTaggerComponent;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Disposal
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a <see cref="DisposalTaggerWindow"/> and updates it when new server messages are received.
|
||||||
|
/// </summary>
|
||||||
|
[UsedImplicitly]
|
||||||
|
public class DisposalTaggerBoundUserInterface : BoundUserInterface
|
||||||
|
{
|
||||||
|
private DisposalTaggerWindow? _window;
|
||||||
|
|
||||||
|
public DisposalTaggerBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Open()
|
||||||
|
{
|
||||||
|
base.Open();
|
||||||
|
|
||||||
|
_window = new DisposalTaggerWindow();
|
||||||
|
|
||||||
|
_window.OpenCentered();
|
||||||
|
_window.OnClose += Close;
|
||||||
|
|
||||||
|
_window.Confirm.OnPressed += _ => ButtonPressed(UiAction.Ok, _window.TagInput.Text);
|
||||||
|
_window.TagInput.OnTextEntered += args => ButtonPressed(UiAction.Ok, args.Text);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ButtonPressed(UiAction action, string tag)
|
||||||
|
{
|
||||||
|
SendMessage(new UiActionMessage(action, tag));
|
||||||
|
_window?.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateState(BoundUserInterfaceState state)
|
||||||
|
{
|
||||||
|
base.UpdateState(state);
|
||||||
|
|
||||||
|
if (!(state is DisposalTaggerUserInterfaceState cast))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_window?.UpdateState(cast);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_window?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
using Content.Shared.GameObjects.Components.Disposal;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using static Content.Shared.GameObjects.Components.Disposal.SharedDisposalTaggerComponent;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Disposal
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Client-side UI used to control a <see cref="SharedDisposalTaggerComponent"/>
|
||||||
|
/// </summary>
|
||||||
|
public class DisposalTaggerWindow : SS14Window
|
||||||
|
{
|
||||||
|
public readonly LineEdit TagInput;
|
||||||
|
public readonly Button Confirm;
|
||||||
|
|
||||||
|
protected override Vector2? CustomSize => (400, 80);
|
||||||
|
|
||||||
|
public DisposalTaggerWindow()
|
||||||
|
{
|
||||||
|
Title = Loc.GetString("Disposal Tagger");
|
||||||
|
|
||||||
|
Contents.AddChild(new VBoxContainer
|
||||||
|
{
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new Label {Text = Loc.GetString("Tag:")},
|
||||||
|
new Control {CustomMinimumSize = (0, 10)},
|
||||||
|
new HBoxContainer
|
||||||
|
{
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
(TagInput = new LineEdit {SizeFlagsHorizontal = SizeFlags.Expand, CustomMinimumSize = (320, 0),
|
||||||
|
IsValid = tag => TagRegex.IsMatch(tag)}),
|
||||||
|
new Control {CustomMinimumSize = (10, 0)},
|
||||||
|
(Confirm = new Button {Text = Loc.GetString("Confirm")})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void UpdateState(DisposalTaggerUserInterfaceState state)
|
||||||
|
{
|
||||||
|
TagInput.Text = state.Tag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -148,7 +148,7 @@ namespace Content.Client.GameObjects.Components.Items
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!entity.TryGetComponent(out ItemComponent item)) return;
|
if (!entity.TryGetComponent(out ItemComponent? item)) return;
|
||||||
|
|
||||||
var maybeInHands = item.GetInHandStateInfo(hand.Location);
|
var maybeInHands = item.GetInHandStateInfo(hand.Location);
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
|
using JetBrains.Annotations;
|
||||||
using Robust.Client.GameObjects.Components.UserInterface;
|
using Robust.Client.GameObjects.Components.UserInterface;
|
||||||
using Robust.Shared.GameObjects.Components.UserInterface;
|
using Robust.Shared.GameObjects.Components.UserInterface;
|
||||||
using static Content.Shared.GameObjects.Components.Medical.SharedMedicalScannerComponent;
|
using static Content.Shared.GameObjects.Components.Medical.SharedMedicalScannerComponent;
|
||||||
|
|
||||||
namespace Content.Client.GameObjects.Components.MedicalScanner
|
namespace Content.Client.GameObjects.Components.MedicalScanner
|
||||||
{
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
public class MedicalScannerBoundUserInterface : BoundUserInterface
|
public class MedicalScannerBoundUserInterface : BoundUserInterface
|
||||||
{
|
{
|
||||||
public MedicalScannerBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public MedicalScannerBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
||||||
@@ -20,6 +22,7 @@ namespace Content.Client.GameObjects.Components.MedicalScanner
|
|||||||
Title = Owner.Owner.Name,
|
Title = Owner.Owner.Name,
|
||||||
};
|
};
|
||||||
_window.OnClose += Close;
|
_window.OnClose += Close;
|
||||||
|
_window.ScanButton.OnPressed += _ => SendMessage(new UiButtonPressedMessage(UiButton.ScanDNA));
|
||||||
_window.OpenCentered();
|
_window.OpenCentered();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Content.Shared.Damage;
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
using Robust.Client.UserInterface.CustomControls;
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
using Robust.Shared.Maths;
|
using Robust.Shared.Maths;
|
||||||
using static Content.Shared.GameObjects.Components.Medical.SharedMedicalScannerComponent;
|
using static Content.Shared.GameObjects.Components.Medical.SharedMedicalScannerComponent;
|
||||||
|
|
||||||
@@ -8,28 +12,63 @@ namespace Content.Client.GameObjects.Components.MedicalScanner
|
|||||||
{
|
{
|
||||||
public class MedicalScannerWindow : SS14Window
|
public class MedicalScannerWindow : SS14Window
|
||||||
{
|
{
|
||||||
|
public readonly Button ScanButton;
|
||||||
|
private readonly Label _diagnostics;
|
||||||
protected override Vector2? CustomSize => (485, 90);
|
protected override Vector2? CustomSize => (485, 90);
|
||||||
|
|
||||||
|
public MedicalScannerWindow()
|
||||||
|
{
|
||||||
|
Contents.AddChild(new VBoxContainer
|
||||||
|
{
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
(ScanButton = new Button
|
||||||
|
{
|
||||||
|
Text = "Scan and Save DNA"
|
||||||
|
}),
|
||||||
|
(_diagnostics = new Label
|
||||||
|
{
|
||||||
|
Text = ""
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public void Populate(MedicalScannerBoundUserInterfaceState state)
|
public void Populate(MedicalScannerBoundUserInterfaceState state)
|
||||||
{
|
{
|
||||||
Contents.RemoveAllChildren();
|
|
||||||
var text = new StringBuilder();
|
var text = new StringBuilder();
|
||||||
if (state.MaxHealth == 0)
|
|
||||||
{
|
|
||||||
text.Append("No patient data.");
|
|
||||||
} else
|
|
||||||
{
|
|
||||||
text.Append($"Patient's health: {state.CurrentHealth}/{state.MaxHealth}\n");
|
|
||||||
|
|
||||||
if (state.DamageDictionary != null)
|
if (!state.Entity.HasValue ||
|
||||||
{
|
!state.HasDamage() ||
|
||||||
foreach (var (dmgType, amount) in state.DamageDictionary)
|
!IoCManager.Resolve<IEntityManager>().TryGetEntity(state.Entity.Value, out var entity))
|
||||||
{
|
{
|
||||||
text.Append($"\n{dmgType}: {amount}");
|
_diagnostics.Text = Loc.GetString("No patient data.");
|
||||||
}
|
ScanButton.Disabled = true;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
text.Append($"{entity.Name}{Loc.GetString("'s health:")}\n");
|
||||||
|
|
||||||
|
foreach (var (@class, classAmount) in state.DamageClasses)
|
||||||
|
{
|
||||||
|
text.Append($"\n{Loc.GetString("{0}: {1}", @class, classAmount)}");
|
||||||
|
|
||||||
|
foreach (var type in @class.ToTypes())
|
||||||
|
{
|
||||||
|
if (!state.DamageTypes.TryGetValue(type, out var typeAmount))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
text.Append($"\n- {Loc.GetString("{0}: {1}", type, typeAmount)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
text.Append("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
_diagnostics.Text = text.ToString();
|
||||||
|
ScanButton.Disabled = state.IsScanned;
|
||||||
}
|
}
|
||||||
Contents.AddChild(new Label(){Text = text.ToString()});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ namespace Content.Client.GameObjects.Components.Mobs
|
|||||||
private const float RestoreRateRamp = 0.1f;
|
private const float RestoreRateRamp = 0.1f;
|
||||||
|
|
||||||
// The maximum magnitude of the kick applied to the camera at any point.
|
// The maximum magnitude of the kick applied to the camera at any point.
|
||||||
private const float KickMagnitudeMax = 5f;
|
private const float KickMagnitudeMax = 2f;
|
||||||
|
|
||||||
private Vector2 _currentKick;
|
private Vector2 _currentKick;
|
||||||
private float _lastKickTime;
|
private float _lastKickTime;
|
||||||
|
|||||||
@@ -44,8 +44,8 @@ namespace Content.Client.GameObjects.Components.Mobs
|
|||||||
sprite.LayerSetColor(HumanoidVisualLayers.Hair, Appearance.HairColor);
|
sprite.LayerSetColor(HumanoidVisualLayers.Hair, Appearance.HairColor);
|
||||||
sprite.LayerSetColor(HumanoidVisualLayers.FacialHair, Appearance.FacialHairColor);
|
sprite.LayerSetColor(HumanoidVisualLayers.FacialHair, Appearance.FacialHairColor);
|
||||||
|
|
||||||
sprite.LayerSetState(HumanoidVisualLayers.Chest, Sex == Sex.Male ? "human_chest_m" : "human_chest_f");
|
sprite.LayerSetState(HumanoidVisualLayers.Chest, Sex == Sex.Male ? "torso_m" : "torso_f");
|
||||||
sprite.LayerSetState(HumanoidVisualLayers.Head, Sex == Sex.Male ? "human_head_m" : "human_head_f");
|
sprite.LayerSetState(HumanoidVisualLayers.Head, Sex == Sex.Male ? "head_m" : "head_f");
|
||||||
|
|
||||||
sprite.LayerSetVisible(HumanoidVisualLayers.StencilMask, Sex == Sex.Female);
|
sprite.LayerSetVisible(HumanoidVisualLayers.StencilMask, Sex == Sex.Female);
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
using Content.Client.GameObjects.Components.Disposal;
|
|
||||||
using Content.Client.Interfaces.GameObjects.Components.Interaction;
|
|
||||||
using Content.Shared.GameObjects.Components.Mobs;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Client.GameObjects.Components.Mobs
|
|
||||||
{
|
|
||||||
[RegisterComponent]
|
|
||||||
[ComponentReference(typeof(SharedSpeciesComponent))]
|
|
||||||
public class SpeciesComponent : SharedSpeciesComponent, IClientDraggable
|
|
||||||
{
|
|
||||||
bool IClientDraggable.ClientCanDropOn(CanDropEventArgs eventArgs)
|
|
||||||
{
|
|
||||||
return eventArgs.Target.HasComponent<DisposalUnitComponent>();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IClientDraggable.ClientCanDrag(CanDragEventArgs eventArgs)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -34,7 +34,7 @@ namespace Content.Client.GameObjects.Components.Mobs
|
|||||||
WalkModifierOverride = state.WalkModifierOverride;
|
WalkModifierOverride = state.WalkModifierOverride;
|
||||||
RunModifierOverride = state.RunModifierOverride;
|
RunModifierOverride = state.RunModifierOverride;
|
||||||
|
|
||||||
if (Owner.TryGetComponent(out MovementSpeedModifierComponent movement))
|
if (Owner.TryGetComponent(out MovementSpeedModifierComponent? movement))
|
||||||
{
|
{
|
||||||
movement.RefreshMovementSpeedModifiers();
|
movement.RefreshMovementSpeedModifiers();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Content.Shared.GameObjects.Components.Movement;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Movement
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
[ComponentReference(typeof(IClimbable))]
|
||||||
|
public class ClimbableComponent : SharedClimbableComponent
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects.Components;
|
||||||
|
using Content.Shared.GameObjects.Components.Movement;
|
||||||
|
using Content.Client.Interfaces.GameObjects.Components.Interaction;
|
||||||
|
using Content.Shared.Physics;
|
||||||
|
|
||||||
|
namespace Content.Client.GameObjects.Components.Movement
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
public class ClimbingComponent : SharedClimbingComponent, IClientDraggable
|
||||||
|
{
|
||||||
|
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
|
||||||
|
{
|
||||||
|
if (!(curState is ClimbModeComponentState climbModeState) || Body == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IsClimbing = climbModeState.Climbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool IsClimbing { get; set; }
|
||||||
|
|
||||||
|
bool IClientDraggable.ClientCanDropOn(CanDropEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
return eventArgs.Target.HasComponent<IClimbable>();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IClientDraggable.ClientCanDrag(CanDragEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,7 +20,7 @@ namespace Content.Client.GameObjects.Components.Nutrition
|
|||||||
|
|
||||||
_currentHungerThreshold = hunger.CurrentThreshold;
|
_currentHungerThreshold = hunger.CurrentThreshold;
|
||||||
|
|
||||||
if (Owner.TryGetComponent(out MovementSpeedModifierComponent movement))
|
if (Owner.TryGetComponent(out MovementSpeedModifierComponent? movement))
|
||||||
{
|
{
|
||||||
movement.RefreshMovementSpeedModifiers();
|
movement.RefreshMovementSpeedModifiers();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ namespace Content.Client.GameObjects.Components.Nutrition
|
|||||||
|
|
||||||
_currentThirstThreshold = thirst.CurrentThreshold;
|
_currentThirstThreshold = thirst.CurrentThreshold;
|
||||||
|
|
||||||
if (Owner.TryGetComponent(out MovementSpeedModifierComponent movement))
|
if (Owner.TryGetComponent(out MovementSpeedModifierComponent? movement))
|
||||||
{
|
{
|
||||||
movement.RefreshMovementSpeedModifiers();
|
movement.RefreshMovementSpeedModifiers();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Content.Shared.GameObjects.Components.PDA;
|
using Content.Shared.GameObjects.Components.PDA;
|
||||||
using Robust.Client.GameObjects;
|
using Robust.Client.GameObjects;
|
||||||
using Robust.Client.Interfaces.GameObjects.Components;
|
using Robust.Client.Interfaces.GameObjects.Components;
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ namespace Content.Client.GameObjects.Components.PDA
|
|||||||
private enum PDAVisualLayers
|
private enum PDAVisualLayers
|
||||||
{
|
{
|
||||||
Base,
|
Base,
|
||||||
Unlit
|
Flashlight
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -22,13 +22,13 @@ namespace Content.Client.GameObjects.Components.PDA
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var sprite = component.Owner.GetComponent<ISpriteComponent>();
|
var sprite = component.Owner.GetComponent<ISpriteComponent>();
|
||||||
sprite.LayerSetVisible(PDAVisualLayers.Unlit, false);
|
sprite.LayerSetVisible(PDAVisualLayers.Flashlight, false);
|
||||||
if(!component.TryGetData<bool>(PDAVisuals.ScreenLit, out var isScreenLit))
|
if(!component.TryGetData<bool>(PDAVisuals.FlashlightLit, out var isScreenLit))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sprite.LayerSetState(PDAVisualLayers.Unlit, "unlit_pda_screen");
|
sprite.LayerSetState(PDAVisualLayers.Flashlight, "light_overlay");
|
||||||
sprite.LayerSetVisible(PDAVisualLayers.Unlit, isScreenLit);
|
sprite.LayerSetVisible(PDAVisualLayers.Flashlight, isScreenLit);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using Content.Shared.GameObjects.Components.Mobs;
|
using Content.Shared.GameObjects.Components.Rotation;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using Robust.Client.Animations;
|
using Robust.Client.Animations;
|
||||||
using Robust.Client.GameObjects;
|
using Robust.Client.GameObjects;
|
||||||
using Robust.Client.GameObjects.Components.Animations;
|
using Robust.Client.GameObjects.Components.Animations;
|
||||||
@@ -7,22 +8,23 @@ using Robust.Client.Interfaces.GameObjects.Components;
|
|||||||
using Robust.Shared.Animations;
|
using Robust.Shared.Animations;
|
||||||
using Robust.Shared.Maths;
|
using Robust.Shared.Maths;
|
||||||
|
|
||||||
namespace Content.Client.GameObjects.Components.Mobs
|
namespace Content.Client.GameObjects.Components.Rotation
|
||||||
{
|
{
|
||||||
public class SpeciesVisualizer : AppearanceVisualizer
|
[UsedImplicitly]
|
||||||
|
public class RotationVisualizer : AppearanceVisualizer
|
||||||
{
|
{
|
||||||
public override void OnChangeData(AppearanceComponent component)
|
public override void OnChangeData(AppearanceComponent component)
|
||||||
{
|
{
|
||||||
base.OnChangeData(component);
|
base.OnChangeData(component);
|
||||||
|
|
||||||
if (component.TryGetData<SharedSpeciesComponent.MobState>(SharedSpeciesComponent.MobVisuals.RotationState, out var state))
|
if (component.TryGetData<RotationState>(RotationVisuals.RotationState, out var state))
|
||||||
{
|
{
|
||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
case SharedSpeciesComponent.MobState.Standing:
|
case RotationState.Vertical:
|
||||||
SetRotation(component, 0);
|
SetRotation(component, 0);
|
||||||
break;
|
break;
|
||||||
case SharedSpeciesComponent.MobState.Down:
|
case RotationState.Horizontal:
|
||||||
SetRotation(component, Angle.FromDegrees(90));
|
SetRotation(component, Angle.FromDegrees(90));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -40,7 +42,9 @@ namespace Content.Client.GameObjects.Components.Mobs
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (animation.HasRunningAnimation("rotate"))
|
if (animation.HasRunningAnimation("rotate"))
|
||||||
|
{
|
||||||
animation.Stop("rotate");
|
animation.Stop("rotate");
|
||||||
|
}
|
||||||
|
|
||||||
animation.Play(new Animation
|
animation.Play(new Animation
|
||||||
{
|
{
|
||||||
@@ -143,7 +143,7 @@ namespace Content.Client.GameObjects.EntitySystems.DoAfter
|
|||||||
{
|
{
|
||||||
base.FrameUpdate(args);
|
base.FrameUpdate(args);
|
||||||
|
|
||||||
if (AttachedEntity?.IsValid() != true || !AttachedEntity.TryGetComponent(out DoAfterComponent doAfterComponent))
|
if (AttachedEntity?.IsValid() != true || !AttachedEntity.TryGetComponent(out DoAfterComponent? doAfterComponent))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ namespace Content.Client.GameObjects.EntitySystems.DoAfter
|
|||||||
Gui ??= new DoAfterGui();
|
Gui ??= new DoAfterGui();
|
||||||
Gui.AttachedEntity = entity;
|
Gui.AttachedEntity = entity;
|
||||||
|
|
||||||
if (entity.TryGetComponent(out DoAfterComponent doAfterComponent))
|
if (entity.TryGetComponent(out DoAfterComponent? doAfterComponent))
|
||||||
{
|
{
|
||||||
foreach (var (_, doAfter) in doAfterComponent.DoAfters)
|
foreach (var (_, doAfter) in doAfterComponent.DoAfters)
|
||||||
{
|
{
|
||||||
@@ -87,7 +87,7 @@ namespace Content.Client.GameObjects.EntitySystems.DoAfter
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_player.TryGetComponent(out DoAfterComponent doAfterComponent))
|
if (!_player.TryGetComponent(out DoAfterComponent? doAfterComponent))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
using System;
|
#nullable enable
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Content.Client.Atmos;
|
||||||
|
using Content.Client.GameObjects.Components.Atmos;
|
||||||
using Content.Shared.Atmos;
|
using Content.Shared.Atmos;
|
||||||
using Content.Shared.GameObjects.EntitySystems;
|
using Content.Shared.GameObjects.EntitySystems.Atmos;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.GameObjects.EntitySystems;
|
||||||
using Robust.Client.Graphics;
|
using Robust.Client.Graphics;
|
||||||
|
using Robust.Client.Interfaces.Graphics.Overlays;
|
||||||
using Robust.Client.Interfaces.ResourceManagement;
|
using Robust.Client.Interfaces.ResourceManagement;
|
||||||
using Robust.Client.ResourceManagement;
|
using Robust.Client.ResourceManagement;
|
||||||
using Robust.Client.Utility;
|
using Robust.Client.Utility;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Shared.Interfaces.Map;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Maths;
|
using Robust.Shared.Maths;
|
||||||
@@ -16,8 +22,9 @@ using Robust.Shared.Utility;
|
|||||||
namespace Content.Client.GameObjects.EntitySystems
|
namespace Content.Client.GameObjects.EntitySystems
|
||||||
{
|
{
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public sealed class GasTileOverlaySystem : SharedGasTileOverlaySystem
|
internal sealed class GasTileOverlaySystem : SharedGasTileOverlaySystem
|
||||||
{
|
{
|
||||||
|
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||||
|
|
||||||
private readonly Dictionary<float, Color> _fireCache = new Dictionary<float, Color>();
|
private readonly Dictionary<float, Color> _fireCache = new Dictionary<float, Color>();
|
||||||
@@ -36,19 +43,20 @@ namespace Content.Client.GameObjects.EntitySystems
|
|||||||
private readonly float[][] _fireFrameDelays = new float[FireStates][];
|
private readonly float[][] _fireFrameDelays = new float[FireStates][];
|
||||||
private readonly int[] _fireFrameCounter = new int[FireStates];
|
private readonly int[] _fireFrameCounter = new int[FireStates];
|
||||||
private readonly Texture[][] _fireFrames = new Texture[FireStates][];
|
private readonly Texture[][] _fireFrames = new Texture[FireStates][];
|
||||||
|
|
||||||
private Dictionary<GridId, Dictionary<MapIndices, GasOverlayData>> _overlay = new Dictionary<GridId, Dictionary<MapIndices, GasOverlayData>>();
|
private Dictionary<GridId, Dictionary<MapIndices, GasOverlayChunk>> _tileData =
|
||||||
|
new Dictionary<GridId, Dictionary<MapIndices, GasOverlayChunk>>();
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
SubscribeNetworkEvent<GasOverlayMessage>(HandleGasOverlayMessage);
|
||||||
SubscribeNetworkEvent(new EntityEventHandler<GasTileOverlayMessage>(OnTileOverlayMessage));
|
_mapManager.OnGridRemoved += OnGridRemoved;
|
||||||
|
|
||||||
for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
|
for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
|
||||||
{
|
{
|
||||||
var gas = Atmospherics.GetGas(i);
|
var overlay = Atmospherics.GetOverlay(i);
|
||||||
switch (gas.GasOverlay)
|
switch (overlay)
|
||||||
{
|
{
|
||||||
case SpriteSpecifier.Rsi animated:
|
case SpriteSpecifier.Rsi animated:
|
||||||
var rsi = _resourceCache.GetResource<RSIResource>(animated.RsiPath).RSI;
|
var rsi = _resourceCache.GetResource<RSIResource>(animated.RsiPath).RSI;
|
||||||
@@ -82,13 +90,77 @@ namespace Content.Client.GameObjects.EntitySystems
|
|||||||
_fireFrameDelays[i] = state.GetDelays();
|
_fireFrameDelays[i] = state.GetDelays();
|
||||||
_fireFrameCounter[i] = 0;
|
_fireFrameCounter[i] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var overlayManager = IoCManager.Resolve<IOverlayManager>();
|
||||||
|
if(!overlayManager.HasOverlay(nameof(GasTileOverlay)))
|
||||||
|
overlayManager.AddOverlay(new GasTileOverlay());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleGasOverlayMessage(GasOverlayMessage message)
|
||||||
|
{
|
||||||
|
foreach (var (indices, data) in message.OverlayData)
|
||||||
|
{
|
||||||
|
var chunk = GetOrCreateChunk(message.GridId, indices);
|
||||||
|
chunk.Update(data, indices);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slightly different to the server-side system version
|
||||||
|
private GasOverlayChunk GetOrCreateChunk(GridId gridId, MapIndices indices)
|
||||||
|
{
|
||||||
|
if (!_tileData.TryGetValue(gridId, out var chunks))
|
||||||
|
{
|
||||||
|
chunks = new Dictionary<MapIndices, GasOverlayChunk>();
|
||||||
|
_tileData[gridId] = chunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
var chunkIndices = GetGasChunkIndices(indices);
|
||||||
|
|
||||||
|
if (!chunks.TryGetValue(chunkIndices, out var chunk))
|
||||||
|
{
|
||||||
|
chunk = new GasOverlayChunk(gridId, chunkIndices);
|
||||||
|
chunks[chunkIndices] = chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Shutdown()
|
||||||
|
{
|
||||||
|
base.Shutdown();
|
||||||
|
_mapManager.OnGridRemoved -= OnGridRemoved;
|
||||||
|
var overlayManager = IoCManager.Resolve<IOverlayManager>();
|
||||||
|
if(!overlayManager.HasOverlay(nameof(GasTileOverlay)))
|
||||||
|
overlayManager.RemoveOverlay(nameof(GasTileOverlay));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGridRemoved(GridId gridId)
|
||||||
|
{
|
||||||
|
if (_tileData.ContainsKey(gridId))
|
||||||
|
{
|
||||||
|
_tileData.Remove(gridId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasData(GridId gridId)
|
||||||
|
{
|
||||||
|
return _tileData.ContainsKey(gridId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public (Texture, Color color)[] GetOverlays(GridId gridIndex, MapIndices indices)
|
public (Texture, Color color)[] GetOverlays(GridId gridIndex, MapIndices indices)
|
||||||
{
|
{
|
||||||
if (!_overlay.TryGetValue(gridIndex, out var tiles) || !tiles.TryGetValue(indices, out var overlays))
|
if (!_tileData.TryGetValue(gridIndex, out var chunks))
|
||||||
return Array.Empty<(Texture, Color)>();
|
return Array.Empty<(Texture, Color)>();
|
||||||
|
|
||||||
|
var chunkIndex = GetGasChunkIndices(indices);
|
||||||
|
if (!chunks.TryGetValue(chunkIndex, out var chunk))
|
||||||
|
return Array.Empty<(Texture, Color)>();
|
||||||
|
|
||||||
|
var overlays = chunk.GetData(indices);
|
||||||
|
|
||||||
|
if (overlays.Gas == null)
|
||||||
|
return Array.Empty<(Texture, Color)>();
|
||||||
|
|
||||||
var fire = overlays.FireState != 0;
|
var fire = overlays.FireState != 0;
|
||||||
var length = overlays.Gas.Length + (fire ? 1 : 0);
|
var length = overlays.Gas.Length + (fire ? 1 : 0);
|
||||||
|
|
||||||
@@ -112,23 +184,6 @@ namespace Content.Client.GameObjects.EntitySystems
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnTileOverlayMessage(GasTileOverlayMessage ev)
|
|
||||||
{
|
|
||||||
if(ev.ClearAllOtherOverlays)
|
|
||||||
_overlay.Clear();
|
|
||||||
|
|
||||||
foreach (var data in ev.OverlayData)
|
|
||||||
{
|
|
||||||
if (!_overlay.TryGetValue(data.GridIndex, out var gridOverlays))
|
|
||||||
{
|
|
||||||
gridOverlays = new Dictionary<MapIndices, GasOverlayData>();
|
|
||||||
_overlay.Add(data.GridIndex, gridOverlays);
|
|
||||||
}
|
|
||||||
|
|
||||||
gridOverlays[data.GridIndices] = data.Data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void FrameUpdate(float frameTime)
|
public override void FrameUpdate(float frameTime)
|
||||||
{
|
{
|
||||||
base.FrameUpdate(frameTime);
|
base.FrameUpdate(frameTime);
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ namespace Content.Client.GameObjects.EntitySystems
|
|||||||
{
|
{
|
||||||
var playerEnt = _playerManager.LocalPlayer?.ControlledEntity;
|
var playerEnt = _playerManager.LocalPlayer?.ControlledEntity;
|
||||||
|
|
||||||
if (playerEnt == null || !playerEnt.TryGetComponent(out IMoverComponent mover))
|
if (playerEnt == null || !playerEnt.TryGetComponent(out IMoverComponent? mover))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Content.Client.Interfaces;
|
using Content.Client.Interfaces;
|
||||||
using Content.Client.State;
|
using Content.Client.State;
|
||||||
using Content.Client.UserInterface;
|
using Content.Client.UserInterface;
|
||||||
using Content.Shared;
|
using Content.Shared;
|
||||||
|
using Content.Shared.Network.NetMessages;
|
||||||
|
using Robust.Client.Interfaces.Graphics;
|
||||||
using Robust.Client.Interfaces.State;
|
using Robust.Client.Interfaces.State;
|
||||||
using Robust.Shared.Interfaces.Network;
|
using Robust.Shared.Interfaces.Network;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Network;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
using Robust.Shared.ViewVariables;
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
@@ -22,12 +26,16 @@ namespace Content.Client.GameTicking
|
|||||||
|
|
||||||
[ViewVariables] public bool AreWeReady { get; private set; }
|
[ViewVariables] public bool AreWeReady { get; private set; }
|
||||||
[ViewVariables] public bool IsGameStarted { get; private set; }
|
[ViewVariables] public bool IsGameStarted { get; private set; }
|
||||||
|
[ViewVariables] public bool DisallowedLateJoin { get; private set; }
|
||||||
[ViewVariables] public string ServerInfoBlob { get; private set; }
|
[ViewVariables] public string ServerInfoBlob { get; private set; }
|
||||||
[ViewVariables] public DateTime StartTime { get; private set; }
|
[ViewVariables] public DateTime StartTime { get; private set; }
|
||||||
[ViewVariables] public bool Paused { get; private set; }
|
[ViewVariables] public bool Paused { get; private set; }
|
||||||
|
[ViewVariables] public Dictionary<NetSessionId, bool> Ready { get; private set; }
|
||||||
|
|
||||||
public event Action InfoBlobUpdated;
|
public event Action InfoBlobUpdated;
|
||||||
public event Action LobbyStatusUpdated;
|
public event Action LobbyStatusUpdated;
|
||||||
|
public event Action LobbyReadyUpdated;
|
||||||
|
public event Action LobbyLateJoinStatusUpdated;
|
||||||
|
|
||||||
public void Initialize()
|
public void Initialize()
|
||||||
{
|
{
|
||||||
@@ -38,11 +46,23 @@ namespace Content.Client.GameTicking
|
|||||||
_netManager.RegisterNetMessage<MsgTickerLobbyStatus>(nameof(MsgTickerLobbyStatus), LobbyStatus);
|
_netManager.RegisterNetMessage<MsgTickerLobbyStatus>(nameof(MsgTickerLobbyStatus), LobbyStatus);
|
||||||
_netManager.RegisterNetMessage<MsgTickerLobbyInfo>(nameof(MsgTickerLobbyInfo), LobbyInfo);
|
_netManager.RegisterNetMessage<MsgTickerLobbyInfo>(nameof(MsgTickerLobbyInfo), LobbyInfo);
|
||||||
_netManager.RegisterNetMessage<MsgTickerLobbyCountdown>(nameof(MsgTickerLobbyCountdown), LobbyCountdown);
|
_netManager.RegisterNetMessage<MsgTickerLobbyCountdown>(nameof(MsgTickerLobbyCountdown), LobbyCountdown);
|
||||||
|
_netManager.RegisterNetMessage<MsgTickerLobbyReady>(nameof(MsgTickerLobbyReady), LobbyReady);
|
||||||
_netManager.RegisterNetMessage<MsgRoundEndMessage>(nameof(MsgRoundEndMessage), RoundEnd);
|
_netManager.RegisterNetMessage<MsgRoundEndMessage>(nameof(MsgRoundEndMessage), RoundEnd);
|
||||||
|
_netManager.RegisterNetMessage<MsgRequestWindowAttention>(nameof(MsgRequestWindowAttention), msg =>
|
||||||
|
{
|
||||||
|
IoCManager.Resolve<IClyde>().RequestWindowAttention();
|
||||||
|
});
|
||||||
|
_netManager.RegisterNetMessage<MsgTickerLateJoinStatus>(nameof(MsgTickerLateJoinStatus), LateJoinStatus);
|
||||||
|
|
||||||
|
Ready = new Dictionary<NetSessionId, bool>();
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void LateJoinStatus(MsgTickerLateJoinStatus message)
|
||||||
|
{
|
||||||
|
DisallowedLateJoin = message.Disallowed;
|
||||||
|
LobbyLateJoinStatusUpdated?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private void JoinLobby(MsgTickerJoinLobby message)
|
private void JoinLobby(MsgTickerJoinLobby message)
|
||||||
@@ -56,6 +76,8 @@ namespace Content.Client.GameTicking
|
|||||||
IsGameStarted = message.IsRoundStarted;
|
IsGameStarted = message.IsRoundStarted;
|
||||||
AreWeReady = message.YouAreReady;
|
AreWeReady = message.YouAreReady;
|
||||||
Paused = message.Paused;
|
Paused = message.Paused;
|
||||||
|
if (IsGameStarted)
|
||||||
|
Ready.Clear();
|
||||||
|
|
||||||
LobbyStatusUpdated?.Invoke();
|
LobbyStatusUpdated?.Invoke();
|
||||||
}
|
}
|
||||||
@@ -78,11 +100,20 @@ namespace Content.Client.GameTicking
|
|||||||
Paused = message.Paused;
|
Paused = message.Paused;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void LobbyReady(MsgTickerLobbyReady message)
|
||||||
|
{
|
||||||
|
// Merge the Dictionaries
|
||||||
|
foreach (var p in message.PlayerReady)
|
||||||
|
{
|
||||||
|
Ready[p.Key] = p.Value;
|
||||||
|
}
|
||||||
|
LobbyReadyUpdated?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
private void RoundEnd(MsgRoundEndMessage message)
|
private void RoundEnd(MsgRoundEndMessage message)
|
||||||
{
|
{
|
||||||
|
|
||||||
//This is not ideal at all, but I don't see an immediately better fit anywhere else.
|
//This is not ideal at all, but I don't see an immediately better fit anywhere else.
|
||||||
var roundEnd = new RoundEndSummaryWindow(message.GamemodeTitle, message.RoundDuration, message.AllPlayersEndInfo);
|
var roundEnd = new RoundEndSummaryWindow(message.GamemodeTitle, message.RoundEndText, message.RoundDuration, message.AllPlayersEndInfo);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,155 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
|
||||||
using Content.Shared.Health.BodySystem.BodyScanner;
|
|
||||||
using Robust.Client.UserInterface.Controls;
|
|
||||||
using Robust.Client.UserInterface.CustomControls;
|
|
||||||
using Robust.Shared.IoC;
|
|
||||||
using Robust.Shared.Localization;
|
|
||||||
using Robust.Shared.Maths;
|
|
||||||
using static Robust.Client.UserInterface.Controls.ItemList;
|
|
||||||
|
|
||||||
namespace Content.Client.Health.BodySystem.BodyScanner
|
|
||||||
{
|
|
||||||
public sealed class BodyScannerDisplay : SS14Window
|
|
||||||
{
|
|
||||||
#pragma warning disable 649
|
|
||||||
[Dependency] private readonly ILocalizationManager _loc;
|
|
||||||
#pragma warning restore 649
|
|
||||||
|
|
||||||
public BodyScannerBoundUserInterface Owner { get; private set; }
|
|
||||||
protected override Vector2? CustomSize => (800, 600);
|
|
||||||
private ItemList BodyPartList { get; }
|
|
||||||
private Label BodyPartLabel { get; }
|
|
||||||
private Label BodyPartHealth { get; }
|
|
||||||
private ItemList MechanismList { get; }
|
|
||||||
private RichTextLabel MechanismInfoLabel { get; }
|
|
||||||
|
|
||||||
|
|
||||||
private BodyScannerTemplateData _template;
|
|
||||||
private Dictionary<string, BodyScannerBodyPartData> _parts;
|
|
||||||
private List<string> _slots;
|
|
||||||
private BodyScannerBodyPartData _currentBodyPart;
|
|
||||||
|
|
||||||
|
|
||||||
public BodyScannerDisplay(BodyScannerBoundUserInterface owner)
|
|
||||||
{
|
|
||||||
IoCManager.InjectDependencies(this);
|
|
||||||
Owner = owner;
|
|
||||||
Title = _loc.GetString("Body Scanner");
|
|
||||||
|
|
||||||
var hSplit = new HBoxContainer();
|
|
||||||
Contents.AddChild(hSplit);
|
|
||||||
|
|
||||||
//Left half
|
|
||||||
var scrollBox = new ScrollContainer
|
|
||||||
{
|
|
||||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
|
||||||
};
|
|
||||||
hSplit.AddChild(scrollBox);
|
|
||||||
BodyPartList = new ItemList { };
|
|
||||||
scrollBox.AddChild(BodyPartList);
|
|
||||||
BodyPartList.OnItemSelected += BodyPartOnItemSelected;
|
|
||||||
|
|
||||||
//Right half
|
|
||||||
var vSplit = new VBoxContainer
|
|
||||||
{
|
|
||||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
|
||||||
};
|
|
||||||
hSplit.AddChild(vSplit);
|
|
||||||
|
|
||||||
//Top half of the right half
|
|
||||||
var limbBox = new VBoxContainer
|
|
||||||
{
|
|
||||||
SizeFlagsVertical = SizeFlags.FillExpand
|
|
||||||
};
|
|
||||||
vSplit.AddChild(limbBox);
|
|
||||||
BodyPartLabel = new Label();
|
|
||||||
limbBox.AddChild(BodyPartLabel);
|
|
||||||
var limbHealthHBox = new HBoxContainer();
|
|
||||||
limbBox.AddChild(limbHealthHBox);
|
|
||||||
var healthLabel = new Label
|
|
||||||
{
|
|
||||||
Text = "Health: "
|
|
||||||
};
|
|
||||||
limbHealthHBox.AddChild(healthLabel);
|
|
||||||
BodyPartHealth = new Label();
|
|
||||||
limbHealthHBox.AddChild(BodyPartHealth);
|
|
||||||
var limbScroll = new ScrollContainer
|
|
||||||
{
|
|
||||||
SizeFlagsVertical = SizeFlags.FillExpand
|
|
||||||
};
|
|
||||||
limbBox.AddChild(limbScroll);
|
|
||||||
MechanismList = new ItemList();
|
|
||||||
limbScroll.AddChild(MechanismList);
|
|
||||||
MechanismList.OnItemSelected += MechanismOnItemSelected;
|
|
||||||
|
|
||||||
//Bottom half of the right half
|
|
||||||
MechanismInfoLabel = new RichTextLabel
|
|
||||||
{
|
|
||||||
SizeFlagsVertical = SizeFlags.FillExpand
|
|
||||||
};
|
|
||||||
vSplit.AddChild(MechanismInfoLabel);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void UpdateDisplay(BodyScannerTemplateData template, Dictionary<string, BodyScannerBodyPartData> parts)
|
|
||||||
{
|
|
||||||
_template = template;
|
|
||||||
_parts = parts;
|
|
||||||
_slots = new List<string>();
|
|
||||||
BodyPartList.Clear();
|
|
||||||
foreach (var (key, value) in _parts)
|
|
||||||
{
|
|
||||||
_slots.Add(key); //We have to do this since ItemLists only return the index of what item is selected and dictionaries don't allow you to explicitly grab things by index.
|
|
||||||
//So we put the contents of the dictionary into a list so that we can grab the list by index. I don't know either.
|
|
||||||
BodyPartList.AddItem(CultureInfo.CurrentCulture.TextInfo.ToTitleCase(key));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void BodyPartOnItemSelected(ItemListSelectedEventArgs args)
|
|
||||||
{
|
|
||||||
if(_parts.TryGetValue(_slots[args.ItemIndex], out _currentBodyPart)) {
|
|
||||||
UpdateBodyPartBox(_currentBodyPart, _slots[args.ItemIndex]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private void UpdateBodyPartBox(BodyScannerBodyPartData part, string slotName)
|
|
||||||
{
|
|
||||||
BodyPartLabel.Text = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(slotName) + ": " + CultureInfo.CurrentCulture.TextInfo.ToTitleCase(part.Name);
|
|
||||||
BodyPartHealth.Text = part.CurrentDurability + "/" + part.MaxDurability;
|
|
||||||
|
|
||||||
MechanismList.Clear();
|
|
||||||
foreach (var mechanism in part.Mechanisms) {
|
|
||||||
MechanismList.AddItem(mechanism.Name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void MechanismOnItemSelected(ItemListSelectedEventArgs args)
|
|
||||||
{
|
|
||||||
UpdateMechanismBox(_currentBodyPart.Mechanisms[args.ItemIndex]);
|
|
||||||
}
|
|
||||||
private void UpdateMechanismBox(BodyScannerMechanismData mechanism)
|
|
||||||
{
|
|
||||||
//TODO: Make UI look less shit and clean up whatever the fuck this is lmao
|
|
||||||
if (mechanism != null)
|
|
||||||
{
|
|
||||||
string message = "";
|
|
||||||
message += mechanism.Name;
|
|
||||||
message += "\nHealth: ";
|
|
||||||
message += mechanism.CurrentDurability;
|
|
||||||
message += "/";
|
|
||||||
message += mechanism.MaxDurability;
|
|
||||||
message += "\n";
|
|
||||||
message += mechanism.Description;
|
|
||||||
MechanismInfoLabel.SetMessage(message);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
MechanismInfoLabel.SetMessage("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -96,7 +96,6 @@
|
|||||||
"BarSign",
|
"BarSign",
|
||||||
"DroppedBodyPart",
|
"DroppedBodyPart",
|
||||||
"DroppedMechanism",
|
"DroppedMechanism",
|
||||||
"BodyManager",
|
|
||||||
"SolarPanel",
|
"SolarPanel",
|
||||||
"BodyScanner",
|
"BodyScanner",
|
||||||
"Stunbaton",
|
"Stunbaton",
|
||||||
@@ -143,6 +142,8 @@
|
|||||||
"Listening",
|
"Listening",
|
||||||
"Radio",
|
"Radio",
|
||||||
"DisposalHolder",
|
"DisposalHolder",
|
||||||
|
"DisposalTagger",
|
||||||
|
"DisposalRouter",
|
||||||
"DisposalTransit",
|
"DisposalTransit",
|
||||||
"DisposalEntry",
|
"DisposalEntry",
|
||||||
"DisposalJunction",
|
"DisposalJunction",
|
||||||
@@ -157,6 +158,10 @@
|
|||||||
"Vapor",
|
"Vapor",
|
||||||
"DamageOnHighSpeedImpact",
|
"DamageOnHighSpeedImpact",
|
||||||
"Barotrauma",
|
"Barotrauma",
|
||||||
|
"GasSprayer",
|
||||||
|
"GasVapor",
|
||||||
|
"MobStateManager",
|
||||||
|
"Metabolism",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ namespace Content.Client.Input
|
|||||||
common.AddFunction(ContentKeyFunctions.OpenTutorial);
|
common.AddFunction(ContentKeyFunctions.OpenTutorial);
|
||||||
common.AddFunction(ContentKeyFunctions.TakeScreenshot);
|
common.AddFunction(ContentKeyFunctions.TakeScreenshot);
|
||||||
common.AddFunction(ContentKeyFunctions.TakeScreenshotNoUI);
|
common.AddFunction(ContentKeyFunctions.TakeScreenshotNoUI);
|
||||||
|
common.AddFunction(ContentKeyFunctions.Point);
|
||||||
|
|
||||||
var human = contexts.GetContext("human");
|
var human = contexts.GetContext("human");
|
||||||
human.AddFunction(ContentKeyFunctions.SwapHands);
|
human.AddFunction(ContentKeyFunctions.SwapHands);
|
||||||
@@ -37,9 +38,6 @@ namespace Content.Client.Input
|
|||||||
human.AddFunction(ContentKeyFunctions.MouseMiddle);
|
human.AddFunction(ContentKeyFunctions.MouseMiddle);
|
||||||
human.AddFunction(ContentKeyFunctions.ToggleCombatMode);
|
human.AddFunction(ContentKeyFunctions.ToggleCombatMode);
|
||||||
human.AddFunction(ContentKeyFunctions.WideAttack);
|
human.AddFunction(ContentKeyFunctions.WideAttack);
|
||||||
human.AddFunction(ContentKeyFunctions.Point);
|
|
||||||
human.AddFunction(ContentKeyFunctions.TryPullObject);
|
|
||||||
human.AddFunction(ContentKeyFunctions.MovePulledObject);
|
|
||||||
|
|
||||||
var ghost = contexts.New("ghost", "common");
|
var ghost = contexts.New("ghost", "common");
|
||||||
ghost.AddFunction(EngineKeyFunctions.MoveUp);
|
ghost.AddFunction(EngineKeyFunctions.MoveUp);
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
using Robust.Shared.Network;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Content.Client.Interfaces
|
namespace Content.Client.Interfaces
|
||||||
{
|
{
|
||||||
@@ -7,11 +9,15 @@ namespace Content.Client.Interfaces
|
|||||||
bool IsGameStarted { get; }
|
bool IsGameStarted { get; }
|
||||||
string ServerInfoBlob { get; }
|
string ServerInfoBlob { get; }
|
||||||
bool AreWeReady { get; }
|
bool AreWeReady { get; }
|
||||||
|
bool DisallowedLateJoin { get; }
|
||||||
DateTime StartTime { get; }
|
DateTime StartTime { get; }
|
||||||
bool Paused { get; }
|
bool Paused { get; }
|
||||||
|
Dictionary<NetSessionId, bool> Ready { get; }
|
||||||
|
|
||||||
void Initialize();
|
void Initialize();
|
||||||
event Action InfoBlobUpdated;
|
event Action InfoBlobUpdated;
|
||||||
event Action LobbyStatusUpdated;
|
event Action LobbyStatusUpdated;
|
||||||
|
event Action LobbyReadyUpdated;
|
||||||
|
event Action LobbyLateJoinStatusUpdated;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -226,7 +226,10 @@ namespace Content.Client.State
|
|||||||
|
|
||||||
// client side command handlers will always be sent the local player session.
|
// client side command handlers will always be sent the local player session.
|
||||||
var session = PlayerManager.LocalPlayer.Session;
|
var session = PlayerManager.LocalPlayer.Session;
|
||||||
inputSys.HandleInputCommand(session, func, message);
|
if (inputSys.HandleInputCommand(session, func, message))
|
||||||
|
{
|
||||||
|
args.Handle();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,14 +99,20 @@ namespace Content.Client.State
|
|||||||
|
|
||||||
_playerManager.PlayerListUpdated += PlayerManagerOnPlayerListUpdated;
|
_playerManager.PlayerListUpdated += PlayerManagerOnPlayerListUpdated;
|
||||||
_clientGameTicker.InfoBlobUpdated += UpdateLobbyUi;
|
_clientGameTicker.InfoBlobUpdated += UpdateLobbyUi;
|
||||||
_clientGameTicker.LobbyStatusUpdated += UpdateLobbyUi;
|
_clientGameTicker.LobbyStatusUpdated += LobbyStatusUpdated;
|
||||||
|
_clientGameTicker.LobbyReadyUpdated += LobbyReadyUpdated;
|
||||||
|
_clientGameTicker.LobbyLateJoinStatusUpdated += LobbyLateJoinStatusUpdated;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Shutdown()
|
public override void Shutdown()
|
||||||
{
|
{
|
||||||
_playerManager.PlayerListUpdated -= PlayerManagerOnPlayerListUpdated;
|
_playerManager.PlayerListUpdated -= PlayerManagerOnPlayerListUpdated;
|
||||||
_clientGameTicker.InfoBlobUpdated -= UpdateLobbyUi;
|
_clientGameTicker.InfoBlobUpdated -= UpdateLobbyUi;
|
||||||
_clientGameTicker.LobbyStatusUpdated -= UpdateLobbyUi;
|
_clientGameTicker.LobbyStatusUpdated -= LobbyStatusUpdated;
|
||||||
|
_clientGameTicker.LobbyReadyUpdated -= LobbyReadyUpdated;
|
||||||
|
_clientGameTicker.LobbyLateJoinStatusUpdated -= LobbyLateJoinStatusUpdated;
|
||||||
|
|
||||||
|
_clientGameTicker.Ready.Clear();
|
||||||
|
|
||||||
_lobby.Dispose();
|
_lobby.Dispose();
|
||||||
_characterSetup.Dispose();
|
_characterSetup.Dispose();
|
||||||
@@ -149,7 +155,30 @@ namespace Content.Client.State
|
|||||||
_lobby.StartTime.Text = Loc.GetString("Round Starts In: {0}", text);
|
_lobby.StartTime.Text = Loc.GetString("Round Starts In: {0}", text);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PlayerManagerOnPlayerListUpdated(object sender, EventArgs e) => UpdatePlayerList();
|
private void PlayerManagerOnPlayerListUpdated(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
// Remove disconnected sessions from the Ready Dict
|
||||||
|
foreach (var p in _clientGameTicker.Ready)
|
||||||
|
{
|
||||||
|
if (!_playerManager.SessionsDict.TryGetValue(p.Key, out _))
|
||||||
|
{
|
||||||
|
_clientGameTicker.Ready.Remove(p.Key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UpdatePlayerList();
|
||||||
|
}
|
||||||
|
private void LobbyReadyUpdated() => UpdatePlayerList();
|
||||||
|
|
||||||
|
private void LobbyStatusUpdated()
|
||||||
|
{
|
||||||
|
UpdatePlayerList();
|
||||||
|
UpdateLobbyUi();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LobbyLateJoinStatusUpdated()
|
||||||
|
{
|
||||||
|
_lobby.ReadyButton.Disabled = _clientGameTicker.DisallowedLateJoin;
|
||||||
|
}
|
||||||
|
|
||||||
private void UpdateLobbyUi()
|
private void UpdateLobbyUi()
|
||||||
{
|
{
|
||||||
@@ -169,6 +198,7 @@ namespace Content.Client.State
|
|||||||
_lobby.StartTime.Text = "";
|
_lobby.StartTime.Text = "";
|
||||||
_lobby.ReadyButton.Text = Loc.GetString("Ready Up");
|
_lobby.ReadyButton.Text = Loc.GetString("Ready Up");
|
||||||
_lobby.ReadyButton.ToggleMode = true;
|
_lobby.ReadyButton.ToggleMode = true;
|
||||||
|
_lobby.ReadyButton.Disabled = false;
|
||||||
_lobby.ReadyButton.Pressed = _clientGameTicker.AreWeReady;
|
_lobby.ReadyButton.Pressed = _clientGameTicker.AreWeReady;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,10 +208,24 @@ namespace Content.Client.State
|
|||||||
private void UpdatePlayerList()
|
private void UpdatePlayerList()
|
||||||
{
|
{
|
||||||
_lobby.OnlinePlayerItemList.Clear();
|
_lobby.OnlinePlayerItemList.Clear();
|
||||||
|
_lobby.PlayerReadyList.Clear();
|
||||||
|
|
||||||
foreach (var session in _playerManager.Sessions.OrderBy(s => s.Name))
|
foreach (var session in _playerManager.Sessions.OrderBy(s => s.Name))
|
||||||
{
|
{
|
||||||
_lobby.OnlinePlayerItemList.AddItem(session.Name);
|
_lobby.OnlinePlayerItemList.AddItem(session.Name);
|
||||||
|
|
||||||
|
var readyState = "";
|
||||||
|
// Don't show ready state if we're ingame
|
||||||
|
if (!_clientGameTicker.IsGameStarted)
|
||||||
|
{
|
||||||
|
var ready = false;
|
||||||
|
if (session.SessionId == _playerManager.LocalPlayer.SessionId)
|
||||||
|
ready = _clientGameTicker.AreWeReady;
|
||||||
|
else
|
||||||
|
_clientGameTicker.Ready.TryGetValue(session.SessionId, out ready);
|
||||||
|
readyState = ready ? Loc.GetString("Ready") : Loc.GetString("Not Ready");
|
||||||
|
}
|
||||||
|
_lobby.PlayerReadyList.AddItem(readyState, null, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,6 +237,7 @@ namespace Content.Client.State
|
|||||||
}
|
}
|
||||||
|
|
||||||
_console.ProcessCommand($"toggleready {newReady}");
|
_console.ProcessCommand($"toggleready {newReady}");
|
||||||
|
UpdatePlayerList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
13
Content.Client/StationEvents/IStationEventManager.cs
Normal file
13
Content.Client/StationEvents/IStationEventManager.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Content.Client.StationEvents
|
||||||
|
{
|
||||||
|
public interface IStationEventManager
|
||||||
|
{
|
||||||
|
public List<string>? StationEvents { get; }
|
||||||
|
public void Initialize();
|
||||||
|
public event Action OnStationEventsReceived;
|
||||||
|
}
|
||||||
|
}
|
||||||
42
Content.Client/StationEvents/StationEventManager.cs
Normal file
42
Content.Client/StationEvents/StationEventManager.cs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#nullable enable
|
||||||
|
using Content.Shared.StationEvents;
|
||||||
|
using Robust.Shared.Interfaces.Network;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Content.Client.StationEvents
|
||||||
|
{
|
||||||
|
class StationEventManager : SharedStationEvent, IStationEventManager
|
||||||
|
{
|
||||||
|
private List<string>? _events;
|
||||||
|
public List<string>? StationEvents
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_events == null)
|
||||||
|
RequestEvents();
|
||||||
|
return _events;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public event Action? OnStationEventsReceived;
|
||||||
|
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
var netManager = IoCManager.Resolve<IClientNetManager>();
|
||||||
|
netManager.RegisterNetMessage<MsgGetStationEvents>(nameof(MsgGetStationEvents), EventHandler);
|
||||||
|
netManager.Disconnect += (sender, msg) => _events = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EventHandler(MsgGetStationEvents msg)
|
||||||
|
{
|
||||||
|
_events = msg.Events;
|
||||||
|
OnStationEventsReceived?.Invoke();
|
||||||
|
}
|
||||||
|
public void RequestEvents()
|
||||||
|
{
|
||||||
|
var netManager = IoCManager.Resolve<IClientNetManager>();
|
||||||
|
netManager.ClientSendMessage(netManager.CreateNetMessage<MsgGetStationEvents>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,6 +30,7 @@ namespace Content.Client.UserInterface
|
|||||||
|
|
||||||
protected override void Draw(DrawingHandleScreen handle)
|
protected override void Draw(DrawingHandleScreen handle)
|
||||||
{
|
{
|
||||||
|
Span<float> x = stackalloc float[10];
|
||||||
Color color;
|
Color color;
|
||||||
|
|
||||||
var lerp = 1f - MathF.Abs(Progress); // for future bikeshedding purposes
|
var lerp = 1f - MathF.Abs(Progress); // for future bikeshedding purposes
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Content.Client.Chat;
|
using Content.Client.Chat;
|
||||||
using Content.Client.Interfaces;
|
using Content.Client.Interfaces;
|
||||||
using Content.Client.UserInterface.Stylesheets;
|
using Content.Client.UserInterface.Stylesheets;
|
||||||
using Content.Client.Utility;
|
using Content.Client.Utility;
|
||||||
@@ -22,6 +22,7 @@ namespace Content.Client.UserInterface
|
|||||||
public Button LeaveButton { get; }
|
public Button LeaveButton { get; }
|
||||||
public ChatBox Chat { get; }
|
public ChatBox Chat { get; }
|
||||||
public ItemList OnlinePlayerItemList { get; }
|
public ItemList OnlinePlayerItemList { get; }
|
||||||
|
public ItemList PlayerReadyList { get; }
|
||||||
public ServerInfo ServerInfo { get; }
|
public ServerInfo ServerInfo { get; }
|
||||||
public LobbyCharacterPreviewPanel CharacterPreview { get; }
|
public LobbyCharacterPreviewPanel CharacterPreview { get; }
|
||||||
|
|
||||||
@@ -219,7 +220,25 @@ namespace Content.Client.UserInterface
|
|||||||
MarginBottomOverride = 3,
|
MarginBottomOverride = 3,
|
||||||
Children =
|
Children =
|
||||||
{
|
{
|
||||||
(OnlinePlayerItemList = new ItemList())
|
new HBoxContainer
|
||||||
|
{
|
||||||
|
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||||
|
CustomMinimumSize = (50,50),
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
(OnlinePlayerItemList = new ItemList
|
||||||
|
{
|
||||||
|
SizeFlagsVertical = SizeFlags.FillExpand,
|
||||||
|
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||||
|
}),
|
||||||
|
(PlayerReadyList = new ItemList
|
||||||
|
{
|
||||||
|
SizeFlagsVertical = SizeFlags.FillExpand,
|
||||||
|
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||||
|
SizeFlagsStretchRatio = 0.2f
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new NanoHeading
|
new NanoHeading
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Client.Utility;
|
using Content.Client.Utility;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
using Robust.Client.UserInterface.CustomControls;
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
using Robust.Shared.Localization;
|
using Robust.Shared.Localization;
|
||||||
@@ -17,7 +18,7 @@ namespace Content.Client.UserInterface
|
|||||||
private TabContainer RoundEndWindowTabs { get; }
|
private TabContainer RoundEndWindowTabs { get; }
|
||||||
protected override Vector2? CustomSize => (520, 580);
|
protected override Vector2? CustomSize => (520, 580);
|
||||||
|
|
||||||
public RoundEndSummaryWindow(string gm, TimeSpan roundTimeSpan, List<RoundEndPlayerInfo> info )
|
public RoundEndSummaryWindow(string gm, string roundEnd, TimeSpan roundTimeSpan, List<RoundEndPlayerInfo> info)
|
||||||
{
|
{
|
||||||
|
|
||||||
Title = Loc.GetString("Round End Summary");
|
Title = Loc.GetString("Round End Summary");
|
||||||
@@ -49,6 +50,14 @@ namespace Content.Client.UserInterface
|
|||||||
gamemodeLabel.SetMarkup(Loc.GetString("Round of [color=white]{0}[/color] has ended.", gm));
|
gamemodeLabel.SetMarkup(Loc.GetString("Round of [color=white]{0}[/color] has ended.", gm));
|
||||||
RoundEndSummaryTab.AddChild(gamemodeLabel);
|
RoundEndSummaryTab.AddChild(gamemodeLabel);
|
||||||
|
|
||||||
|
//Round end text
|
||||||
|
if (!string.IsNullOrEmpty(roundEnd))
|
||||||
|
{
|
||||||
|
var roundendLabel = new RichTextLabel();
|
||||||
|
roundendLabel.SetMarkup(Loc.GetString(roundEnd));
|
||||||
|
RoundEndSummaryTab.AddChild(roundendLabel);
|
||||||
|
}
|
||||||
|
|
||||||
//Duration
|
//Duration
|
||||||
var roundTimeLabel = new RichTextLabel();
|
var roundTimeLabel = new RichTextLabel();
|
||||||
roundTimeLabel.SetMarkup(Loc.GetString("It lasted for [color=yellow]{0} hours, {1} minutes, and {2} seconds.",
|
roundTimeLabel.SetMarkup(Loc.GetString("It lasted for [color=yellow]{0} hours, {1} minutes, and {2} seconds.",
|
||||||
@@ -65,30 +74,40 @@ namespace Content.Client.UserInterface
|
|||||||
//Create labels for each player info.
|
//Create labels for each player info.
|
||||||
foreach (var plyinfo in manifestSortedList)
|
foreach (var plyinfo in manifestSortedList)
|
||||||
{
|
{
|
||||||
|
|
||||||
var playerInfoText = new RichTextLabel()
|
var playerInfoText = new RichTextLabel()
|
||||||
{
|
{
|
||||||
SizeFlagsVertical = SizeFlags.Fill
|
SizeFlagsVertical = SizeFlags.Fill,
|
||||||
};
|
};
|
||||||
|
|
||||||
//TODO: On Hover display a popup detailing more play info.
|
//TODO: On Hover display a popup detailing more play info.
|
||||||
//For example: their antag goals and if they completed them sucessfully.
|
//For example: their antag goals and if they completed them sucessfully.
|
||||||
var icNameColor = plyinfo.Antag ? "red" : "white";
|
var icNameColor = plyinfo.Antag ? "red" : "white";
|
||||||
playerInfoText.SetMarkup(
|
playerInfoText.SetMarkup(
|
||||||
Loc.GetString($"[color=gray]{plyinfo.PlayerOOCName}[/color] was [color={icNameColor}]{plyinfo.PlayerICName}[/color] playing role of [color=orange]{plyinfo.Role}[/color]."));
|
Loc.GetString("[color=gray]{0}[/color] was [color={1}]{2}[/color] playing role of [color=orange]{3}[/color].",
|
||||||
|
plyinfo.PlayerOOCName, icNameColor, plyinfo.PlayerICName, Loc.GetString(plyinfo.Role)));
|
||||||
innerScrollContainer.AddChild(playerInfoText);
|
innerScrollContainer.AddChild(playerInfoText);
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollContainer.AddChild(innerScrollContainer);
|
scrollContainer.AddChild(innerScrollContainer);
|
||||||
//Attach the entire ScrollContainer that holds all the playerinfo.
|
//Attach the entire ScrollContainer that holds all the playerinfo.
|
||||||
PlayerManifestoTab.AddChild(scrollContainer);
|
PlayerManifestoTab.AddChild(scrollContainer);
|
||||||
|
// TODO: 1240 Overlap, remove once it's fixed. Temp Hack to make the lines not overlap
|
||||||
|
PlayerManifestoTab.OnVisibilityChanged += PlayerManifestoTab_OnVisibilityChanged;
|
||||||
|
|
||||||
//Finally, display the window.
|
//Finally, display the window.
|
||||||
OpenCentered();
|
OpenCentered();
|
||||||
MoveToFront();
|
MoveToFront();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void PlayerManifestoTab_OnVisibilityChanged(Control obj)
|
||||||
|
{
|
||||||
|
if (obj.Visible)
|
||||||
|
{
|
||||||
|
// For some reason the lines get not properly drawn with the right height
|
||||||
|
// so we just force a update
|
||||||
|
ForceRunLayoutUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using Content.Client;
|
|||||||
using Content.Client.Interfaces.Parallax;
|
using Content.Client.Interfaces.Parallax;
|
||||||
using Content.Server;
|
using Content.Server;
|
||||||
using Content.Server.Interfaces.GameTicking;
|
using Content.Server.Interfaces.GameTicking;
|
||||||
|
using NUnit.Framework;
|
||||||
using Robust.Shared.ContentPack;
|
using Robust.Shared.ContentPack;
|
||||||
using Robust.Shared.Interfaces.Network;
|
using Robust.Shared.Interfaces.Network;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
@@ -12,6 +13,7 @@ using EntryPoint = Content.Client.EntryPoint;
|
|||||||
|
|
||||||
namespace Content.IntegrationTests
|
namespace Content.IntegrationTests
|
||||||
{
|
{
|
||||||
|
[Parallelizable(ParallelScope.All)]
|
||||||
public abstract class ContentIntegrationTest : RobustIntegrationTest
|
public abstract class ContentIntegrationTest : RobustIntegrationTest
|
||||||
{
|
{
|
||||||
protected sealed override ClientIntegrationInstance StartClient(ClientIntegrationOptions options = null)
|
protected sealed override ClientIntegrationInstance StartClient(ClientIntegrationOptions options = null)
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ namespace Content.IntegrationTests
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public void EndRound()
|
public void EndRound(string roundEnd)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
160
Content.IntegrationTests/Tests/Atmos/AtmosHelpersTest.cs
Normal file
160
Content.IntegrationTests/Tests/Atmos/AtmosHelpersTest.cs
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Content.Server.Atmos;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
|
||||||
|
namespace Content.IntegrationTests.Tests.Atmos
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
[TestOf(typeof(AtmosHelpersTest))]
|
||||||
|
public class AtmosHelpersTest : ContentIntegrationTest
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public async Task GetTileAtmosphereGridCoordinatesNullTest()
|
||||||
|
{
|
||||||
|
var server = StartServerDummyTicker();
|
||||||
|
|
||||||
|
server.Assert(() =>
|
||||||
|
{
|
||||||
|
Assert.DoesNotThrow(() =>
|
||||||
|
{
|
||||||
|
var atmosphere = default(GridCoordinates).GetTileAtmosphere();
|
||||||
|
|
||||||
|
Assert.Null(atmosphere);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.WaitIdleAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task GetTileAirGridCoordinatesNullTest()
|
||||||
|
{
|
||||||
|
var server = StartServerDummyTicker();
|
||||||
|
|
||||||
|
server.Assert(() =>
|
||||||
|
{
|
||||||
|
Assert.DoesNotThrow(() =>
|
||||||
|
{
|
||||||
|
var air = default(GridCoordinates).GetTileAir();
|
||||||
|
|
||||||
|
Assert.Null(air);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.WaitIdleAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task TryGetTileAtmosphereGridCoordinatesNullTest()
|
||||||
|
{
|
||||||
|
var server = StartServerDummyTicker();
|
||||||
|
|
||||||
|
server.Assert(() =>
|
||||||
|
{
|
||||||
|
Assert.DoesNotThrow(() =>
|
||||||
|
{
|
||||||
|
var hasAtmosphere = default(GridCoordinates).TryGetTileAtmosphere(out var atmosphere);
|
||||||
|
|
||||||
|
Assert.False(hasAtmosphere);
|
||||||
|
Assert.Null(atmosphere);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.WaitIdleAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task TryGetTileTileAirGridCoordinatesNullTest()
|
||||||
|
{
|
||||||
|
var server = StartServerDummyTicker();
|
||||||
|
|
||||||
|
server.Assert(() =>
|
||||||
|
{
|
||||||
|
Assert.DoesNotThrow(() =>
|
||||||
|
{
|
||||||
|
var hasAir = default(GridCoordinates).TryGetTileAir(out var air);
|
||||||
|
|
||||||
|
Assert.False(hasAir);
|
||||||
|
Assert.Null(air);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.WaitIdleAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task GetTileAtmosphereMapIndicesNullTest()
|
||||||
|
{
|
||||||
|
var server = StartServerDummyTicker();
|
||||||
|
|
||||||
|
server.Assert(() =>
|
||||||
|
{
|
||||||
|
Assert.DoesNotThrow(() =>
|
||||||
|
{
|
||||||
|
var atmosphere = default(MapIndices).GetTileAtmosphere(default);
|
||||||
|
|
||||||
|
Assert.Null(atmosphere);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.WaitIdleAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task GetTileAirMapIndicesNullTest()
|
||||||
|
{
|
||||||
|
var server = StartServerDummyTicker();
|
||||||
|
|
||||||
|
server.Assert(() =>
|
||||||
|
{
|
||||||
|
Assert.DoesNotThrow(() =>
|
||||||
|
{
|
||||||
|
var air = default(MapIndices).GetTileAir(default);
|
||||||
|
|
||||||
|
Assert.Null(air);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.WaitIdleAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task TryGetTileAtmosphereMapIndicesNullTest()
|
||||||
|
{
|
||||||
|
var server = StartServerDummyTicker();
|
||||||
|
|
||||||
|
server.Assert(() =>
|
||||||
|
{
|
||||||
|
Assert.DoesNotThrow(() =>
|
||||||
|
{
|
||||||
|
var hasAtmosphere = default(MapIndices).TryGetTileAtmosphere(default, out var atmosphere);
|
||||||
|
|
||||||
|
Assert.False(hasAtmosphere);
|
||||||
|
Assert.Null(atmosphere);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.WaitIdleAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task TryGetTileAirMapIndicesNullTest()
|
||||||
|
{
|
||||||
|
var server = StartServerDummyTicker();
|
||||||
|
|
||||||
|
server.Assert(() =>
|
||||||
|
{
|
||||||
|
Assert.DoesNotThrow(() =>
|
||||||
|
{
|
||||||
|
var hasAir = default(MapIndices).TryGetTileAir(default, out var air);
|
||||||
|
|
||||||
|
Assert.False(hasAir);
|
||||||
|
Assert.Null(air);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.WaitIdleAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -66,7 +66,7 @@ namespace Content.IntegrationTests.Tests.Disposal
|
|||||||
DisposalUnitComponent unit;
|
DisposalUnitComponent unit;
|
||||||
DisposalEntryComponent entry;
|
DisposalEntryComponent entry;
|
||||||
|
|
||||||
server.Assert(() =>
|
server.Assert(async () =>
|
||||||
{
|
{
|
||||||
var mapManager = IoCManager.Resolve<IMapManager>();
|
var mapManager = IoCManager.Resolve<IMapManager>();
|
||||||
|
|
||||||
@@ -81,19 +81,19 @@ namespace Content.IntegrationTests.Tests.Disposal
|
|||||||
var disposalTrunk = entityManager.SpawnEntity("DisposalTrunk", disposalUnit.Transform.MapPosition);
|
var disposalTrunk = entityManager.SpawnEntity("DisposalTrunk", disposalUnit.Transform.MapPosition);
|
||||||
|
|
||||||
// Test for components existing
|
// Test for components existing
|
||||||
Assert.True(disposalUnit.TryGetComponent(out unit));
|
Assert.True(disposalUnit.TryGetComponent(out unit!));
|
||||||
Assert.True(disposalTrunk.TryGetComponent(out entry));
|
Assert.True(disposalTrunk.TryGetComponent(out entry!));
|
||||||
|
|
||||||
// Can't insert, unanchored and unpowered
|
// Can't insert, unanchored and unpowered
|
||||||
var disposalUnitAnchorable = disposalUnit.GetComponent<AnchorableComponent>();
|
var disposalUnitAnchorable = disposalUnit.GetComponent<AnchorableComponent>();
|
||||||
disposalUnitAnchorable.TryUnAnchor(human, null, true);
|
await disposalUnitAnchorable.TryUnAnchor(human, null, true);
|
||||||
Assert.False(unit.Anchored);
|
Assert.False(unit.Anchored);
|
||||||
UnitInsertContains(unit, false, human, wrench, disposalUnit, disposalTrunk);
|
UnitInsertContains(unit, false, human, wrench, disposalUnit, disposalTrunk);
|
||||||
|
|
||||||
// Anchor the disposal unit
|
// Anchor the disposal unit
|
||||||
disposalUnitAnchorable.TryAnchor(human, null, true);
|
await disposalUnitAnchorable.TryAnchor(human, null, true);
|
||||||
Assert.True(disposalUnit.TryGetComponent(out AnchorableComponent anchorableUnit));
|
Assert.True(disposalUnit.TryGetComponent(out AnchorableComponent? anchorableUnit));
|
||||||
Assert.True(anchorableUnit.TryAnchor(human, wrench));
|
Assert.True(await anchorableUnit!.TryAnchor(human, wrench));
|
||||||
Assert.True(unit.Anchored);
|
Assert.True(unit.Anchored);
|
||||||
|
|
||||||
// No power
|
// No power
|
||||||
@@ -118,8 +118,8 @@ namespace Content.IntegrationTests.Tests.Disposal
|
|||||||
Flush(unit, false, entry, human, wrench);
|
Flush(unit, false, entry, human, wrench);
|
||||||
|
|
||||||
// Remove power need
|
// Remove power need
|
||||||
Assert.True(disposalUnit.TryGetComponent(out PowerReceiverComponent power));
|
Assert.True(disposalUnit.TryGetComponent(out PowerReceiverComponent? power));
|
||||||
power.NeedsPower = false;
|
power!.NeedsPower = false;
|
||||||
Assert.True(unit.Powered);
|
Assert.True(unit.Powered);
|
||||||
|
|
||||||
// Flush with a mob and an item
|
// Flush with a mob and an item
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ namespace Content.IntegrationTests.Tests
|
|||||||
public class EntityTest : ContentIntegrationTest
|
public class EntityTest : ContentIntegrationTest
|
||||||
{
|
{
|
||||||
[Test]
|
[Test]
|
||||||
public async Task Test()
|
public async Task SpawnTest()
|
||||||
{
|
{
|
||||||
var server = StartServerDummyTicker();
|
var server = StartServerDummyTicker();
|
||||||
await server.WaitIdleAsync();
|
await server.WaitIdleAsync();
|
||||||
@@ -41,43 +41,65 @@ namespace Content.IntegrationTests.Tests
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.Assert(() =>
|
server.Assert(() =>
|
||||||
|
{
|
||||||
|
var testLocation = new GridCoordinates(new Vector2(0, 0), grid);
|
||||||
|
|
||||||
|
//Generate list of non-abstract prototypes to test
|
||||||
|
foreach (var prototype in prototypeMan.EnumeratePrototypes<EntityPrototype>())
|
||||||
{
|
{
|
||||||
var testLocation = new GridCoordinates(new Vector2(0, 0), grid);
|
if (prototype.Abstract)
|
||||||
|
|
||||||
//Generate list of non-abstract prototypes to test
|
|
||||||
foreach (var prototype in prototypeMan.EnumeratePrototypes<EntityPrototype>())
|
|
||||||
{
|
{
|
||||||
if (prototype.Abstract)
|
continue;
|
||||||
{
|
}
|
||||||
continue;
|
prototypes.Add(prototype);
|
||||||
}
|
}
|
||||||
prototypes.Add(prototype);
|
|
||||||
|
//Iterate list of prototypes to spawn
|
||||||
|
foreach (var prototype in prototypes)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Logger.LogS(LogLevel.Debug, "EntityTest", "Testing: " + prototype.ID);
|
||||||
|
testEntity = entityMan.SpawnEntity(prototype.ID, testLocation);
|
||||||
|
server.RunTicks(2);
|
||||||
|
Assert.That(testEntity.Initialized);
|
||||||
|
entityMan.DeleteEntity(testEntity.Uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Iterate list of prototypes to spawn
|
//Fail any exceptions thrown on spawn
|
||||||
foreach (var prototype in prototypes)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
try
|
Logger.LogS(LogLevel.Error, "EntityTest", "Entity '" + prototype.ID + "' threw: " + e.Message);
|
||||||
{
|
//Assert.Fail();
|
||||||
Logger.LogS(LogLevel.Debug, "EntityTest", "Testing: " + prototype.ID);
|
throw;
|
||||||
testEntity = entityMan.SpawnEntity(prototype.ID, testLocation);
|
|
||||||
server.RunTicks(2);
|
|
||||||
Assert.That(testEntity.Initialized);
|
|
||||||
entityMan.DeleteEntity(testEntity.Uid);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Fail any exceptions thrown on spawn
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Logger.LogS(LogLevel.Error, "EntityTest", "Entity '" + prototype.ID + "' threw: " + e.Message);
|
|
||||||
//Assert.Fail();
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
|
||||||
await server.WaitIdleAsync();
|
await server.WaitIdleAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task NotAbstractIconTest()
|
||||||
|
{
|
||||||
|
var client = StartClient();
|
||||||
|
await client.WaitIdleAsync();
|
||||||
|
var prototypeMan = client.ResolveDependency<IPrototypeManager>();
|
||||||
|
|
||||||
|
client.Assert(() =>
|
||||||
|
{
|
||||||
|
foreach (var prototype in prototypeMan.EnumeratePrototypes<EntityPrototype>())
|
||||||
|
{
|
||||||
|
if (prototype.Abstract)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.That(prototype.Components.ContainsKey("Icon"), $"Entity {prototype.ID} does not have an Icon component, but is not abstract");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await client.WaitIdleAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Map;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Content.Server.GameObjects.Components.Movement;
|
||||||
|
using Content.Shared.Physics;
|
||||||
|
using Robust.Shared.GameObjects.Components;
|
||||||
|
|
||||||
|
namespace Content.IntegrationTests.Tests.GameObjects.Components.Movement
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
[TestOf(typeof(ClimbableComponent))]
|
||||||
|
[TestOf(typeof(ClimbingComponent))]
|
||||||
|
public class ClimbUnitTest : ContentIntegrationTest
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public async Task Test()
|
||||||
|
{
|
||||||
|
var server = StartServerDummyTicker();
|
||||||
|
|
||||||
|
IEntity human;
|
||||||
|
IEntity table;
|
||||||
|
IEntity carpet;
|
||||||
|
ClimbableComponent climbable;
|
||||||
|
ClimbingComponent climbing;
|
||||||
|
|
||||||
|
server.Assert(() =>
|
||||||
|
{
|
||||||
|
var mapManager = IoCManager.Resolve<IMapManager>();
|
||||||
|
mapManager.CreateNewMapEntity(MapId.Nullspace);
|
||||||
|
|
||||||
|
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||||
|
|
||||||
|
// Spawn the entities
|
||||||
|
human = entityManager.SpawnEntity("HumanMob_Content", MapCoordinates.Nullspace);
|
||||||
|
table = entityManager.SpawnEntity("Table", MapCoordinates.Nullspace);
|
||||||
|
|
||||||
|
// Test for climb components existing
|
||||||
|
// Players and tables should have these in their prototypes.
|
||||||
|
Assert.True(human.TryGetComponent(out climbing!), "Human has no climbing");
|
||||||
|
Assert.True(table.TryGetComponent(out climbable!), "Table has no climbable");
|
||||||
|
|
||||||
|
// Now let's make the player enter a climbing transitioning state.
|
||||||
|
climbing.IsClimbing = true;
|
||||||
|
climbing.TryMoveTo(human.Transform.WorldPosition, table.Transform.WorldPosition);
|
||||||
|
var body = human.GetComponent<ICollidableComponent>();
|
||||||
|
|
||||||
|
Assert.True(body.HasController<ClimbController>(), "Player has no ClimbController");
|
||||||
|
|
||||||
|
// Force the player out of climb state. It should immediately remove the ClimbController.
|
||||||
|
climbing.IsClimbing = false;
|
||||||
|
|
||||||
|
Assert.True(!body.HasController<ClimbController>(), "Player wrongly has a ClimbController");
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.WaitIdleAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
150
Content.IntegrationTests/Tests/PowerTest.cs
Normal file
150
Content.IntegrationTests/Tests/PowerTest.cs
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
using Content.Server.GameObjects.Components.Power;
|
||||||
|
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
|
||||||
|
using Content.Server.GameObjects.Components.Power.PowerNetComponents;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Map;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Content.IntegrationTests.Tests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class PowerTest : ContentIntegrationTest
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public async Task PowerNetTest()
|
||||||
|
{
|
||||||
|
var server = StartServerDummyTicker();
|
||||||
|
|
||||||
|
PowerSupplierComponent supplier = null;
|
||||||
|
PowerConsumerComponent consumer1 = null;
|
||||||
|
PowerConsumerComponent consumer2 = null;
|
||||||
|
|
||||||
|
server.Assert(() =>
|
||||||
|
{
|
||||||
|
var mapMan = IoCManager.Resolve<IMapManager>();
|
||||||
|
var entityMan = IoCManager.Resolve<IEntityManager>();
|
||||||
|
mapMan.CreateMap(new MapId(1));
|
||||||
|
var grid = mapMan.CreateGrid(new MapId(1));
|
||||||
|
|
||||||
|
var generatorEnt = entityMan.SpawnEntity("DebugGenerator", new GridCoordinates(new Vector2(0, 0), grid.Index));
|
||||||
|
var consumerEnt1 = entityMan.SpawnEntity("DebugConsumer", new GridCoordinates(new Vector2(0, 1), grid.Index));
|
||||||
|
var consumerEnt2 = entityMan.SpawnEntity("DebugConsumer", new GridCoordinates(new Vector2(0, 2), grid.Index));
|
||||||
|
|
||||||
|
Assert.That(generatorEnt.TryGetComponent(out supplier));
|
||||||
|
Assert.That(consumerEnt1.TryGetComponent(out consumer1));
|
||||||
|
Assert.That(consumerEnt2.TryGetComponent(out consumer2));
|
||||||
|
|
||||||
|
var supplyRate = 1000; //arbitrary amount of power supply
|
||||||
|
|
||||||
|
supplier.SupplyRate = supplyRate;
|
||||||
|
consumer1.DrawRate = supplyRate / 2; //arbitrary draw less than supply
|
||||||
|
consumer2.DrawRate = supplyRate * 2; //arbitrary draw greater than supply
|
||||||
|
|
||||||
|
consumer1.Priority = Priority.First; //power goes to this consumer first
|
||||||
|
consumer2.Priority = Priority.Last; //any excess power should go to low priority consumer
|
||||||
|
});
|
||||||
|
|
||||||
|
server.RunTicks(1); //let run a tick for PowerNet to process power
|
||||||
|
|
||||||
|
server.Assert(() =>
|
||||||
|
{
|
||||||
|
Assert.That(consumer1.DrawRate, Is.EqualTo(consumer1.ReceivedPower)); //first should be fully powered
|
||||||
|
Assert.That(consumer2.ReceivedPower, Is.EqualTo(supplier.SupplyRate - consumer1.ReceivedPower)); //second should get remaining power
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.WaitIdleAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task ApcChargingTest()
|
||||||
|
{
|
||||||
|
var server = StartServerDummyTicker();
|
||||||
|
|
||||||
|
BatteryComponent apcBattery = null;
|
||||||
|
PowerSupplierComponent substationSupplier = null;
|
||||||
|
|
||||||
|
server.Assert(() =>
|
||||||
|
{
|
||||||
|
var mapMan = IoCManager.Resolve<IMapManager>();
|
||||||
|
var entityMan = IoCManager.Resolve<IEntityManager>();
|
||||||
|
mapMan.CreateMap(new MapId(1));
|
||||||
|
var grid = mapMan.CreateGrid(new MapId(1));
|
||||||
|
|
||||||
|
var generatorEnt = entityMan.SpawnEntity("DebugGenerator", new GridCoordinates(new Vector2(0, 0), grid.Index));
|
||||||
|
var substationEnt = entityMan.SpawnEntity("DebugSubstation", new GridCoordinates(new Vector2(0, 1), grid.Index));
|
||||||
|
var apcEnt = entityMan.SpawnEntity("DebugApc", new GridCoordinates(new Vector2(0, 2), grid.Index));
|
||||||
|
|
||||||
|
Assert.That(generatorEnt.TryGetComponent<PowerSupplierComponent>(out var generatorSupplier));
|
||||||
|
|
||||||
|
Assert.That(substationEnt.TryGetComponent(out substationSupplier));
|
||||||
|
Assert.That(substationEnt.TryGetComponent<BatteryStorageComponent>(out var substationStorage));
|
||||||
|
Assert.That(substationEnt.TryGetComponent<BatteryDischargerComponent>(out var substationDischarger));
|
||||||
|
|
||||||
|
Assert.That(apcEnt.TryGetComponent(out apcBattery));
|
||||||
|
Assert.That(apcEnt.TryGetComponent<BatteryStorageComponent>(out var apcStorage));
|
||||||
|
|
||||||
|
generatorSupplier.SupplyRate = 1000; //arbitrary nonzero amount of power
|
||||||
|
substationStorage.ActiveDrawRate = 1000; //arbitrary nonzero power draw
|
||||||
|
substationDischarger.ActiveSupplyRate = 500; //arbitirary nonzero power supply less than substation storage draw
|
||||||
|
apcStorage.ActiveDrawRate = 500; //arbitrary nonzero power draw
|
||||||
|
apcBattery.MaxCharge = 100; //abbitrary nonzero amount of charge
|
||||||
|
apcBattery.CurrentCharge = 0; //no charge
|
||||||
|
});
|
||||||
|
|
||||||
|
server.RunTicks(5); //let run a few ticks for PowerNets to reevaluate and start charging apc
|
||||||
|
|
||||||
|
server.Assert(() =>
|
||||||
|
{
|
||||||
|
Assert.That(substationSupplier.SupplyRate, Is.Not.EqualTo(0)); //substation should be providing power
|
||||||
|
Assert.That(apcBattery.CurrentCharge, Is.Not.EqualTo(0)); //apc battery should have gained charge
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.WaitIdleAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task ApcNetTest()
|
||||||
|
{
|
||||||
|
var server = StartServerDummyTicker();
|
||||||
|
|
||||||
|
PowerReceiverComponent receiver = null;
|
||||||
|
|
||||||
|
server.Assert(() =>
|
||||||
|
{
|
||||||
|
var mapMan = IoCManager.Resolve<IMapManager>();
|
||||||
|
var entityMan = IoCManager.Resolve<IEntityManager>();
|
||||||
|
mapMan.CreateMap(new MapId(1));
|
||||||
|
var grid = mapMan.CreateGrid(new MapId(1));
|
||||||
|
|
||||||
|
var apcEnt = entityMan.SpawnEntity("DebugApc", new GridCoordinates(new Vector2(0, 0), grid.Index));
|
||||||
|
var apcExtensionEnt = entityMan.SpawnEntity("ApcExtensionCable", new GridCoordinates(new Vector2(0, 1), grid.Index));
|
||||||
|
var powerReceiverEnt = entityMan.SpawnEntity("DebugPowerReceiver", new GridCoordinates(new Vector2(0, 2), grid.Index));
|
||||||
|
|
||||||
|
Assert.That(apcEnt.TryGetComponent<ApcComponent>(out var apc));
|
||||||
|
Assert.That(apcExtensionEnt.TryGetComponent<PowerProviderComponent>(out var provider));
|
||||||
|
Assert.That(powerReceiverEnt.TryGetComponent(out receiver));
|
||||||
|
|
||||||
|
provider.PowerTransferRange = 5; //arbitrary range to reach receiver
|
||||||
|
receiver.PowerReceptionRange = 5; //arbitrary range to reach provider
|
||||||
|
|
||||||
|
apc.Battery.MaxCharge = 10000; //arbitrary nonzero amount of charge
|
||||||
|
apc.Battery.CurrentCharge = apc.Battery.MaxCharge; //fill battery
|
||||||
|
|
||||||
|
receiver.Load = 1; //arbitrary small amount of power
|
||||||
|
});
|
||||||
|
|
||||||
|
server.RunTicks(1); //let run a tick for ApcNet to process power
|
||||||
|
|
||||||
|
server.Assert(() =>
|
||||||
|
{
|
||||||
|
Assert.That(receiver.Powered);
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.WaitIdleAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,8 +3,11 @@ using NUnit.Framework;
|
|||||||
using Robust.Server.Interfaces.Maps;
|
using Robust.Server.Interfaces.Maps;
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
using Robust.Shared.Interfaces.Map;
|
using Robust.Shared.Interfaces.Map;
|
||||||
|
using Robust.Shared.Interfaces.Resources;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Maths;
|
using Robust.Shared.Maths;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.IntegrationTests.Tests
|
namespace Content.IntegrationTests.Tests
|
||||||
{
|
{
|
||||||
@@ -14,7 +17,7 @@ namespace Content.IntegrationTests.Tests
|
|||||||
[Test]
|
[Test]
|
||||||
public async Task SaveLoadMultiGridMap()
|
public async Task SaveLoadMultiGridMap()
|
||||||
{
|
{
|
||||||
const string mapPath = @"Maps/Test/TestMap.yml";
|
const string mapPath = @"/Maps/Test/TestMap.yml";
|
||||||
|
|
||||||
var server = StartServer();
|
var server = StartServer();
|
||||||
await server.WaitIdleAsync();
|
await server.WaitIdleAsync();
|
||||||
@@ -24,6 +27,10 @@ namespace Content.IntegrationTests.Tests
|
|||||||
|
|
||||||
server.Post(() =>
|
server.Post(() =>
|
||||||
{
|
{
|
||||||
|
var dir = new ResourcePath(mapPath).Directory;
|
||||||
|
IoCManager.Resolve<IResourceManager>()
|
||||||
|
.UserData.CreateDir(dir);
|
||||||
|
|
||||||
var mapId = mapManager.CreateMap(new MapId(5));
|
var mapId = mapManager.CreateMap(new MapId(5));
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -38,14 +38,14 @@ namespace Content.IntegrationTests.Tests
|
|||||||
string one;
|
string one;
|
||||||
string two;
|
string two;
|
||||||
|
|
||||||
var rp1 = new ResourcePath("save load save 1.yml");
|
var rp1 = new ResourcePath("/save load save 1.yml");
|
||||||
using (var stream = userData.Open(rp1, FileMode.Open))
|
using (var stream = userData.Open(rp1, FileMode.Open))
|
||||||
using (var reader = new StreamReader(stream))
|
using (var reader = new StreamReader(stream))
|
||||||
{
|
{
|
||||||
one = reader.ReadToEnd();
|
one = reader.ReadToEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
var rp2 = new ResourcePath("save load save 2.yml");
|
var rp2 = new ResourcePath("/save load save 2.yml");
|
||||||
using (var stream = userData.Open(rp2, FileMode.Open))
|
using (var stream = userData.Open(rp2, FileMode.Open))
|
||||||
using (var reader = new StreamReader(stream))
|
using (var reader = new StreamReader(stream))
|
||||||
{
|
{
|
||||||
@@ -96,7 +96,7 @@ namespace Content.IntegrationTests.Tests
|
|||||||
|
|
||||||
server.Post(() =>
|
server.Post(() =>
|
||||||
{
|
{
|
||||||
mapLoader.SaveBlueprint(grid.Index, "load save ticks save 2.yml");
|
mapLoader.SaveBlueprint(grid.Index, "/load save ticks save 2.yml");
|
||||||
});
|
});
|
||||||
|
|
||||||
await server.WaitIdleAsync();
|
await server.WaitIdleAsync();
|
||||||
@@ -105,13 +105,13 @@ namespace Content.IntegrationTests.Tests
|
|||||||
string one;
|
string one;
|
||||||
string two;
|
string two;
|
||||||
|
|
||||||
using (var stream = userData.Open(new ResourcePath("load save ticks save 1.yml"), FileMode.Open))
|
using (var stream = userData.Open(new ResourcePath("/load save ticks save 1.yml"), FileMode.Open))
|
||||||
using (var reader = new StreamReader(stream))
|
using (var reader = new StreamReader(stream))
|
||||||
{
|
{
|
||||||
one = reader.ReadToEnd();
|
one = reader.ReadToEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var stream = userData.Open(new ResourcePath("load save ticks save 2.yml"), FileMode.Open))
|
using (var stream = userData.Open(new ResourcePath("/load save ticks save 2.yml"), FileMode.Open))
|
||||||
using (var reader = new StreamReader(stream))
|
using (var reader = new StreamReader(stream))
|
||||||
{
|
{
|
||||||
two = reader.ReadToEnd();
|
two = reader.ReadToEnd();
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.Data.Sqlite;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Npgsql;
|
using Npgsql;
|
||||||
|
|
||||||
namespace Content.Server.Database
|
namespace Content.Server.Database
|
||||||
@@ -50,9 +51,10 @@ namespace Content.Server.Database
|
|||||||
|
|
||||||
public class SqliteConfiguration : IDatabaseConfiguration
|
public class SqliteConfiguration : IDatabaseConfiguration
|
||||||
{
|
{
|
||||||
private readonly string _databaseFilePath;
|
private readonly string? _databaseFilePath;
|
||||||
|
|
||||||
public SqliteConfiguration(string databaseFilePath)
|
/// <param name="databaseFilePath">If null, an in-memory database is used.</param>
|
||||||
|
public SqliteConfiguration(string? databaseFilePath)
|
||||||
{
|
{
|
||||||
_databaseFilePath = databaseFilePath;
|
_databaseFilePath = databaseFilePath;
|
||||||
}
|
}
|
||||||
@@ -62,7 +64,20 @@ namespace Content.Server.Database
|
|||||||
get
|
get
|
||||||
{
|
{
|
||||||
var optionsBuilder = new DbContextOptionsBuilder<PreferencesDbContext>();
|
var optionsBuilder = new DbContextOptionsBuilder<PreferencesDbContext>();
|
||||||
optionsBuilder.UseSqlite($"Data Source={_databaseFilePath}");
|
SqliteConnection connection;
|
||||||
|
if (_databaseFilePath != null)
|
||||||
|
{
|
||||||
|
connection = new SqliteConnection($"Data Source={_databaseFilePath}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
connection = new SqliteConnection("Data Source=:memory:");
|
||||||
|
// When using an in-memory DB we have to open it manually
|
||||||
|
// so EFCore doesn't open, close and wipe it.
|
||||||
|
connection.Open();
|
||||||
|
}
|
||||||
|
|
||||||
|
optionsBuilder.UseSqlite(connection);
|
||||||
return optionsBuilder.Options;
|
return optionsBuilder.Options;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,19 +112,14 @@ namespace Content.Server.AI.Utility.Actions
|
|||||||
UpdateBlackboard(context);
|
UpdateBlackboard(context);
|
||||||
var considerations = GetConsiderations(context);
|
var considerations = GetConsiderations(context);
|
||||||
DebugTools.Assert(considerations.Count > 0);
|
DebugTools.Assert(considerations.Count > 0);
|
||||||
// I used the IAUS video although I did have some confusion on how to structure it overall
|
|
||||||
// as some of the slides seemed contradictory
|
|
||||||
|
|
||||||
// Ideally we should early-out each action as cheaply as possible if it's not valid
|
// Overall structure is based on Building a better centaur
|
||||||
|
// Ideally we should early-out each action as cheaply as possible if it's not valid, thus
|
||||||
// We also need some way to tell if the action isn't going to
|
// the finalScore can only go down over time.
|
||||||
// have a better score than the current action (if applicable) and early-out that way as well.
|
|
||||||
|
|
||||||
// 23:00 Building a better centaur
|
|
||||||
var finalScore = 1.0f;
|
var finalScore = 1.0f;
|
||||||
var minThreshold = min / Bonus;
|
var minThreshold = min / Bonus;
|
||||||
context.GetState<ConsiderationState>().SetValue(considerations.Count);
|
context.GetState<ConsiderationState>().SetValue(considerations.Count);
|
||||||
// See 10:09 for this and the adjustments
|
|
||||||
|
|
||||||
foreach (var consideration in considerations)
|
foreach (var consideration in considerations)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Content.Server.AI.Operators;
|
using Content.Server.AI.Operators;
|
||||||
@@ -6,11 +6,13 @@ using Content.Server.AI.Utility.Actions;
|
|||||||
using Content.Server.AI.Utility.BehaviorSets;
|
using Content.Server.AI.Utility.BehaviorSets;
|
||||||
using Content.Server.AI.WorldState;
|
using Content.Server.AI.WorldState;
|
||||||
using Content.Server.AI.WorldState.States.Utility;
|
using Content.Server.AI.WorldState.States.Utility;
|
||||||
using Content.Server.GameObjects.Components.Damage;
|
using Content.Server.GameObjects.EntitySystems.AI;
|
||||||
using Content.Server.GameObjects.Components.Mobs;
|
|
||||||
using Content.Server.GameObjects.EntitySystems.AI.LoadBalancer;
|
using Content.Server.GameObjects.EntitySystems.AI.LoadBalancer;
|
||||||
using Content.Server.GameObjects.EntitySystems.JobQueues;
|
using Content.Server.GameObjects.EntitySystems.JobQueues;
|
||||||
|
using Content.Shared.GameObjects.Components.Damage;
|
||||||
using Robust.Server.AI;
|
using Robust.Server.AI;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects.Systems;
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Log;
|
using Robust.Shared.Log;
|
||||||
@@ -63,6 +65,13 @@ namespace Content.Server.AI.Utility.AiLogic
|
|||||||
{
|
{
|
||||||
SortActions();
|
SortActions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (BehaviorSets.Count == 1 && !EntitySystem.Get<AiSystem>().IsAwake(this))
|
||||||
|
{
|
||||||
|
IoCManager.Resolve<IEntityManager>()
|
||||||
|
.EventBus
|
||||||
|
.RaiseEvent(EventSource.Local, new SleepAiMessage(this, false));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveBehaviorSet(Type behaviorSet)
|
public void RemoveBehaviorSet(Type behaviorSet)
|
||||||
@@ -74,6 +83,13 @@ namespace Content.Server.AI.Utility.AiLogic
|
|||||||
BehaviorSets.Remove(behaviorSet);
|
BehaviorSets.Remove(behaviorSet);
|
||||||
SortActions();
|
SortActions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (BehaviorSets.Count == 0)
|
||||||
|
{
|
||||||
|
IoCManager.Resolve<IEntityManager>()
|
||||||
|
.EventBus
|
||||||
|
.RaiseEvent(EventSource.Local, new SleepAiMessage(this, true));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -114,38 +130,42 @@ namespace Content.Server.AI.Utility.AiLogic
|
|||||||
_planCooldownRemaining = PlanCooldown;
|
_planCooldownRemaining = PlanCooldown;
|
||||||
_blackboard = new Blackboard(SelfEntity);
|
_blackboard = new Blackboard(SelfEntity);
|
||||||
_planner = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<AiActionSystem>();
|
_planner = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<AiActionSystem>();
|
||||||
if (SelfEntity.TryGetComponent(out DamageableComponent damageableComponent))
|
if (SelfEntity.TryGetComponent(out IDamageableComponent damageableComponent))
|
||||||
{
|
{
|
||||||
damageableComponent.DamageThresholdPassed += DamageThresholdHandle;
|
damageableComponent.HealthChangedEvent += DeathHandle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Shutdown()
|
public override void Shutdown()
|
||||||
{
|
{
|
||||||
// TODO: If DamageableComponent removed still need to unsubscribe?
|
// TODO: If DamageableComponent removed still need to unsubscribe?
|
||||||
if (SelfEntity.TryGetComponent(out DamageableComponent damageableComponent))
|
if (SelfEntity.TryGetComponent(out IDamageableComponent damageableComponent))
|
||||||
{
|
{
|
||||||
damageableComponent.DamageThresholdPassed -= DamageThresholdHandle;
|
damageableComponent.HealthChangedEvent -= DeathHandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentOp = CurrentAction?.ActionOperators.Peek();
|
var currentOp = CurrentAction?.ActionOperators.Peek();
|
||||||
currentOp?.Shutdown(Outcome.Failed);
|
currentOp?.Shutdown(Outcome.Failed);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DamageThresholdHandle(object sender, DamageThresholdPassedEventArgs eventArgs)
|
private void DeathHandle(HealthChangedEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
if (!SelfEntity.TryGetComponent(out SpeciesComponent speciesComponent))
|
var oldDeadState = _isDead;
|
||||||
{
|
_isDead = eventArgs.Damageable.CurrentDamageState == DamageState.Dead || eventArgs.Damageable.CurrentDamageState == DamageState.Critical;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (speciesComponent.CurrentDamageState is DeadState)
|
if (oldDeadState != _isDead)
|
||||||
{
|
{
|
||||||
_isDead = true;
|
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||||
}
|
|
||||||
else
|
switch (_isDead)
|
||||||
{
|
{
|
||||||
_isDead = false;
|
case true:
|
||||||
|
entityManager.EventBus.RaiseEvent(EventSource.Local, new SleepAiMessage(this, true));
|
||||||
|
break;
|
||||||
|
case false:
|
||||||
|
entityManager.EventBus.RaiseEvent(EventSource.Local, new SleepAiMessage(this, false));
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,16 +200,6 @@ namespace Content.Server.AI.Utility.AiLogic
|
|||||||
|
|
||||||
public override void Update(float frameTime)
|
public override void Update(float frameTime)
|
||||||
{
|
{
|
||||||
// If we can't do anything then there's no point thinking
|
|
||||||
if (_isDead || BehaviorSets.Count == 0)
|
|
||||||
{
|
|
||||||
_actionCancellation?.Cancel();
|
|
||||||
_blackboard.GetState<LastUtilityScoreState>().SetValue(0.0f);
|
|
||||||
CurrentAction?.Shutdown();
|
|
||||||
CurrentAction = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we asked for a new action we don't want to dump the existing one.
|
// If we asked for a new action we don't want to dump the existing one.
|
||||||
if (_actionRequest != null)
|
if (_actionRequest != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Content.Server.AI.WorldState;
|
using Content.Server.AI.WorldState;
|
||||||
using Content.Server.AI.WorldState.States;
|
using Content.Server.AI.WorldState.States;
|
||||||
using Content.Server.GameObjects.Components.Damage;
|
using Content.Server.GameObjects.Components.Damage;
|
||||||
using Content.Shared.GameObjects.Components.Damage;
|
using Content.Shared.GameObjects.Components.Damage;
|
||||||
@@ -11,13 +11,12 @@ namespace Content.Server.AI.Utility.Considerations.Combat
|
|||||||
{
|
{
|
||||||
var target = context.GetState<TargetEntityState>().GetValue();
|
var target = context.GetState<TargetEntityState>().GetValue();
|
||||||
|
|
||||||
if (target == null || !target.TryGetComponent(out DamageableComponent damageableComponent))
|
if (target == null || !target.TryGetComponent(out IDamageableComponent damageableComponent))
|
||||||
{
|
{
|
||||||
return 0.0f;
|
return 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Just went with max health
|
return damageableComponent.TotalDamage / 300.0f;
|
||||||
return damageableComponent.CurrentDamage[DamageType.Total] / 300.0f;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using Content.Server.AI.WorldState;
|
using Content.Server.AI.WorldState;
|
||||||
using Content.Server.AI.WorldState.States;
|
using Content.Server.AI.WorldState.States;
|
||||||
using Content.Server.GameObjects.Components.Mobs;
|
using Content.Shared.GameObjects.Components.Damage;
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.Considerations.Combat
|
namespace Content.Server.AI.Utility.Considerations.Combat
|
||||||
{
|
{
|
||||||
@@ -10,12 +10,12 @@ namespace Content.Server.AI.Utility.Considerations.Combat
|
|||||||
{
|
{
|
||||||
var target = context.GetState<TargetEntityState>().GetValue();
|
var target = context.GetState<TargetEntityState>().GetValue();
|
||||||
|
|
||||||
if (target == null || !target.TryGetComponent(out SpeciesComponent speciesComponent))
|
if (target == null || !target.TryGetComponent(out IDamageableComponent damageableComponent))
|
||||||
{
|
{
|
||||||
return 0.0f;
|
return 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (speciesComponent.CurrentDamageState is CriticalState)
|
if (damageableComponent.CurrentDamageState == DamageState.Critical)
|
||||||
{
|
{
|
||||||
return 1.0f;
|
return 1.0f;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using Content.Server.AI.WorldState;
|
using Content.Server.AI.WorldState;
|
||||||
using Content.Server.AI.WorldState.States;
|
using Content.Server.AI.WorldState.States;
|
||||||
using Content.Server.GameObjects.Components.Mobs;
|
using Content.Shared.GameObjects.Components.Damage;
|
||||||
|
|
||||||
namespace Content.Server.AI.Utility.Considerations.Combat
|
namespace Content.Server.AI.Utility.Considerations.Combat
|
||||||
{
|
{
|
||||||
@@ -10,12 +10,12 @@ namespace Content.Server.AI.Utility.Considerations.Combat
|
|||||||
{
|
{
|
||||||
var target = context.GetState<TargetEntityState>().GetValue();
|
var target = context.GetState<TargetEntityState>().GetValue();
|
||||||
|
|
||||||
if (target == null || !target.TryGetComponent(out SpeciesComponent speciesComponent))
|
if (target == null || !target.TryGetComponent(out IDamageableComponent damageableComponent))
|
||||||
{
|
{
|
||||||
return 0.0f;
|
return 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (speciesComponent.CurrentDamageState is DeadState)
|
if (damageableComponent.CurrentDamageState == DamageState.Dead)
|
||||||
{
|
{
|
||||||
return 1.0f;
|
return 1.0f;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,18 +13,27 @@ namespace Content.Server.AI.Utility.Considerations
|
|||||||
private float GetAdjustedScore(Blackboard context)
|
private float GetAdjustedScore(Blackboard context)
|
||||||
{
|
{
|
||||||
var score = GetScore(context);
|
var score = GetScore(context);
|
||||||
|
/*
|
||||||
|
* Now using the geometric mean
|
||||||
|
* for n scores you take the n-th root of the scores multiplied
|
||||||
|
* e.g. a, b, c scores you take Math.Pow(a * b * c, 1/3)
|
||||||
|
* To get the ACTUAL geometric mean at any one stage you'd need to divide by the running consideration count
|
||||||
|
* however, the downside to this is it will fluctuate up and down over time.
|
||||||
|
* For our purposes if we go below the minimum threshold we want to cut it off, thus we take a
|
||||||
|
* "running geometric mean" which can only ever go down (and by the final value will equal the actual geometric mean).
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Previously we used a makeupvalue method although the geometric mean is less punishing for more considerations
|
||||||
var considerationsCount = context.GetState<ConsiderationState>().GetValue();
|
var considerationsCount = context.GetState<ConsiderationState>().GetValue();
|
||||||
var modificationFactor = 1.0f - 1.0f / considerationsCount;
|
var adjustedScore = MathF.Pow(score, 1 / (float) considerationsCount);
|
||||||
var makeUpValue = (1.0f - score) * modificationFactor;
|
return FloatMath.Clamp(adjustedScore, 0.0f, 1.0f);
|
||||||
var adjustedScore = score + makeUpValue * score;
|
|
||||||
return MathHelper.Clamp(adjustedScore, 0.0f, 1.0f);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Pure]
|
[Pure]
|
||||||
private static float BoolCurve(float x)
|
private static float BoolCurve(float x)
|
||||||
{
|
{
|
||||||
// ReSharper disable once CompareOfFloatsByEqualityOperator
|
// ReSharper disable once CompareOfFloatsByEqualityOperator
|
||||||
return x == 1.0f ? 1.0f : 0.0f;
|
return x > 0.0f ? 1.0f : 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Func<float> BoolCurve(Blackboard context)
|
public Func<float> BoolCurve(Blackboard context)
|
||||||
@@ -42,7 +51,7 @@ namespace Content.Server.AI.Utility.Considerations
|
|||||||
private static float InverseBoolCurve(float x)
|
private static float InverseBoolCurve(float x)
|
||||||
{
|
{
|
||||||
// ReSharper disable once CompareOfFloatsByEqualityOperator
|
// ReSharper disable once CompareOfFloatsByEqualityOperator
|
||||||
return x == 1.0f ? 0.0f : 1.0f;
|
return x == 0.0f ? 1.0f : 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Func<float> InverseBoolCurve(Blackboard context)
|
public Func<float> InverseBoolCurve(Blackboard context)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Content.Server.AI.Utility.Actions;
|
using Content.Server.AI.Utility.Actions;
|
||||||
using Content.Server.AI.Utility.Actions.Combat.Melee;
|
using Content.Server.AI.Utility.Actions.Combat.Melee;
|
||||||
@@ -7,8 +7,8 @@ using Content.Server.AI.Utility.Considerations.Combat.Melee;
|
|||||||
using Content.Server.AI.Utils;
|
using Content.Server.AI.Utils;
|
||||||
using Content.Server.AI.WorldState;
|
using Content.Server.AI.WorldState;
|
||||||
using Content.Server.AI.WorldState.States;
|
using Content.Server.AI.WorldState.States;
|
||||||
using Content.Server.GameObjects.Components.Mobs;
|
|
||||||
using Content.Server.GameObjects.Components.Movement;
|
using Content.Server.GameObjects.Components.Movement;
|
||||||
|
using Content.Shared.GameObjects.Components.Damage;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ namespace Content.Server.AI.Utility.ExpandableActions.Combat.Melee
|
|||||||
throw new InvalidOperationException();
|
throw new InvalidOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var entity in Visibility.GetEntitiesInRange(owner.Transform.GridPosition, typeof(SpeciesComponent),
|
foreach (var entity in Visibility.GetEntitiesInRange(owner.Transform.GridPosition, typeof(IDamageableComponent),
|
||||||
controller.VisionRadius))
|
controller.VisionRadius))
|
||||||
{
|
{
|
||||||
if (entity.HasComponent<BasicActorComponent>() && entity != owner)
|
if (entity.HasComponent<BasicActorComponent>() && entity != owner)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace Content.Server.AI.Utility.ExpandableActions.Combat.Melee
|
|||||||
{
|
{
|
||||||
var owner = context.GetState<SelfState>().GetValue();
|
var owner = context.GetState<SelfState>().GetValue();
|
||||||
|
|
||||||
foreach (var entity in context.GetState<NearbySpeciesState>().GetValue())
|
foreach (var entity in context.GetState<NearbyBodiesState>().GetValue())
|
||||||
{
|
{
|
||||||
yield return new MeleeWeaponAttackEntity(owner, entity, Bonus);
|
yield return new MeleeWeaponAttackEntity(owner, entity, Bonus);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Content.Server.AI.Utility.Actions;
|
using Content.Server.AI.Utility.Actions;
|
||||||
using Content.Server.AI.Utility.Actions.Combat.Melee;
|
using Content.Server.AI.Utility.Actions.Combat.Melee;
|
||||||
@@ -7,8 +7,8 @@ using Content.Server.AI.Utility.Considerations.Combat.Melee;
|
|||||||
using Content.Server.AI.Utils;
|
using Content.Server.AI.Utils;
|
||||||
using Content.Server.AI.WorldState;
|
using Content.Server.AI.WorldState;
|
||||||
using Content.Server.AI.WorldState.States;
|
using Content.Server.AI.WorldState.States;
|
||||||
using Content.Server.GameObjects.Components.Mobs;
|
|
||||||
using Content.Server.GameObjects.Components.Movement;
|
using Content.Server.GameObjects.Components.Movement;
|
||||||
|
using Content.Shared.GameObjects.Components.Body;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ namespace Content.Server.AI.Utility.ExpandableActions.Combat.Melee
|
|||||||
throw new InvalidOperationException();
|
throw new InvalidOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var entity in Visibility.GetEntitiesInRange(owner.Transform.GridPosition, typeof(SpeciesComponent),
|
foreach (var entity in Visibility.GetEntitiesInRange(owner.Transform.GridPosition, typeof(IBodyManagerComponent),
|
||||||
controller.VisionRadius))
|
controller.VisionRadius))
|
||||||
{
|
{
|
||||||
if (entity.HasComponent<BasicActorComponent>() && entity != owner)
|
if (entity.HasComponent<BasicActorComponent>() && entity != owner)
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ namespace Content.Server.AI.WorldState.States.Inventory
|
|||||||
{
|
{
|
||||||
foreach (var item in handsComponent.GetAllHeldItems())
|
foreach (var item in handsComponent.GetAllHeldItems())
|
||||||
{
|
{
|
||||||
|
if (item.Owner.Deleted)
|
||||||
|
continue;
|
||||||
|
|
||||||
yield return item.Owner;
|
yield return item.Owner;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Content.Server.AI.Utils;
|
using Content.Server.AI.Utils;
|
||||||
using Content.Server.GameObjects.Components.Mobs;
|
|
||||||
using Content.Server.GameObjects.Components.Movement;
|
using Content.Server.GameObjects.Components.Movement;
|
||||||
|
using Content.Shared.GameObjects.Components.Body;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
namespace Content.Server.AI.WorldState.States.Mobs
|
namespace Content.Server.AI.WorldState.States.Mobs
|
||||||
{
|
{
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public sealed class NearbySpeciesState : CachedStateData<List<IEntity>>
|
public sealed class NearbyBodiesState : CachedStateData<List<IEntity>>
|
||||||
{
|
{
|
||||||
public override string Name => "NearbySpecies";
|
public override string Name => "NearbyBodies";
|
||||||
|
|
||||||
protected override List<IEntity> GetTrueValue()
|
protected override List<IEntity> GetTrueValue()
|
||||||
{
|
{
|
||||||
@@ -21,7 +21,7 @@ namespace Content.Server.AI.WorldState.States.Mobs
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var entity in Visibility.GetEntitiesInRange(Owner.Transform.GridPosition, typeof(SpeciesComponent), controller.VisionRadius))
|
foreach (var entity in Visibility.GetEntitiesInRange(Owner.Transform.GridPosition, typeof(IBodyManagerComponent), controller.VisionRadius))
|
||||||
{
|
{
|
||||||
if (entity == Owner) continue;
|
if (entity == Owner) continue;
|
||||||
result.Add(entity);
|
result.Add(entity);
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Content.Server.GameObjects.Components.Mobs;
|
|
||||||
using Content.Server.GameObjects.Components.Movement;
|
using Content.Server.GameObjects.Components.Movement;
|
||||||
|
using Content.Shared.GameObjects.Components.Damage;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Server.Interfaces.Player;
|
using Robust.Server.Interfaces.Player;
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
@@ -27,7 +27,7 @@ namespace Content.Server.AI.WorldState.States.Mobs
|
|||||||
|
|
||||||
foreach (var player in nearbyPlayers)
|
foreach (var player in nearbyPlayers)
|
||||||
{
|
{
|
||||||
if (player.AttachedEntity != Owner && player.AttachedEntity.HasComponent<SpeciesComponent>())
|
if (player.AttachedEntity != Owner && player.AttachedEntity.HasComponent<IDamageableComponent>())
|
||||||
{
|
{
|
||||||
result.Add(player.AttachedEntity);
|
result.Add(player.AttachedEntity);
|
||||||
}
|
}
|
||||||
|
|||||||
40
Content.Server/Administration/ReadyAll.cs
Normal file
40
Content.Server/Administration/ReadyAll.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#nullable enable
|
||||||
|
using Content.Server.GameTicking;
|
||||||
|
using Content.Server.Interfaces.GameTicking;
|
||||||
|
using Robust.Server.Interfaces.Console;
|
||||||
|
using Robust.Server.Interfaces.Player;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
|
||||||
|
namespace Content.Server.Administration
|
||||||
|
{
|
||||||
|
public class ReadyAll : IClientCommand
|
||||||
|
{
|
||||||
|
public string Command => "readyall";
|
||||||
|
public string Description => "Readies up all players in the lobby.";
|
||||||
|
public string Help => $"{Command} | ̣{Command} <ready>";
|
||||||
|
public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args)
|
||||||
|
{
|
||||||
|
var ready = true;
|
||||||
|
|
||||||
|
if (args.Length > 0)
|
||||||
|
{
|
||||||
|
ready = bool.Parse(args[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var gameTicker = IoCManager.Resolve<IGameTicker>();
|
||||||
|
var playerManager = IoCManager.Resolve<IPlayerManager>();
|
||||||
|
|
||||||
|
|
||||||
|
if (gameTicker.RunLevel != GameRunLevel.PreRoundLobby)
|
||||||
|
{
|
||||||
|
shell.SendText(player, "This command can only be ran while in the lobby!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var p in playerManager.GetAllPlayers())
|
||||||
|
{
|
||||||
|
gameTicker.ToggleReady(p, ready);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -138,7 +138,7 @@ namespace Content.Server.Atmos
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class FillGas : IClientCommand
|
public class FillGas : IClientCommand
|
||||||
{
|
{
|
||||||
public string Command => "fillgas";
|
public string Command => "fillgas";
|
||||||
public string Description => "Adds gas to all tiles in a grid.";
|
public string Description => "Adds gas to all tiles in a grid.";
|
||||||
|
|||||||
@@ -20,14 +20,16 @@ namespace Content.Server.Atmos
|
|||||||
return coordinates.GetTileAtmosphere()?.Air;
|
return coordinates.GetTileAtmosphere()?.Air;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool TryGetTileAtmosphere(this GridCoordinates coordinates, [NotNullWhen(true)] out TileAtmosphere atmosphere)
|
public static bool TryGetTileAtmosphere(this GridCoordinates coordinates, [MaybeNullWhen(false)] out TileAtmosphere atmosphere)
|
||||||
{
|
{
|
||||||
return (atmosphere = coordinates.GetTileAtmosphere()!) != default;
|
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
|
||||||
|
return !Equals(atmosphere = coordinates.GetTileAtmosphere()!, default);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool TryGetTileAir(this GridCoordinates coordinates, [NotNullWhen(true)] out GasMixture air)
|
public static bool TryGetTileAir(this GridCoordinates coordinates, [MaybeNullWhen(false)] out GasMixture air)
|
||||||
{
|
{
|
||||||
return !(air = coordinates.GetTileAir()!).Equals(default);
|
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
|
||||||
|
return !Equals(air = coordinates.GetTileAir()!, default);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TileAtmosphere? GetTileAtmosphere(this MapIndices indices, GridId gridId)
|
public static TileAtmosphere? GetTileAtmosphere(this MapIndices indices, GridId gridId)
|
||||||
@@ -43,14 +45,16 @@ namespace Content.Server.Atmos
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static bool TryGetTileAtmosphere(this MapIndices indices, GridId gridId,
|
public static bool TryGetTileAtmosphere(this MapIndices indices, GridId gridId,
|
||||||
[NotNullWhen(true)] out TileAtmosphere atmosphere)
|
[MaybeNullWhen(false)] out TileAtmosphere atmosphere)
|
||||||
{
|
{
|
||||||
return (atmosphere = indices.GetTileAtmosphere(gridId)!) != default;
|
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
|
||||||
|
return !Equals(atmosphere = indices.GetTileAtmosphere(gridId)!, default);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool TryGetTileAir(this MapIndices indices, GridId gridId, [NotNullWhen(true)] out GasMixture air)
|
public static bool TryGetTileAir(this MapIndices indices, GridId gridId, [MaybeNullWhen(false)] out GasMixture air)
|
||||||
{
|
{
|
||||||
return !(air = indices.GetTileAir(gridId)!).Equals(default);
|
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
|
||||||
|
return !Equals(air = indices.GetTileAir(gridId)!, default);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
74
Content.Server/Atmos/GasSprayerComponent.cs
Normal file
74
Content.Server/Atmos/GasSprayerComponent.cs
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
using Content.Server.GameObjects.Components.Chemistry;
|
||||||
|
using Content.Server.Interfaces;
|
||||||
|
using Content.Shared.Chemistry;
|
||||||
|
using Content.Shared.GameObjects.Components;
|
||||||
|
using Content.Shared.GameObjects.Components.Pointing;
|
||||||
|
using Content.Shared.Interfaces.GameObjects.Components;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Server.GameObjects.EntitySystems;
|
||||||
|
using Robust.Server.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects.Systems;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
|
||||||
|
namespace Content.Server.Atmos
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
public class GasSprayerComponent : Component, IAfterInteract
|
||||||
|
{
|
||||||
|
#pragma warning disable 649
|
||||||
|
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
|
||||||
|
[Dependency] private readonly IServerEntityManager _serverEntityManager = default!;
|
||||||
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
//TODO: create a function that can create a gas based on a solution mix
|
||||||
|
public override string Name => "GasSprayer";
|
||||||
|
|
||||||
|
private string _spraySound;
|
||||||
|
private string _sprayType;
|
||||||
|
private string _fuelType;
|
||||||
|
private string _fuelName;
|
||||||
|
private int _fuelCost;
|
||||||
|
|
||||||
|
public override void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
base.ExposeData(serializer);
|
||||||
|
serializer.DataField(ref _spraySound, "spraySound", string.Empty);
|
||||||
|
serializer.DataField(ref _sprayType, "sprayType", string.Empty);
|
||||||
|
serializer.DataField(ref _fuelType, "fuelType", string.Empty);
|
||||||
|
serializer.DataField(ref _fuelName, "fuelName", "fuel");
|
||||||
|
serializer.DataField(ref _fuelCost, "fuelCost", 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AfterInteract(AfterInteractEventArgs eventArgs)
|
||||||
|
{
|
||||||
|
if (!Owner.TryGetComponent(out SolutionComponent tank))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (tank.Solution.GetReagentQuantity(_fuelType) == 0)
|
||||||
|
{
|
||||||
|
_notifyManager.PopupMessage(Owner, eventArgs.User,
|
||||||
|
Loc.GetString("{0:theName} is out of {1}!", Owner, _fuelName));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tank.TryRemoveReagent(_fuelType, ReagentUnit.New(_fuelCost));
|
||||||
|
|
||||||
|
var playerPos = eventArgs.User.Transform.GridPosition;
|
||||||
|
var direction = (eventArgs.ClickLocation.Position - playerPos.Position).Normalized;
|
||||||
|
playerPos.Offset(direction/2);
|
||||||
|
|
||||||
|
var spray = _serverEntityManager.SpawnEntity(_sprayType, playerPos);
|
||||||
|
spray.GetComponent<AppearanceComponent>()
|
||||||
|
.SetData(ExtinguisherVisuals.Rotation, direction.ToAngle().Degrees);
|
||||||
|
spray.GetComponent<GasVaporComponent>().StartMove(direction, 5);
|
||||||
|
|
||||||
|
EntitySystem.Get<AudioSystem>().PlayFromEntity(_spraySound, Owner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
120
Content.Server/Atmos/GasVaporComponent.cs
Normal file
120
Content.Server/Atmos/GasVaporComponent.cs
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
using Content.Shared.Physics;
|
||||||
|
using Content.Server.Atmos.Reactions;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
using Robust.Shared.GameObjects.Components;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Map;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
using Content.Server.GameObjects.Components.Atmos;
|
||||||
|
using Content.Server.Interfaces;
|
||||||
|
using Content.Shared.Atmos;
|
||||||
|
|
||||||
|
namespace Content.Server.Atmos
|
||||||
|
{
|
||||||
|
[RegisterComponent]
|
||||||
|
class GasVaporComponent : Component, ICollideBehavior, IGasMixtureHolder
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||||
|
public override string Name => "GasVapor";
|
||||||
|
|
||||||
|
[ViewVariables] public GasMixture Air { get; set; }
|
||||||
|
|
||||||
|
private bool _running;
|
||||||
|
private Vector2 _direction;
|
||||||
|
private float _velocity;
|
||||||
|
private float _disspateTimer = 0;
|
||||||
|
private float _dissipationInterval;
|
||||||
|
private Gas _gas;
|
||||||
|
private float _gasVolume;
|
||||||
|
private float _gasTemperature;
|
||||||
|
private float _gasAmount;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
Air = new GasMixture(_gasVolume){Temperature = _gasTemperature};
|
||||||
|
Air.SetMoles(_gas,_gasAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ExposeData(ObjectSerializer serializer)
|
||||||
|
{
|
||||||
|
base.ExposeData(serializer);
|
||||||
|
serializer.DataField(ref _dissipationInterval, "dissipationInterval", 1);
|
||||||
|
serializer.DataField(ref _gas, "gas", Gas.WaterVapor);
|
||||||
|
serializer.DataField(ref _gasVolume, "gasVolume", 200);
|
||||||
|
serializer.DataField(ref _gasTemperature, "gasTemperature", Atmospherics.T20C);
|
||||||
|
serializer.DataField(ref _gasAmount, "gasAmount", 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StartMove(Vector2 dir, float velocity)
|
||||||
|
{
|
||||||
|
_running = true;
|
||||||
|
_direction = dir;
|
||||||
|
_velocity = velocity;
|
||||||
|
|
||||||
|
if (Owner.TryGetComponent(out ICollidableComponent collidable))
|
||||||
|
{
|
||||||
|
var controller = collidable.EnsureController<GasVaporController>();
|
||||||
|
controller.Move(_direction, _velocity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(float frameTime)
|
||||||
|
{
|
||||||
|
if (!_running)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (Owner.TryGetComponent(out ICollidableComponent collidable))
|
||||||
|
{
|
||||||
|
var worldBounds = collidable.WorldAABB;
|
||||||
|
var mapGrid = _mapManager.GetGrid(Owner.Transform.GridID);
|
||||||
|
|
||||||
|
var tiles = mapGrid.GetTilesIntersecting(worldBounds);
|
||||||
|
|
||||||
|
foreach (var tile in tiles)
|
||||||
|
{
|
||||||
|
var pos = tile.GridIndices.ToGridCoordinates(_mapManager, tile.GridIndex);
|
||||||
|
var atmos = AtmosHelpers.GetTileAtmosphere(pos);
|
||||||
|
|
||||||
|
if (atmos.Air == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (atmos.Air.React(this) != ReactionResult.NoReaction)
|
||||||
|
{
|
||||||
|
Owner.Delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_disspateTimer += frameTime;
|
||||||
|
if (_disspateTimer > _dissipationInterval)
|
||||||
|
{
|
||||||
|
Air.SetMoles(_gas, Air.TotalMoles/2 );
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Air.TotalMoles < 1)
|
||||||
|
{
|
||||||
|
Owner.Delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ICollideBehavior.CollideWith(IEntity collidedWith)
|
||||||
|
{
|
||||||
|
// Check for collision with a impassable object (e.g. wall) and stop
|
||||||
|
if (collidedWith.TryGetComponent(out ICollidableComponent collidable) &&
|
||||||
|
(collidable.CollisionLayer & (int) CollisionGroup.Impassable) != 0 &&
|
||||||
|
collidable.Hard &&
|
||||||
|
Owner.TryGetComponent(out ICollidableComponent coll))
|
||||||
|
{
|
||||||
|
var controller = coll.EnsureController<GasVaporController>();
|
||||||
|
controller.Stop();
|
||||||
|
Owner.Delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,7 +23,7 @@ namespace Content.Server.Atmos
|
|||||||
/// State for the fire sprite.
|
/// State for the fire sprite.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
public int State;
|
public byte State;
|
||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using System.Runtime.CompilerServices;
|
|||||||
using Content.Server.Atmos.Reactions;
|
using Content.Server.Atmos.Reactions;
|
||||||
using Content.Server.GameObjects.Components.Atmos;
|
using Content.Server.GameObjects.Components.Atmos;
|
||||||
using Content.Server.GameObjects.EntitySystems;
|
using Content.Server.GameObjects.EntitySystems;
|
||||||
|
using Content.Server.GameObjects.EntitySystems.Atmos;
|
||||||
using Content.Server.Interfaces;
|
using Content.Server.Interfaces;
|
||||||
using Content.Shared.Atmos;
|
using Content.Shared.Atmos;
|
||||||
using Content.Shared.Audio;
|
using Content.Shared.Audio;
|
||||||
@@ -332,32 +333,40 @@ namespace Content.Server.Atmos
|
|||||||
var tile = tiles[i];
|
var tile = tiles[i];
|
||||||
tile._tileAtmosInfo.FastDone = true;
|
tile._tileAtmosInfo.FastDone = true;
|
||||||
if (!(tile._tileAtmosInfo.MoleDelta > 0)) continue;
|
if (!(tile._tileAtmosInfo.MoleDelta > 0)) continue;
|
||||||
var eligibleDirections = new List<Direction>();
|
var eligibleDirections = ArrayPool<Direction>.Shared.Rent(4);
|
||||||
var amtEligibleAdj = 0;
|
var eligibleDirectionCount = 0;
|
||||||
foreach (var direction in Cardinal)
|
foreach (var direction in Cardinal)
|
||||||
{
|
{
|
||||||
if (!tile._adjacentTiles.TryGetValue(direction, out var tile2)) continue;
|
if (!tile._adjacentTiles.TryGetValue(direction, out var tile2)) continue;
|
||||||
|
|
||||||
// skip anything that isn't part of our current processing block. Original one didn't do this unfortunately, which probably cause some massive lag.
|
// skip anything that isn't part of our current processing block.
|
||||||
if (tile2._tileAtmosInfo.FastDone || tile2._tileAtmosInfo.LastQueueCycle != queueCycle)
|
if (tile2._tileAtmosInfo.FastDone || tile2._tileAtmosInfo.LastQueueCycle != queueCycle)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
eligibleDirections.Add(direction);
|
eligibleDirections[eligibleDirectionCount++] = direction;
|
||||||
amtEligibleAdj++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (amtEligibleAdj <= 0)
|
if (eligibleDirectionCount <= 0)
|
||||||
continue; // Oof we've painted ourselves into a corner. Bad luck. Next part will handle this.
|
continue; // Oof we've painted ourselves into a corner. Bad luck. Next part will handle this.
|
||||||
|
|
||||||
var molesToMove = tile._tileAtmosInfo.MoleDelta / amtEligibleAdj;
|
var molesToMove = tile._tileAtmosInfo.MoleDelta / eligibleDirectionCount;
|
||||||
foreach (var direction in Cardinal)
|
foreach (var direction in Cardinal)
|
||||||
{
|
{
|
||||||
if (eligibleDirections.Contains(direction) ||
|
var hasDirection = false;
|
||||||
!tile._adjacentTiles.TryGetValue(direction, out var tile2)) continue;
|
for (var j = 0; j < eligibleDirectionCount; j++)
|
||||||
|
{
|
||||||
|
if (eligibleDirections[j] != direction) continue;
|
||||||
|
hasDirection = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasDirection || !tile._adjacentTiles.TryGetValue(direction, out var tile2)) continue;
|
||||||
tile.AdjustEqMovement(direction, molesToMove);
|
tile.AdjustEqMovement(direction, molesToMove);
|
||||||
tile._tileAtmosInfo.MoleDelta -= molesToMove;
|
tile._tileAtmosInfo.MoleDelta -= molesToMove;
|
||||||
tile2._tileAtmosInfo.MoleDelta += molesToMove;
|
tile2._tileAtmosInfo.MoleDelta += molesToMove;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ArrayPool<Direction>.Shared.Return(eligibleDirections);
|
||||||
}
|
}
|
||||||
|
|
||||||
giverTilesLength = 0;
|
giverTilesLength = 0;
|
||||||
@@ -446,7 +455,7 @@ namespace Content.Server.Atmos
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ArrayPool<TileAtmosphere>.Shared.Return(queue, true);
|
ArrayPool<TileAtmosphere>.Shared.Return(queue);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -516,7 +525,7 @@ namespace Content.Server.Atmos
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ArrayPool<TileAtmosphere>.Shared.Return(queue, true);
|
ArrayPool<TileAtmosphere>.Shared.Return(queue);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < tileCount; i++)
|
for (var i = 0; i < tileCount; i++)
|
||||||
@@ -537,9 +546,9 @@ namespace Content.Server.Atmos
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ArrayPool<TileAtmosphere>.Shared.Return(tiles, true);
|
ArrayPool<TileAtmosphere>.Shared.Return(tiles);
|
||||||
ArrayPool<TileAtmosphere>.Shared.Return(giverTiles, true);
|
ArrayPool<TileAtmosphere>.Shared.Return(giverTiles);
|
||||||
ArrayPool<TileAtmosphere>.Shared.Return(takerTiles, true);
|
ArrayPool<TileAtmosphere>.Shared.Return(takerTiles);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -737,7 +746,7 @@ namespace Content.Server.Atmos
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Hotspot.State = Hotspot.Volume > Atmospherics.CellVolume * 0.4f ? 2 : 1;
|
Hotspot.State = (byte) (Hotspot.Volume > Atmospherics.CellVolume * 0.4f ? 2 : 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Hotspot.Temperature > MaxFireTemperatureSustained)
|
if (Hotspot.Temperature > MaxFireTemperatureSustained)
|
||||||
@@ -925,16 +934,22 @@ namespace Content.Server.Atmos
|
|||||||
public void ExplosivelyDepressurize(int cycleNum)
|
public void ExplosivelyDepressurize(int cycleNum)
|
||||||
{
|
{
|
||||||
if (Air == null) return;
|
if (Air == null) return;
|
||||||
|
|
||||||
|
const int limit = Atmospherics.ZumosTileLimit;
|
||||||
|
|
||||||
var totalGasesRemoved = 0f;
|
var totalGasesRemoved = 0f;
|
||||||
var queueCycle = ++_gridAtmosphereComponent.EqualizationQueueCycleControl;
|
var queueCycle = ++_gridAtmosphereComponent.EqualizationQueueCycleControl;
|
||||||
var tiles = new List<TileAtmosphere>();
|
var tiles = ArrayPool<TileAtmosphere>.Shared.Rent(limit);
|
||||||
var spaceTiles = new List<TileAtmosphere>();
|
var spaceTiles = ArrayPool<TileAtmosphere>.Shared.Rent(limit);
|
||||||
tiles.Add(this);
|
|
||||||
|
var tileCount = 0;
|
||||||
|
var spaceTileCount = 0;
|
||||||
|
|
||||||
|
tiles[tileCount++] = this;
|
||||||
|
|
||||||
ResetTileAtmosInfo();
|
ResetTileAtmosInfo();
|
||||||
_tileAtmosInfo.LastQueueCycle = queueCycle;
|
_tileAtmosInfo.LastQueueCycle = queueCycle;
|
||||||
|
|
||||||
var tileCount = 1;
|
|
||||||
for (var i = 0; i < tileCount; i++)
|
for (var i = 0; i < tileCount; i++)
|
||||||
{
|
{
|
||||||
var tile = tiles[i];
|
var tile = tiles[i];
|
||||||
@@ -942,40 +957,44 @@ namespace Content.Server.Atmos
|
|||||||
tile._tileAtmosInfo.CurrentTransferDirection = Direction.Invalid;
|
tile._tileAtmosInfo.CurrentTransferDirection = Direction.Invalid;
|
||||||
if (tile.Air.Immutable)
|
if (tile.Air.Immutable)
|
||||||
{
|
{
|
||||||
spaceTiles.Add(tile);
|
spaceTiles[spaceTileCount++] = tile;
|
||||||
tile.PressureSpecificTarget = tile;
|
tile.PressureSpecificTarget = tile;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (i > Atmospherics.ZumosTileLimit) continue;
|
|
||||||
foreach (var direction in Cardinal)
|
foreach (var direction in Cardinal)
|
||||||
{
|
{
|
||||||
if (!tile._adjacentTiles.TryGetValue(direction, out var tile2)) continue;
|
if (!tile._adjacentTiles.TryGetValue(direction, out var tile2)) continue;
|
||||||
if (tile2?.Air == null) continue;
|
if (tile2.Air == null) continue;
|
||||||
if (tile2._tileAtmosInfo.LastQueueCycle == queueCycle) continue;
|
if (tile2._tileAtmosInfo.LastQueueCycle == queueCycle) continue;
|
||||||
|
|
||||||
tile.ConsiderFirelocks(tile2);
|
tile.ConsiderFirelocks(tile2);
|
||||||
if (tile._adjacentTiles[direction]?.Air != null)
|
|
||||||
{
|
// The firelocks might have closed on us.
|
||||||
tile2.ResetTileAtmosInfo();
|
if (tile._adjacentTiles[direction]?.Air == null) continue;
|
||||||
tile2._tileAtmosInfo.LastQueueCycle = queueCycle;
|
tile2.ResetTileAtmosInfo();
|
||||||
tiles.Add(tile2);
|
tile2._tileAtmosInfo.LastQueueCycle = queueCycle;
|
||||||
tileCount++;
|
tiles[tileCount++] = tile2;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tileCount >= limit || spaceTileCount >= limit)
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
var queueCycleSlow = ++_gridAtmosphereComponent.EqualizationQueueCycleControl;
|
var queueCycleSlow = ++_gridAtmosphereComponent.EqualizationQueueCycleControl;
|
||||||
var progressionOrder = new List<TileAtmosphere>();
|
var progressionOrder = ArrayPool<TileAtmosphere>.Shared.Rent(limit * 2);
|
||||||
foreach (var tile in spaceTiles)
|
var progressionCount = 0;
|
||||||
|
|
||||||
|
for (var i = 0; i < spaceTileCount; i++)
|
||||||
{
|
{
|
||||||
progressionOrder.Add(tile);
|
var tile = spaceTiles[i];
|
||||||
|
progressionOrder[progressionCount++] = tile;
|
||||||
tile._tileAtmosInfo.LastSlowQueueCycle = queueCycleSlow;
|
tile._tileAtmosInfo.LastSlowQueueCycle = queueCycleSlow;
|
||||||
tile._tileAtmosInfo.CurrentTransferDirection = Direction.Invalid;
|
tile._tileAtmosInfo.CurrentTransferDirection = Direction.Invalid;
|
||||||
}
|
}
|
||||||
|
|
||||||
var progressionCount = progressionOrder.Count;
|
for (var i = 0; i < progressionCount; i++)
|
||||||
for (int i = 0; i < progressionCount; i++)
|
|
||||||
{
|
{
|
||||||
var tile = progressionOrder[i];
|
var tile = progressionOrder[i];
|
||||||
foreach (var direction in Cardinal)
|
foreach (var direction in Cardinal)
|
||||||
@@ -988,8 +1007,7 @@ namespace Content.Server.Atmos
|
|||||||
tile2._tileAtmosInfo.CurrentTransferAmount = 0;
|
tile2._tileAtmosInfo.CurrentTransferAmount = 0;
|
||||||
tile2.PressureSpecificTarget = tile.PressureSpecificTarget;
|
tile2.PressureSpecificTarget = tile.PressureSpecificTarget;
|
||||||
tile2._tileAtmosInfo.LastSlowQueueCycle = queueCycleSlow;
|
tile2._tileAtmosInfo.LastSlowQueueCycle = queueCycleSlow;
|
||||||
progressionOrder.Add(tile2);
|
progressionOrder[progressionCount++] = tile2;
|
||||||
progressionCount++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1017,6 +1035,10 @@ namespace Content.Server.Atmos
|
|||||||
tile.UpdateVisuals();
|
tile.UpdateVisuals();
|
||||||
tile.HandleDecompressionFloorRip(sum);
|
tile.HandleDecompressionFloorRip(sum);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ArrayPool<TileAtmosphere>.Shared.Return(tiles);
|
||||||
|
ArrayPool<TileAtmosphere>.Shared.Return(spaceTiles);
|
||||||
|
ArrayPool<TileAtmosphere>.Shared.Return(progressionOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleDecompressionFloorRip(float sum)
|
private void HandleDecompressionFloorRip(float sum)
|
||||||
@@ -1029,7 +1051,6 @@ namespace Content.Server.Atmos
|
|||||||
private void ConsiderFirelocks(TileAtmosphere other)
|
private void ConsiderFirelocks(TileAtmosphere other)
|
||||||
{
|
{
|
||||||
// TODO ATMOS firelocks!
|
// TODO ATMOS firelocks!
|
||||||
//throw new NotImplementedException();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void React()
|
private void React()
|
||||||
|
|||||||
147
Content.Server/Body/BodyCommands.cs
Normal file
147
Content.Server/Body/BodyCommands.cs
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System.Linq;
|
||||||
|
using Content.Server.GameObjects.Components.Body;
|
||||||
|
using Content.Shared.Body.Part;
|
||||||
|
using Content.Shared.GameObjects.Components.Body;
|
||||||
|
using Robust.Server.Interfaces.Console;
|
||||||
|
using Robust.Server.Interfaces.Player;
|
||||||
|
using Robust.Shared.Interfaces.Random;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
|
||||||
|
namespace Content.Server.Body
|
||||||
|
{
|
||||||
|
class AddHandCommand : IClientCommand
|
||||||
|
{
|
||||||
|
public string Command => "addhand";
|
||||||
|
public string Description => "Adds a hand to your entity.";
|
||||||
|
public string Help => $"Usage: {Command}";
|
||||||
|
|
||||||
|
public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args)
|
||||||
|
{
|
||||||
|
if (player == null)
|
||||||
|
{
|
||||||
|
shell.SendText(player, "Only a player can run this command.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player.AttachedEntity == null)
|
||||||
|
{
|
||||||
|
shell.SendText(player, "You have no entity.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!player.AttachedEntity.TryGetComponent(out BodyManagerComponent? body))
|
||||||
|
{
|
||||||
|
var random = IoCManager.Resolve<IRobustRandom>();
|
||||||
|
var text = $"You have no body{(random.Prob(0.2f) ? " and you must scream." : ".")}";
|
||||||
|
|
||||||
|
shell.SendText(player, text);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||||
|
prototypeManager.TryIndex("bodyPart.Hand.BasicHuman", out BodyPartPrototype prototype);
|
||||||
|
|
||||||
|
var part = new BodyPart(prototype);
|
||||||
|
var slot = part.GetHashCode().ToString();
|
||||||
|
|
||||||
|
body.Template.Slots.Add(slot, BodyPartType.Hand);
|
||||||
|
body.InstallBodyPart(part, slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RemoveHandCommand : IClientCommand
|
||||||
|
{
|
||||||
|
public string Command => "removehand";
|
||||||
|
public string Description => "Removes a hand from your entity.";
|
||||||
|
public string Help => $"Usage: {Command}";
|
||||||
|
|
||||||
|
public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args)
|
||||||
|
{
|
||||||
|
if (player == null)
|
||||||
|
{
|
||||||
|
shell.SendText(player, "Only a player can run this command.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player.AttachedEntity == null)
|
||||||
|
{
|
||||||
|
shell.SendText(player, "You have no entity.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!player.AttachedEntity.TryGetComponent(out BodyManagerComponent? body))
|
||||||
|
{
|
||||||
|
var random = IoCManager.Resolve<IRobustRandom>();
|
||||||
|
var text = $"You have no body{(random.Prob(0.2f) ? " and you must scream." : ".")}";
|
||||||
|
|
||||||
|
shell.SendText(player, text);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hand = body.Parts.FirstOrDefault(x => x.Value.PartType == BodyPartType.Hand);
|
||||||
|
if (hand.Value == null)
|
||||||
|
{
|
||||||
|
shell.SendText(player, "You have no hands.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
body.DisconnectBodyPart(hand.Value, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DestroyMechanismCommand : IClientCommand
|
||||||
|
{
|
||||||
|
public string Command => "destroymechanism";
|
||||||
|
public string Description => "Destroys a mechanism from your entity";
|
||||||
|
public string Help => $"Usage: {Command} <mechanism>";
|
||||||
|
|
||||||
|
public void Execute(IConsoleShell shell, IPlayerSession? player, string[] args)
|
||||||
|
{
|
||||||
|
if (player == null)
|
||||||
|
{
|
||||||
|
shell.SendText(player, "Only a player can run this command.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.Length == 0)
|
||||||
|
{
|
||||||
|
shell.SendText(player, Help);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player.AttachedEntity == null)
|
||||||
|
{
|
||||||
|
shell.SendText(player, "You have no entity.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!player.AttachedEntity.TryGetComponent(out BodyManagerComponent? body))
|
||||||
|
{
|
||||||
|
var random = IoCManager.Resolve<IRobustRandom>();
|
||||||
|
var text = $"You have no body{(random.Prob(0.2f) ? " and you must scream." : ".")}";
|
||||||
|
|
||||||
|
shell.SendText(player, text);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var mechanismName = string.Join(" ", args).ToLowerInvariant();
|
||||||
|
|
||||||
|
foreach (var part in body.Parts.Values)
|
||||||
|
foreach (var mechanism in part.Mechanisms)
|
||||||
|
{
|
||||||
|
if (mechanism.Name.ToLowerInvariant() == mechanismName)
|
||||||
|
{
|
||||||
|
part.DestroyMechanism(mechanism);
|
||||||
|
shell.SendText(player, $"Mechanism with name {mechanismName} has been destroyed.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shell.SendText(player, $"No mechanism was found with name {mechanismName}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
602
Content.Server/Body/BodyPart.cs
Normal file
602
Content.Server/Body/BodyPart.cs
Normal file
@@ -0,0 +1,602 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
|
using Content.Server.Body.Mechanisms;
|
||||||
|
using Content.Server.Body.Surgery;
|
||||||
|
using Content.Server.GameObjects.Components.Body;
|
||||||
|
using Content.Server.GameObjects.Components.Metabolism;
|
||||||
|
using Content.Shared.Body.Mechanism;
|
||||||
|
using Content.Shared.Body.Part;
|
||||||
|
using Content.Shared.Body.Part.Properties;
|
||||||
|
using Content.Shared.Damage.DamageContainer;
|
||||||
|
using Content.Shared.Damage.ResistanceSet;
|
||||||
|
using Content.Shared.GameObjects.Components.Body;
|
||||||
|
using Content.Shared.GameObjects.Components.Damage;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Reflection;
|
||||||
|
using Robust.Shared.Interfaces.Serialization;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.Log;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Maths;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
|
namespace Content.Server.Body
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Data class representing a singular limb such as an arm or a leg.
|
||||||
|
/// Typically held within either a <see cref="BodyManagerComponent"/>,
|
||||||
|
/// which coordinates functions between BodyParts, or a
|
||||||
|
/// <see cref="DroppedBodyPartComponent"/>.
|
||||||
|
/// </summary>
|
||||||
|
public class BodyPart
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The body that this body part is in, if any.
|
||||||
|
/// </summary>
|
||||||
|
private BodyManagerComponent? _body;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set of all <see cref="Mechanism"/> currently inside this
|
||||||
|
/// <see cref="BodyPart"/>.
|
||||||
|
/// To add and remove from this list see <see cref="AddMechanism"/> and
|
||||||
|
/// <see cref="RemoveMechanism"/>
|
||||||
|
/// </summary>
|
||||||
|
private readonly HashSet<Mechanism> _mechanisms = new HashSet<Mechanism>();
|
||||||
|
|
||||||
|
public BodyPart(BodyPartPrototype data)
|
||||||
|
{
|
||||||
|
SurgeryData = null!;
|
||||||
|
Properties = new HashSet<IExposeData>();
|
||||||
|
Name = null!;
|
||||||
|
Plural = null!;
|
||||||
|
RSIPath = null!;
|
||||||
|
RSIState = null!;
|
||||||
|
RSIMap = null!;
|
||||||
|
Damage = null!;
|
||||||
|
Resistances = null!;
|
||||||
|
|
||||||
|
LoadFromPrototype(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The body that this body part is in, if any.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public BodyManagerComponent? Body
|
||||||
|
{
|
||||||
|
get => _body;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
var old = _body;
|
||||||
|
_body = value;
|
||||||
|
|
||||||
|
if (value == null && old != null)
|
||||||
|
{
|
||||||
|
foreach (var mechanism in Mechanisms)
|
||||||
|
{
|
||||||
|
mechanism.RemovedFromBody(old);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var mechanism in Mechanisms)
|
||||||
|
{
|
||||||
|
mechanism.InstalledIntoBody();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="Surgery.SurgeryData"/> class currently representing this BodyPart's
|
||||||
|
/// surgery status.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables] private SurgeryData SurgeryData { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How much space is currently taken up by Mechanisms in this BodyPart.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables] private int SizeUsed { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List of <see cref="IExposeData"/> properties, allowing for additional
|
||||||
|
/// data classes to be attached to a limb, such as a "length" class to an arm.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
private HashSet<IExposeData> Properties { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of this <see cref="BodyPart"/>, often displayed to the user.
|
||||||
|
/// For example, it could be named "advanced robotic arm".
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public string Name { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Plural version of this <see cref="BodyPart"/> name.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public string Plural { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Path to the RSI that represents this <see cref="BodyPart"/>.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public string RSIPath { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// RSI state that represents this <see cref="BodyPart"/>.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public string RSIState { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// RSI map keys that this body part changes on the sprite.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public Enum? RSIMap { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// RSI color of this body part.
|
||||||
|
/// </summary>
|
||||||
|
// TODO: SpriteComponent rework
|
||||||
|
public Color? RSIColor { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="BodyPartType"/> that this <see cref="BodyPart"/> is considered
|
||||||
|
/// to be.
|
||||||
|
/// For example, <see cref="BodyPartType.Arm"/>.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public BodyPartType PartType { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines many things: how many mechanisms can be fit inside this
|
||||||
|
/// <see cref="BodyPart"/>, whether a body can fit through tiny crevices, etc.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
private int Size { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Max HP of this <see cref="BodyPart"/>.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public int MaxDurability { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Current HP of this <see cref="BodyPart"/> based on sum of all damage types.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public int CurrentDurability => MaxDurability - Damage.TotalDamage;
|
||||||
|
|
||||||
|
// TODO: Individual body part damage
|
||||||
|
/// <summary>
|
||||||
|
/// Current damage dealt to this <see cref="BodyPart"/>.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public DamageContainer Damage { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Armor of this <see cref="BodyPart"/> against damages.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public ResistanceSet Resistances { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// At what HP this <see cref="BodyPart"/> destroyed.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public int DestroyThreshold { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// What types of BodyParts this <see cref="BodyPart"/> can easily attach to.
|
||||||
|
/// For the most part, most limbs aren't universal and require extra work to
|
||||||
|
/// attach between types.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public BodyPartCompatibility Compatibility { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set of all <see cref="Mechanism"/> currently inside this
|
||||||
|
/// <see cref="BodyPart"/>.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public IReadOnlyCollection<Mechanism> Mechanisms => _mechanisms;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This method is called by <see cref="BodyManagerComponent.Update"/>
|
||||||
|
/// before <see cref="MetabolismComponent.Update"/> is called.
|
||||||
|
/// </summary>
|
||||||
|
public void PreMetabolism(float frameTime)
|
||||||
|
{
|
||||||
|
foreach (var mechanism in Mechanisms)
|
||||||
|
{
|
||||||
|
mechanism.PreMetabolism(frameTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This method is called by <see cref="BodyManagerComponent.Update"/>
|
||||||
|
/// after <see cref="MetabolismComponent.Update"/> is called.
|
||||||
|
/// </summary>
|
||||||
|
public void PostMetabolism(float frameTime)
|
||||||
|
{
|
||||||
|
foreach (var mechanism in Mechanisms)
|
||||||
|
{
|
||||||
|
mechanism.PreMetabolism(frameTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to add the given <see cref="BodyPartProperty"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// True if a <see cref="BodyPartProperty"/> of that type doesn't exist,
|
||||||
|
/// false otherwise.
|
||||||
|
/// </returns>
|
||||||
|
public bool TryAddProperty(BodyPartProperty property)
|
||||||
|
{
|
||||||
|
if (HasProperty(property.GetType()))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Properties.Add(property);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to retrieve the given <see cref="BodyPartProperty"/> type.
|
||||||
|
/// The resulting <see cref="BodyPartProperty"/> will be null if unsuccessful.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="property">The property if found, null otherwise.</param>
|
||||||
|
/// <typeparam name="T">The type of the property to find.</typeparam>
|
||||||
|
/// <returns>True if successful, false otherwise.</returns>
|
||||||
|
public bool TryGetProperty<T>(out T property)
|
||||||
|
{
|
||||||
|
property = (T) Properties.First(x => x.GetType() == typeof(T));
|
||||||
|
return property != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to retrieve the given <see cref="BodyPartProperty"/> type.
|
||||||
|
/// The resulting <see cref="BodyPartProperty"/> will be null if unsuccessful.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if successful, false otherwise.</returns>
|
||||||
|
public bool TryGetProperty(Type propertyType, out BodyPartProperty property)
|
||||||
|
{
|
||||||
|
property = (BodyPartProperty) Properties.First(x => x.GetType() == propertyType);
|
||||||
|
return property != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the given type <see cref="T"/> is on this <see cref="BodyPart"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">
|
||||||
|
/// The subtype of <see cref="BodyPartProperty"/> to look for.
|
||||||
|
/// </typeparam>
|
||||||
|
/// <returns>
|
||||||
|
/// True if this <see cref="BodyPart"/> has a property of type
|
||||||
|
/// <see cref="T"/>, false otherwise.
|
||||||
|
/// </returns>
|
||||||
|
public bool HasProperty<T>() where T : BodyPartProperty
|
||||||
|
{
|
||||||
|
return Properties.Count(x => x.GetType() == typeof(T)) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if a subtype of <see cref="BodyPartProperty"/> is on this
|
||||||
|
/// <see cref="BodyPart"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="propertyType">
|
||||||
|
/// The subtype of <see cref="BodyPartProperty"/> to look for.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// True if this <see cref="BodyPart"/> has a property of type
|
||||||
|
/// <see cref="propertyType"/>, false otherwise.
|
||||||
|
/// </returns>
|
||||||
|
public bool HasProperty(Type propertyType)
|
||||||
|
{
|
||||||
|
return Properties.Count(x => x.GetType() == propertyType) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if another <see cref="BodyPart"/> can be connected to this one.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="toBeConnected">The part to connect.</param>
|
||||||
|
/// <returns>True if it can be connected, false otherwise.</returns>
|
||||||
|
public bool CanAttachBodyPart(BodyPart toBeConnected)
|
||||||
|
{
|
||||||
|
return SurgeryData.CanAttachBodyPart(toBeConnected);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if a <see cref="Mechanism"/> can be installed on this
|
||||||
|
/// <see cref="BodyPart"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if it can be installed, false otherwise.</returns>
|
||||||
|
public bool CanInstallMechanism(Mechanism mechanism)
|
||||||
|
{
|
||||||
|
return SizeUsed + mechanism.Size <= Size &&
|
||||||
|
SurgeryData.CanInstallMechanism(mechanism);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to install a mechanism onto this body part.
|
||||||
|
/// Call <see cref="TryInstallDroppedMechanism"/> instead if you want to
|
||||||
|
/// easily install an <see cref="IEntity"/> with a
|
||||||
|
/// <see cref="DroppedMechanismComponent"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mechanism">The mechanism to try to install.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// True if successful, false if there was an error
|
||||||
|
/// (e.g. not enough room in <see cref="BodyPart"/>).
|
||||||
|
/// </returns>
|
||||||
|
private bool TryInstallMechanism(Mechanism mechanism)
|
||||||
|
{
|
||||||
|
if (!CanInstallMechanism(mechanism))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
AddMechanism(mechanism);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to install a <see cref="DroppedMechanismComponent"/> into this
|
||||||
|
/// <see cref="BodyPart"/>, potentially deleting the dropped
|
||||||
|
/// <see cref="IEntity"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="droppedMechanism">The mechanism to install.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// True if successful, false if there was an error
|
||||||
|
/// (e.g. not enough room in <see cref="BodyPart"/>).
|
||||||
|
/// </returns>
|
||||||
|
public bool TryInstallDroppedMechanism(DroppedMechanismComponent droppedMechanism)
|
||||||
|
{
|
||||||
|
if (!TryInstallMechanism(droppedMechanism.ContainedMechanism))
|
||||||
|
{
|
||||||
|
return false; //Installing the mechanism failed for some reason.
|
||||||
|
}
|
||||||
|
|
||||||
|
droppedMechanism.Owner.Delete();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to remove the given <see cref="Mechanism"/> reference from
|
||||||
|
/// this <see cref="BodyPart"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// The newly spawned <see cref="DroppedMechanismComponent"/>, or null
|
||||||
|
/// if there was an error in spawning the entity or removing the mechanism.
|
||||||
|
/// </returns>
|
||||||
|
public bool TryDropMechanism(IEntity dropLocation, Mechanism mechanismTarget,
|
||||||
|
[NotNullWhen(true)] out DroppedMechanismComponent dropped)
|
||||||
|
{
|
||||||
|
dropped = null!;
|
||||||
|
|
||||||
|
if (!_mechanisms.Remove(mechanismTarget))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SizeUsed -= mechanismTarget.Size;
|
||||||
|
|
||||||
|
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||||
|
var position = dropLocation.Transform.GridPosition;
|
||||||
|
var mechanismEntity = entityManager.SpawnEntity("BaseDroppedMechanism", position);
|
||||||
|
|
||||||
|
dropped = mechanismEntity.GetComponent<DroppedMechanismComponent>();
|
||||||
|
dropped.InitializeDroppedMechanism(mechanismTarget);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to destroy the given <see cref="Mechanism"/> in this
|
||||||
|
/// <see cref="BodyPart"/>. Does NOT spawn a dropped entity.
|
||||||
|
/// </summary>
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to destroy the given <see cref="Mechanism"/> in this
|
||||||
|
/// <see cref="BodyPart"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mechanismTarget">The mechanism to destroy.</param>
|
||||||
|
/// <returns>True if successful, false otherwise.</returns>
|
||||||
|
public bool DestroyMechanism(Mechanism mechanismTarget)
|
||||||
|
{
|
||||||
|
if (!RemoveMechanism(mechanismTarget))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the given <see cref="SurgeryType"/> can be used on
|
||||||
|
/// the current state of this <see cref="BodyPart"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if it can be used, false otherwise.</returns>
|
||||||
|
public bool SurgeryCheck(SurgeryType toolType)
|
||||||
|
{
|
||||||
|
return SurgeryData.CheckSurgery(toolType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to perform surgery on this <see cref="BodyPart"/> with the given
|
||||||
|
/// tool.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if successful, false if there was an error.</returns>
|
||||||
|
public bool AttemptSurgery(SurgeryType toolType, IBodyPartContainer target, ISurgeon surgeon, IEntity performer)
|
||||||
|
{
|
||||||
|
return SurgeryData.PerformSurgery(toolType, target, surgeon, performer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddMechanism(Mechanism mechanism)
|
||||||
|
{
|
||||||
|
DebugTools.AssertNotNull(mechanism);
|
||||||
|
|
||||||
|
_mechanisms.Add(mechanism);
|
||||||
|
SizeUsed += mechanism.Size;
|
||||||
|
mechanism.Part = this;
|
||||||
|
|
||||||
|
mechanism.EnsureInitialize();
|
||||||
|
|
||||||
|
if (Body == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Body.Template.MechanismLayers.TryGetValue(mechanism.Id, out var mapString))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IoCManager.Resolve<IReflectionManager>().TryParseEnumReference(mapString, out var @enum))
|
||||||
|
{
|
||||||
|
Logger.Warning($"Template {Body.Template.Name} has an invalid RSI map key {mapString} for mechanism {mechanism.Id}.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var message = new MechanismSpriteAddedMessage(@enum);
|
||||||
|
|
||||||
|
Body.Owner.SendNetworkMessage(Body, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to remove the given <see cref="mechanism"/> from this
|
||||||
|
/// <see cref="BodyPart"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mechanism">The mechanism to remove.</param>
|
||||||
|
/// <returns>True if it was removed, false otherwise.</returns>
|
||||||
|
private bool RemoveMechanism(Mechanism mechanism)
|
||||||
|
{
|
||||||
|
DebugTools.AssertNotNull(mechanism);
|
||||||
|
|
||||||
|
if (!_mechanisms.Remove(mechanism))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SizeUsed -= mechanism.Size;
|
||||||
|
mechanism.Part = null;
|
||||||
|
|
||||||
|
if (Body == null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Body.Template.MechanismLayers.TryGetValue(mechanism.Id, out var mapString))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IoCManager.Resolve<IReflectionManager>().TryParseEnumReference(mapString, out var @enum))
|
||||||
|
{
|
||||||
|
Logger.Warning($"Template {Body.Template.Name} has an invalid RSI map key {mapString} for mechanism {mechanism.Id}.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var message = new MechanismSpriteRemovedMessage(@enum);
|
||||||
|
|
||||||
|
Body.Owner.SendNetworkMessage(Body, message);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads the given <see cref="BodyPartPrototype"/>.
|
||||||
|
/// Current data on this <see cref="BodyPart"/> will be overwritten!
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void LoadFromPrototype(BodyPartPrototype data)
|
||||||
|
{
|
||||||
|
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||||
|
|
||||||
|
Name = data.Name;
|
||||||
|
Plural = data.Plural;
|
||||||
|
PartType = data.PartType;
|
||||||
|
RSIPath = data.RSIPath;
|
||||||
|
RSIState = data.RSIState;
|
||||||
|
MaxDurability = data.Durability;
|
||||||
|
|
||||||
|
if (!prototypeManager.TryIndex(data.DamageContainerPresetId,
|
||||||
|
out DamageContainerPrototype damageContainerData))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"No {nameof(DamageContainerPrototype)} found with id {data.DamageContainerPresetId}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Damage = new DamageContainer(OnHealthChanged, damageContainerData);
|
||||||
|
|
||||||
|
if (!prototypeManager.TryIndex(data.ResistanceSetId, out ResistanceSetPrototype resistancesData))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"No {nameof(ResistanceSetPrototype)} found with id {data.ResistanceSetId}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Resistances = new ResistanceSet(resistancesData);
|
||||||
|
Size = data.Size;
|
||||||
|
Compatibility = data.Compatibility;
|
||||||
|
|
||||||
|
Properties.Clear();
|
||||||
|
Properties.UnionWith(data.Properties);
|
||||||
|
|
||||||
|
var surgeryDataType = Type.GetType(data.SurgeryDataName);
|
||||||
|
|
||||||
|
if (surgeryDataType == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"No {nameof(Surgery.SurgeryData)} found with name {data.SurgeryDataName}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!surgeryDataType.IsSubclassOf(typeof(SurgeryData)))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Class {data.SurgeryDataName} is not a subtype of {nameof(Surgery.SurgeryData)} with id {data.ID}");
|
||||||
|
}
|
||||||
|
|
||||||
|
SurgeryData = IoCManager.Resolve<IDynamicTypeFactory>().CreateInstance<SurgeryData>(surgeryDataType, new object[] {this});
|
||||||
|
|
||||||
|
foreach (var id in data.Mechanisms)
|
||||||
|
{
|
||||||
|
if (!prototypeManager.TryIndex(id, out MechanismPrototype mechanismData))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"No {nameof(MechanismPrototype)} found with id {id}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var mechanism = new Mechanism(mechanismData);
|
||||||
|
|
||||||
|
AddMechanism(mechanism);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnHealthChanged(List<HealthChangeData> changes)
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SpawnDropped([NotNullWhen(true)] out IEntity dropped)
|
||||||
|
{
|
||||||
|
dropped = default!;
|
||||||
|
|
||||||
|
if (Body == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
dropped = IoCManager.Resolve<IEntityManager>().SpawnEntity("BaseDroppedBodyPart", Body.Owner.Transform.GridPosition);
|
||||||
|
|
||||||
|
dropped.GetComponent<DroppedBodyPartComponent>().TransferBodyPartData(this);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
Content.Server/Body/BodyPreset.cs
Normal file
36
Content.Server/Body/BodyPreset.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Shared.Body.Part;
|
||||||
|
using Content.Shared.Body.Preset;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
|
namespace Content.Server.Body
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Stores data on what <see cref="BodyPartPrototype"></see> should
|
||||||
|
/// fill a BodyTemplate.
|
||||||
|
/// Used for loading complete body presets, like a "basic human" with all
|
||||||
|
/// human limbs.
|
||||||
|
/// </summary>
|
||||||
|
public class BodyPreset
|
||||||
|
{
|
||||||
|
public BodyPreset(BodyPresetPrototype data)
|
||||||
|
{
|
||||||
|
LoadFromPrototype(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
[ViewVariables] public string Name { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maps a template slot to the ID of the <see cref="BodyPart"/> that should
|
||||||
|
/// fill it. E.g. "right arm" : "BodyPart.arm.basic_human".
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public Dictionary<string, string> PartIDs { get; private set; }
|
||||||
|
|
||||||
|
protected virtual void LoadFromPrototype(BodyPresetPrototype data)
|
||||||
|
{
|
||||||
|
Name = data.Name;
|
||||||
|
PartIDs = data.PartIDs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
145
Content.Server/Body/BodyTemplate.cs
Normal file
145
Content.Server/Body/BodyTemplate.cs
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Content.Server.GameObjects.Components.Body;
|
||||||
|
using Content.Shared.Body.Template;
|
||||||
|
using Content.Shared.GameObjects.Components.Body;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
|
namespace Content.Server.Body
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This class is a data capsule representing the standard format of a
|
||||||
|
/// <see cref="BodyManagerComponent"/>.
|
||||||
|
/// For instance, the "humanoid" BodyTemplate defines two arms, each connected to
|
||||||
|
/// a torso and so on.
|
||||||
|
/// Capable of loading data from a <see cref="BodyTemplatePrototype"/>.
|
||||||
|
/// </summary>
|
||||||
|
public class BodyTemplate
|
||||||
|
{
|
||||||
|
public BodyTemplate()
|
||||||
|
{
|
||||||
|
Name = "empty";
|
||||||
|
CenterSlot = "";
|
||||||
|
Slots = new Dictionary<string, BodyPartType>();
|
||||||
|
Connections = new Dictionary<string, List<string>>();
|
||||||
|
Layers = new Dictionary<string, string>();
|
||||||
|
MechanismLayers = new Dictionary<string, string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BodyTemplate(BodyTemplatePrototype data)
|
||||||
|
{
|
||||||
|
LoadFromPrototype(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
[ViewVariables] public string Name { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the center BodyPart. For humans, this is set to "torso".
|
||||||
|
/// Used in many calculations.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public string CenterSlot { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maps all parts on this template to its BodyPartType.
|
||||||
|
/// For instance, "right arm" is mapped to "BodyPartType.arm" on the humanoid
|
||||||
|
/// template.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public Dictionary<string, BodyPartType> Slots { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maps limb name to the list of their connections to other limbs.
|
||||||
|
/// For instance, on the humanoid template "torso" is mapped to a list
|
||||||
|
/// containing "right arm", "left arm", "left leg", and "right leg".
|
||||||
|
/// This is mapped both ways during runtime, but in the prototype only one
|
||||||
|
/// way has to be defined, i.e., "torso" to "left arm" will automatically
|
||||||
|
/// map "left arm" to "torso".
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public Dictionary<string, List<string>> Connections { get; private set; }
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
public Dictionary<string, string> Layers { get; private set; }
|
||||||
|
|
||||||
|
[ViewVariables]
|
||||||
|
public Dictionary<string, string> MechanismLayers { get; private set; }
|
||||||
|
|
||||||
|
public bool Equals(BodyTemplate other)
|
||||||
|
{
|
||||||
|
return GetHashCode() == other.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the given slot exists in this <see cref="BodyTemplate"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if it does, false otherwise.</returns>
|
||||||
|
public bool SlotExists(string slotName)
|
||||||
|
{
|
||||||
|
return Slots.Keys.Any(slot => slot == slotName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates the hash code for this instance of <see cref="BodyTemplate"/>.
|
||||||
|
/// It does not matter in which order the Connections or Slots are defined.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// An integer unique to this <see cref="BodyTemplate"/>'s layout.
|
||||||
|
/// </returns>
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
var slotsHash = 0;
|
||||||
|
var connectionsHash = 0;
|
||||||
|
|
||||||
|
foreach (var (key, value) in Slots)
|
||||||
|
{
|
||||||
|
var slot = key.GetHashCode();
|
||||||
|
slot = HashCode.Combine(slot, value.GetHashCode());
|
||||||
|
slotsHash ^= slot;
|
||||||
|
}
|
||||||
|
|
||||||
|
var connections = new List<int>();
|
||||||
|
foreach (var (key, value) in Connections)
|
||||||
|
{
|
||||||
|
foreach (var targetBodyPart in value)
|
||||||
|
{
|
||||||
|
var connection = key.GetHashCode() ^ targetBodyPart.GetHashCode();
|
||||||
|
if (!connections.Contains(connection))
|
||||||
|
{
|
||||||
|
connections.Add(connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var connection in connections)
|
||||||
|
{
|
||||||
|
connectionsHash ^= connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
// One of the unit tests considers 0 to be an error, but it will be 0 if
|
||||||
|
// the BodyTemplate is empty, so let's shift that up to 1.
|
||||||
|
var hash = HashCode.Combine(
|
||||||
|
CenterSlot.GetHashCode(),
|
||||||
|
slotsHash,
|
||||||
|
connectionsHash);
|
||||||
|
|
||||||
|
if (hash == 0)
|
||||||
|
{
|
||||||
|
hash++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void LoadFromPrototype(BodyTemplatePrototype data)
|
||||||
|
{
|
||||||
|
Name = data.Name;
|
||||||
|
CenterSlot = data.CenterSlot;
|
||||||
|
Slots = data.Slots;
|
||||||
|
Connections = data.Connections;
|
||||||
|
Layers = data.Layers;
|
||||||
|
MechanismLayers = data.MechanismLayers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
Content.Server/Body/IBodyPartContainer.cs
Normal file
19
Content.Server/Body/IBodyPartContainer.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using Content.Server.Body.Surgery;
|
||||||
|
using Content.Server.GameObjects.Components.Body;
|
||||||
|
|
||||||
|
namespace Content.Server.Body
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Making a class inherit from this interface allows you to do many things with
|
||||||
|
/// it in the <see cref="SurgeryData"/> class.
|
||||||
|
/// This includes passing it as an argument to a
|
||||||
|
/// <see cref="SurgeryData.SurgeryAction"/> delegate, as to later typecast it back
|
||||||
|
/// to the original class type.
|
||||||
|
/// Every BodyPart also needs an <see cref="IBodyPartContainer"/> to be its parent
|
||||||
|
/// (i.e. the <see cref="BodyManagerComponent"/> holds many <see cref="BodyPart"/>,
|
||||||
|
/// each of which have an upward reference to it).
|
||||||
|
/// </summary>
|
||||||
|
public interface IBodyPartContainer
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Content.Server.Body.Mechanisms.Behaviors
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The behaviors of a brain, inhabitable by a player.
|
||||||
|
/// </summary>
|
||||||
|
public class BrainBehavior : MechanismBehavior
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
38
Content.Server/Body/Mechanisms/Behaviors/HeartBehavior.cs
Normal file
38
Content.Server/Body/Mechanisms/Behaviors/HeartBehavior.cs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using Content.Server.Body.Network;
|
||||||
|
using Content.Server.GameObjects.Components.Body.Circulatory;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
|
||||||
|
namespace Content.Server.Body.Mechanisms.Behaviors
|
||||||
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
|
public class HeartBehavior : MechanismBehavior
|
||||||
|
{
|
||||||
|
private float _accumulatedFrameTime;
|
||||||
|
|
||||||
|
protected override Type? Network => typeof(CirculatoryNetwork);
|
||||||
|
|
||||||
|
public override void PreMetabolism(float frameTime)
|
||||||
|
{
|
||||||
|
// TODO do between pre and metabolism
|
||||||
|
base.PreMetabolism(frameTime);
|
||||||
|
|
||||||
|
if (Mechanism.Body == null ||
|
||||||
|
!Mechanism.Body.Owner.TryGetComponent(out BloodstreamComponent? bloodstream))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update at most once per second
|
||||||
|
_accumulatedFrameTime += frameTime;
|
||||||
|
|
||||||
|
// TODO: Move/accept/process bloodstream reagents only when the heart is pumping
|
||||||
|
if (_accumulatedFrameTime >= 1)
|
||||||
|
{
|
||||||
|
// bloodstream.Update(_accumulatedFrameTime);
|
||||||
|
_accumulatedFrameTime -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
Content.Server/Body/Mechanisms/Behaviors/LungBehavior.cs
Normal file
27
Content.Server/Body/Mechanisms/Behaviors/LungBehavior.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using Content.Server.Body.Network;
|
||||||
|
using Content.Server.GameObjects.Components.Body.Respiratory;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
|
||||||
|
namespace Content.Server.Body.Mechanisms.Behaviors
|
||||||
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
|
public class LungBehavior : MechanismBehavior
|
||||||
|
{
|
||||||
|
protected override Type? Network => typeof(RespiratoryNetwork);
|
||||||
|
|
||||||
|
public override void PreMetabolism(float frameTime)
|
||||||
|
{
|
||||||
|
base.PreMetabolism(frameTime);
|
||||||
|
|
||||||
|
if (Mechanism.Body == null ||
|
||||||
|
!Mechanism.Body.Owner.TryGetComponent(out LungComponent? lung))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lung.Update(frameTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
185
Content.Server/Body/Mechanisms/Behaviors/MechanismBehavior.cs
Normal file
185
Content.Server/Body/Mechanisms/Behaviors/MechanismBehavior.cs
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using Content.Server.GameObjects.Components.Body;
|
||||||
|
using Content.Server.GameObjects.Components.Metabolism;
|
||||||
|
|
||||||
|
namespace Content.Server.Body.Mechanisms.Behaviors
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The behaviors a mechanism performs.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class MechanismBehavior
|
||||||
|
{
|
||||||
|
private bool Initialized { get; set; }
|
||||||
|
|
||||||
|
private bool Removed { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The network, if any, that this behavior forms when its mechanism is
|
||||||
|
/// added and destroys when its mechanism is removed.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual Type? Network { get; } = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Upward reference to the parent <see cref="Mechanisms.Mechanism"/> that this
|
||||||
|
/// behavior is attached to.
|
||||||
|
/// </summary>
|
||||||
|
protected Mechanism Mechanism { get; private set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called by a <see cref="Mechanism"/> to initialize this behavior.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mechanism">The mechanism that owns this behavior.</param>
|
||||||
|
/// <exception cref="InvalidOperationException">
|
||||||
|
/// If the mechanism has already been initialized.
|
||||||
|
/// </exception>
|
||||||
|
public void Initialize(Mechanism mechanism)
|
||||||
|
{
|
||||||
|
if (Initialized)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("This mechanism has already been initialized.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Mechanism = mechanism;
|
||||||
|
|
||||||
|
Initialize();
|
||||||
|
|
||||||
|
if (Mechanism.Body != null)
|
||||||
|
{
|
||||||
|
OnInstalledIntoBody();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Mechanism.Part != null)
|
||||||
|
{
|
||||||
|
OnInstalledIntoPart();
|
||||||
|
}
|
||||||
|
|
||||||
|
Initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when a behavior is removed from a <see cref="Mechanism"/>.
|
||||||
|
/// </summary>
|
||||||
|
public void Remove()
|
||||||
|
{
|
||||||
|
OnRemove();
|
||||||
|
TryRemoveNetwork(Mechanism.Body);
|
||||||
|
|
||||||
|
Mechanism = null!;
|
||||||
|
Removed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when the containing <see cref="BodyPart"/> is attached to a
|
||||||
|
/// <see cref="BodyManagerComponent"/>.
|
||||||
|
/// For instance, attaching a head to a body will call this on the brain inside.
|
||||||
|
/// </summary>
|
||||||
|
public void InstalledIntoBody()
|
||||||
|
{
|
||||||
|
TryAddNetwork();
|
||||||
|
OnInstalledIntoBody();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when the parent <see cref="Mechanisms.Mechanism"/> is
|
||||||
|
/// installed into a <see cref="BodyPart"/>.
|
||||||
|
/// For instance, putting a brain into an empty head.
|
||||||
|
/// </summary>
|
||||||
|
public void InstalledIntoPart()
|
||||||
|
{
|
||||||
|
TryAddNetwork();
|
||||||
|
OnInstalledIntoPart();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when the containing <see cref="BodyPart"/> is removed from a
|
||||||
|
/// <see cref="BodyManagerComponent"/>.
|
||||||
|
/// For instance, cutting off ones head will call this on the brain inside.
|
||||||
|
/// </summary>
|
||||||
|
public void RemovedFromBody(BodyManagerComponent old)
|
||||||
|
{
|
||||||
|
OnRemovedFromBody(old);
|
||||||
|
TryRemoveNetwork(old);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when the parent <see cref="Mechanisms.Mechanism"/> is removed from a
|
||||||
|
/// <see cref="BodyPart"/>.
|
||||||
|
/// For instance, taking a brain out of ones head.
|
||||||
|
/// </summary>
|
||||||
|
public void RemovedFromPart(BodyPart old)
|
||||||
|
{
|
||||||
|
OnRemovedFromPart(old);
|
||||||
|
TryRemoveNetwork(old.Body);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TryAddNetwork()
|
||||||
|
{
|
||||||
|
if (Network != null)
|
||||||
|
{
|
||||||
|
Mechanism.Body?.EnsureNetwork(Network);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TryRemoveNetwork(BodyManagerComponent? body)
|
||||||
|
{
|
||||||
|
if (Network != null)
|
||||||
|
{
|
||||||
|
body?.RemoveNetwork(Network);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called by <see cref="Initialize"/> when this behavior is first initialized.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void Initialize() { }
|
||||||
|
|
||||||
|
protected virtual void OnRemove() { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when the containing <see cref="BodyPart"/> is attached to a
|
||||||
|
/// <see cref="BodyManagerComponent"/>.
|
||||||
|
/// For instance, attaching a head to a body will call this on the brain inside.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void OnInstalledIntoBody() { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when the parent <see cref="Mechanisms.Mechanism"/> is
|
||||||
|
/// installed into a <see cref="BodyPart"/>.
|
||||||
|
/// For instance, putting a brain into an empty head.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void OnInstalledIntoPart() { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when the containing <see cref="BodyPart"/> is removed from a
|
||||||
|
/// <see cref="BodyManagerComponent"/>.
|
||||||
|
/// For instance, cutting off ones head will call this on the brain inside.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void OnRemovedFromBody(BodyManagerComponent old) { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when the parent <see cref="Mechanisms.Mechanism"/> is removed from a
|
||||||
|
/// <see cref="BodyPart"/>.
|
||||||
|
/// For instance, taking a brain out of ones head.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void OnRemovedFromPart(BodyPart old) { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called every update when this behavior is connected to a
|
||||||
|
/// <see cref="BodyManagerComponent"/>, but not while in a
|
||||||
|
/// <see cref="DroppedMechanismComponent"/> or
|
||||||
|
/// <see cref="DroppedBodyPartComponent"/>,
|
||||||
|
/// before <see cref="MetabolismComponent.Update"/> is called.
|
||||||
|
/// </summary>
|
||||||
|
public virtual void PreMetabolism(float frameTime) { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called every update when this behavior is connected to a
|
||||||
|
/// <see cref="BodyManagerComponent"/>, but not while in a
|
||||||
|
/// <see cref="DroppedMechanismComponent"/> or
|
||||||
|
/// <see cref="DroppedBodyPartComponent"/>,
|
||||||
|
/// after <see cref="MetabolismComponent.Update"/> is called.
|
||||||
|
/// </summary>
|
||||||
|
public virtual void PostMetabolism(float frameTime) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
36
Content.Server/Body/Mechanisms/Behaviors/StomachBehavior.cs
Normal file
36
Content.Server/Body/Mechanisms/Behaviors/StomachBehavior.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using Content.Server.Body.Network;
|
||||||
|
using Content.Server.GameObjects.Components.Body.Digestive;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
|
||||||
|
namespace Content.Server.Body.Mechanisms.Behaviors
|
||||||
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
|
public class StomachBehavior : MechanismBehavior
|
||||||
|
{
|
||||||
|
private float _accumulatedFrameTime;
|
||||||
|
|
||||||
|
protected override Type? Network => typeof(DigestiveNetwork);
|
||||||
|
|
||||||
|
public override void PreMetabolism(float frameTime)
|
||||||
|
{
|
||||||
|
base.PreMetabolism(frameTime);
|
||||||
|
|
||||||
|
if (Mechanism.Body == null ||
|
||||||
|
!Mechanism.Body.Owner.TryGetComponent(out StomachComponent? stomach))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update at most once per second
|
||||||
|
_accumulatedFrameTime += frameTime;
|
||||||
|
|
||||||
|
if (_accumulatedFrameTime >= 1)
|
||||||
|
{
|
||||||
|
stomach.Update(_accumulatedFrameTime);
|
||||||
|
_accumulatedFrameTime -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
249
Content.Server/Body/Mechanisms/Mechanism.cs
Normal file
249
Content.Server/Body/Mechanisms/Mechanism.cs
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Server.Body.Mechanisms.Behaviors;
|
||||||
|
using Content.Server.GameObjects.Components.Body;
|
||||||
|
using Content.Server.GameObjects.Components.Metabolism;
|
||||||
|
using Content.Shared.Body.Mechanism;
|
||||||
|
using Content.Shared.GameObjects.Components.Body;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
|
namespace Content.Server.Body.Mechanisms
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Data class representing a persistent item inside a <see cref="BodyPart"/>.
|
||||||
|
/// This includes livers, eyes, cameras, brains, explosive implants,
|
||||||
|
/// binary communicators, and other things.
|
||||||
|
/// </summary>
|
||||||
|
public class Mechanism
|
||||||
|
{
|
||||||
|
private BodyPart? _part;
|
||||||
|
|
||||||
|
public Mechanism(MechanismPrototype data)
|
||||||
|
{
|
||||||
|
Data = data;
|
||||||
|
Id = null!;
|
||||||
|
Name = null!;
|
||||||
|
Description = null!;
|
||||||
|
ExamineMessage = null!;
|
||||||
|
RSIPath = null!;
|
||||||
|
RSIState = null!;
|
||||||
|
Behaviors = new List<MechanismBehavior>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[ViewVariables] private bool Initialized { get; set; }
|
||||||
|
|
||||||
|
[ViewVariables] private MechanismPrototype Data { get; set; }
|
||||||
|
|
||||||
|
[ViewVariables] public string Id { get; private set; }
|
||||||
|
|
||||||
|
[ViewVariables] public string Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Professional description of the <see cref="Mechanism"/>.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public string Description { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The message to display upon examining a mob with this Mechanism installed.
|
||||||
|
/// If the string is empty (""), no message will be displayed.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public string ExamineMessage { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Path to the RSI that represents this <see cref="Mechanism"/>.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public string RSIPath { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// RSI state that represents this <see cref="Mechanism"/>.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public string RSIState { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Max HP of this <see cref="Mechanism"/>.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public int MaxDurability { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Current HP of this <see cref="Mechanism"/>.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public int CurrentDurability { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// At what HP this <see cref="Mechanism"/> is completely destroyed.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public int DestroyThreshold { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Armor of this <see cref="Mechanism"/> against attacks.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public int Resistance { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines a handful of things - mostly whether this
|
||||||
|
/// <see cref="Mechanism"/> can fit into a <see cref="BodyPart"/>.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public int Size { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// What kind of <see cref="BodyPart"/> this <see cref="Mechanism"/> can be
|
||||||
|
/// easily installed into.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
public BodyPartCompatibility Compatibility { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The behaviors that this <see cref="Mechanism"/> performs.
|
||||||
|
/// </summary>
|
||||||
|
[ViewVariables]
|
||||||
|
private List<MechanismBehavior> Behaviors { get; }
|
||||||
|
|
||||||
|
public BodyManagerComponent? Body => Part?.Body;
|
||||||
|
|
||||||
|
public BodyPart? Part
|
||||||
|
{
|
||||||
|
get => _part;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
var old = _part;
|
||||||
|
_part = value;
|
||||||
|
|
||||||
|
if (value == null && old != null)
|
||||||
|
{
|
||||||
|
foreach (var behavior in Behaviors)
|
||||||
|
{
|
||||||
|
behavior.RemovedFromPart(old);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var behavior in Behaviors)
|
||||||
|
{
|
||||||
|
behavior.InstalledIntoPart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EnsureInitialize()
|
||||||
|
{
|
||||||
|
if (Initialized)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadFromPrototype(Data);
|
||||||
|
Initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Loads the given <see cref="MechanismPrototype"/>.
|
||||||
|
/// Current data on this <see cref="Mechanism"/> will be overwritten!
|
||||||
|
/// </summary>
|
||||||
|
private void LoadFromPrototype(MechanismPrototype data)
|
||||||
|
{
|
||||||
|
Data = data;
|
||||||
|
Id = data.ID;
|
||||||
|
Name = data.Name;
|
||||||
|
Description = data.Description;
|
||||||
|
ExamineMessage = data.ExamineMessage;
|
||||||
|
RSIPath = data.RSIPath;
|
||||||
|
RSIState = data.RSIState;
|
||||||
|
MaxDurability = data.Durability;
|
||||||
|
CurrentDurability = MaxDurability;
|
||||||
|
DestroyThreshold = data.DestroyThreshold;
|
||||||
|
Resistance = data.Resistance;
|
||||||
|
Size = data.Size;
|
||||||
|
Compatibility = data.Compatibility;
|
||||||
|
|
||||||
|
foreach (var behavior in Behaviors.ToArray())
|
||||||
|
{
|
||||||
|
RemoveBehavior(behavior);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var mechanismBehaviorName in data.BehaviorClasses)
|
||||||
|
{
|
||||||
|
var mechanismBehaviorType = Type.GetType(mechanismBehaviorName);
|
||||||
|
|
||||||
|
if (mechanismBehaviorType == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"No {nameof(MechanismBehavior)} found with name {mechanismBehaviorName}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mechanismBehaviorType.IsSubclassOf(typeof(MechanismBehavior)))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Class {mechanismBehaviorName} is not a subtype of {nameof(MechanismBehavior)} for mechanism prototype {data.ID}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var newBehavior = IoCManager.Resolve<IDynamicTypeFactory>().CreateInstance<MechanismBehavior>(mechanismBehaviorType);
|
||||||
|
|
||||||
|
AddBehavior(newBehavior);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InstalledIntoBody()
|
||||||
|
{
|
||||||
|
foreach (var behavior in Behaviors)
|
||||||
|
{
|
||||||
|
behavior.InstalledIntoBody();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemovedFromBody(BodyManagerComponent old)
|
||||||
|
{
|
||||||
|
foreach (var behavior in Behaviors)
|
||||||
|
{
|
||||||
|
behavior.RemovedFromBody(old);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This method is called by <see cref="BodyPart.PreMetabolism"/> before
|
||||||
|
/// <see cref="MetabolismComponent.Update"/> is called.
|
||||||
|
/// </summary>
|
||||||
|
public void PreMetabolism(float frameTime)
|
||||||
|
{
|
||||||
|
foreach (var behavior in Behaviors)
|
||||||
|
{
|
||||||
|
behavior.PreMetabolism(frameTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This method is called by <see cref="BodyPart.PostMetabolism"/> after
|
||||||
|
/// <see cref="MetabolismComponent.Update"/> is called.
|
||||||
|
/// </summary>
|
||||||
|
public void PostMetabolism(float frameTime)
|
||||||
|
{
|
||||||
|
foreach (var behavior in Behaviors)
|
||||||
|
{
|
||||||
|
behavior.PostMetabolism(frameTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddBehavior(MechanismBehavior behavior)
|
||||||
|
{
|
||||||
|
Behaviors.Add(behavior);
|
||||||
|
behavior.Initialize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool RemoveBehavior(MechanismBehavior behavior)
|
||||||
|
{
|
||||||
|
behavior.Remove();
|
||||||
|
return Behaviors.Remove(behavior);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
76
Content.Server/Body/Network/BodyNetwork.cs
Normal file
76
Content.Server/Body/Network/BodyNetwork.cs
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
using System;
|
||||||
|
using Content.Server.GameObjects.Components.Body;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Interfaces.Serialization;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.ViewVariables;
|
||||||
|
|
||||||
|
namespace Content.Server.Body.Network
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a "network" such as a bloodstream or electrical power that
|
||||||
|
/// is coordinated throughout an entire <see cref="BodyManagerComponent"/>.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class BodyNetwork : IExposeData
|
||||||
|
{
|
||||||
|
[ViewVariables]
|
||||||
|
public abstract string Name { get; }
|
||||||
|
|
||||||
|
protected IEntity Owner { get; private set; }
|
||||||
|
|
||||||
|
public virtual void ExposeData(ObjectSerializer serializer) { }
|
||||||
|
|
||||||
|
public void OnAdd(IEntity entity)
|
||||||
|
{
|
||||||
|
Owner = entity;
|
||||||
|
OnAdd();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnAdd() { }
|
||||||
|
|
||||||
|
public virtual void OnRemove() { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called every update by <see cref="BodyManagerComponent.Update"/>.
|
||||||
|
/// </summary>
|
||||||
|
public virtual void Update(float frameTime) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class BodyNetworkExtensions
|
||||||
|
{
|
||||||
|
public static void TryAddNetwork(this IEntity entity, Type type)
|
||||||
|
{
|
||||||
|
if (!entity.TryGetComponent(out BodyManagerComponent body))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.EnsureNetwork(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void TryAddNetwork<T>(this IEntity entity) where T : BodyNetwork
|
||||||
|
{
|
||||||
|
if (!entity.TryGetComponent(out BodyManagerComponent body))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.EnsureNetwork<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryGetBodyNetwork(this IEntity entity, Type type, out BodyNetwork network)
|
||||||
|
{
|
||||||
|
network = null;
|
||||||
|
|
||||||
|
return entity.TryGetComponent(out BodyManagerComponent body) &&
|
||||||
|
body.TryGetNetwork(type, out network);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryGetBodyNetwork<T>(this IEntity entity, out T network) where T : BodyNetwork
|
||||||
|
{
|
||||||
|
entity.TryGetBodyNetwork(typeof(T), out var unCastNetwork);
|
||||||
|
network = (T) unCastNetwork;
|
||||||
|
return network != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
88
Content.Server/Body/Network/BodyNetworkFactory.cs
Normal file
88
Content.Server/Body/Network/BodyNetworkFactory.cs
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Robust.Shared.Interfaces.Reflection;
|
||||||
|
using Robust.Shared.IoC;
|
||||||
|
|
||||||
|
namespace Content.Server.Body.Network
|
||||||
|
{
|
||||||
|
public class BodyNetworkFactory : IBodyNetworkFactory
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IDynamicTypeFactory _typeFactory = default!;
|
||||||
|
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mapping of body network names to their types.
|
||||||
|
/// </summary>
|
||||||
|
private readonly Dictionary<string, Type> _names = new Dictionary<string, Type>();
|
||||||
|
|
||||||
|
private void Register(Type type)
|
||||||
|
{
|
||||||
|
if (_names.ContainsValue(type))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Type is already registered: {type}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!type.IsSubclassOf(typeof(BodyNetwork)))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"{type} is not a subclass of {nameof(BodyNetwork)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var dummy = _typeFactory.CreateInstance<BodyNetwork>(type);
|
||||||
|
|
||||||
|
if (dummy == null)
|
||||||
|
{
|
||||||
|
throw new NullReferenceException();
|
||||||
|
}
|
||||||
|
|
||||||
|
var name = dummy.Name;
|
||||||
|
|
||||||
|
if (name == null)
|
||||||
|
{
|
||||||
|
throw new NullReferenceException($"{type}'s name cannot be null.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_names.ContainsKey(name))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"{name} is already registered.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_names.Add(name, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DoAutoRegistrations()
|
||||||
|
{
|
||||||
|
var bodyNetwork = typeof(BodyNetwork);
|
||||||
|
|
||||||
|
foreach (var child in _reflectionManager.GetAllChildren(bodyNetwork))
|
||||||
|
{
|
||||||
|
Register(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public BodyNetwork GetNetwork(string name)
|
||||||
|
{
|
||||||
|
Type type;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
type = _names[name];
|
||||||
|
}
|
||||||
|
catch (KeyNotFoundException)
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"No {nameof(BodyNetwork)} exists with name {name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return _typeFactory.CreateInstance<BodyNetwork>(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public BodyNetwork GetNetwork(Type type)
|
||||||
|
{
|
||||||
|
if (!_names.ContainsValue(type))
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"{type} is not registered.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return _typeFactory.CreateInstance<BodyNetwork>(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
Content.Server/Body/Network/CirculatoryNetwork.cs
Normal file
25
Content.Server/Body/Network/CirculatoryNetwork.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
using Content.Server.GameObjects.Components.Body.Circulatory;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.Body.Network
|
||||||
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
|
public class CirculatoryNetwork : BodyNetwork
|
||||||
|
{
|
||||||
|
public override string Name => "Circulatory";
|
||||||
|
|
||||||
|
protected override void OnAdd()
|
||||||
|
{
|
||||||
|
Owner.EnsureComponent<BloodstreamComponent>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnRemove()
|
||||||
|
{
|
||||||
|
if (Owner.HasComponent<BloodstreamComponent>())
|
||||||
|
{
|
||||||
|
Owner.RemoveComponent<BloodstreamComponent>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
Content.Server/Body/Network/DigestiveNetwork.cs
Normal file
28
Content.Server/Body/Network/DigestiveNetwork.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using Content.Server.GameObjects.Components.Body.Digestive;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.Body.Network
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the system that processes food, liquids, and the reagents inside them.
|
||||||
|
/// </summary>
|
||||||
|
[UsedImplicitly]
|
||||||
|
public class DigestiveNetwork : BodyNetwork
|
||||||
|
{
|
||||||
|
public override string Name => "Digestive";
|
||||||
|
|
||||||
|
protected override void OnAdd()
|
||||||
|
{
|
||||||
|
Owner.EnsureComponent<StomachComponent>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnRemove()
|
||||||
|
{
|
||||||
|
if (Owner.HasComponent<StomachComponent>())
|
||||||
|
{
|
||||||
|
Owner.RemoveComponent<StomachComponent>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
Content.Server/Body/Network/IBodyNetworkFactory.cs
Normal file
13
Content.Server/Body/Network/IBodyNetworkFactory.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Content.Server.Body.Network
|
||||||
|
{
|
||||||
|
public interface IBodyNetworkFactory
|
||||||
|
{
|
||||||
|
void DoAutoRegistrations();
|
||||||
|
|
||||||
|
BodyNetwork GetNetwork(string name);
|
||||||
|
|
||||||
|
BodyNetwork GetNetwork(Type type);
|
||||||
|
}
|
||||||
|
}
|
||||||
25
Content.Server/Body/Network/RespiratoryNetwork.cs
Normal file
25
Content.Server/Body/Network/RespiratoryNetwork.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
using Content.Server.GameObjects.Components.Body.Respiratory;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Shared.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.Body.Network
|
||||||
|
{
|
||||||
|
[UsedImplicitly]
|
||||||
|
public class RespiratoryNetwork : BodyNetwork
|
||||||
|
{
|
||||||
|
public override string Name => "Respiratory";
|
||||||
|
|
||||||
|
protected override void OnAdd()
|
||||||
|
{
|
||||||
|
Owner.EnsureComponent<LungComponent>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnRemove()
|
||||||
|
{
|
||||||
|
if (Owner.HasComponent<LungComponent>())
|
||||||
|
{
|
||||||
|
Owner.RemoveComponent<LungComponent>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
250
Content.Server/Body/Surgery/BiologicalSurgeryData.cs
Normal file
250
Content.Server/Body/Surgery/BiologicalSurgeryData.cs
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Content.Server.Body.Mechanisms;
|
||||||
|
using Content.Server.GameObjects.Components.Body;
|
||||||
|
using Content.Shared.GameObjects.Components.Body;
|
||||||
|
using Content.Shared.Interfaces;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
|
||||||
|
namespace Content.Server.Body.Surgery
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Data class representing the surgery state of a biological entity.
|
||||||
|
/// </summary>
|
||||||
|
[UsedImplicitly]
|
||||||
|
public class BiologicalSurgeryData : SurgeryData
|
||||||
|
{
|
||||||
|
private readonly List<Mechanism> _disconnectedOrgans = new List<Mechanism>();
|
||||||
|
|
||||||
|
private bool _skinOpened;
|
||||||
|
private bool _skinRetracted;
|
||||||
|
private bool _vesselsClamped;
|
||||||
|
|
||||||
|
public BiologicalSurgeryData(BodyPart parent) : base(parent) { }
|
||||||
|
|
||||||
|
protected override SurgeryAction? GetSurgeryStep(SurgeryType toolType)
|
||||||
|
{
|
||||||
|
if (toolType == SurgeryType.Amputation)
|
||||||
|
{
|
||||||
|
return RemoveBodyPartSurgery;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_skinOpened)
|
||||||
|
{
|
||||||
|
// Case: skin is normal.
|
||||||
|
if (toolType == SurgeryType.Incision)
|
||||||
|
{
|
||||||
|
return OpenSkinSurgery;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!_vesselsClamped)
|
||||||
|
{
|
||||||
|
// Case: skin is opened, but not clamped.
|
||||||
|
switch (toolType)
|
||||||
|
{
|
||||||
|
case SurgeryType.VesselCompression:
|
||||||
|
return ClampVesselsSurgery;
|
||||||
|
case SurgeryType.Cauterization:
|
||||||
|
return CauterizeIncisionSurgery;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!_skinRetracted)
|
||||||
|
{
|
||||||
|
// Case: skin is opened and clamped, but not retracted.
|
||||||
|
switch (toolType)
|
||||||
|
{
|
||||||
|
case SurgeryType.Retraction:
|
||||||
|
return RetractSkinSurgery;
|
||||||
|
case SurgeryType.Cauterization:
|
||||||
|
return CauterizeIncisionSurgery;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Case: skin is fully open.
|
||||||
|
if (Parent.Mechanisms.Count > 0 &&
|
||||||
|
toolType == SurgeryType.VesselCompression)
|
||||||
|
{
|
||||||
|
if (_disconnectedOrgans.Except(Parent.Mechanisms).Count() != 0 ||
|
||||||
|
Parent.Mechanisms.Except(_disconnectedOrgans).Count() != 0)
|
||||||
|
{
|
||||||
|
return LoosenOrganSurgery;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_disconnectedOrgans.Count > 0 && toolType == SurgeryType.Incision)
|
||||||
|
{
|
||||||
|
return RemoveOrganSurgery;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toolType == SurgeryType.Cauterization)
|
||||||
|
{
|
||||||
|
return CauterizeIncisionSurgery;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string GetDescription(IEntity target)
|
||||||
|
{
|
||||||
|
var toReturn = "";
|
||||||
|
|
||||||
|
if (_skinOpened && !_vesselsClamped)
|
||||||
|
{
|
||||||
|
// Case: skin is opened, but not clamped.
|
||||||
|
toReturn += Loc.GetString("The skin on {0:their} {1} has an incision, but it is prone to bleeding.\n",
|
||||||
|
target, Parent.Name);
|
||||||
|
}
|
||||||
|
else if (_skinOpened && _vesselsClamped && !_skinRetracted)
|
||||||
|
{
|
||||||
|
// Case: skin is opened and clamped, but not retracted.
|
||||||
|
toReturn += Loc.GetString("The skin on {0:their} {1} has an incision, but it is not retracted.\n",
|
||||||
|
target, Parent.Name);
|
||||||
|
}
|
||||||
|
else if (_skinOpened && _vesselsClamped && _skinRetracted)
|
||||||
|
{
|
||||||
|
// Case: skin is fully open.
|
||||||
|
toReturn += Loc.GetString("There is an incision on {0:their} {1}.\n", target, Parent.Name);
|
||||||
|
foreach (var mechanism in _disconnectedOrgans)
|
||||||
|
{
|
||||||
|
toReturn += Loc.GetString("{0:their} {1} is loose.\n", target, mechanism.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return toReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanInstallMechanism(Mechanism mechanism)
|
||||||
|
{
|
||||||
|
return _skinOpened && _vesselsClamped && _skinRetracted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanAttachBodyPart(BodyPart part)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
// TODO: if a bodypart is disconnected, you should have to do some surgery to allow another bodypart to be attached.
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenSkinSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer)
|
||||||
|
{
|
||||||
|
performer.PopupMessage(performer, Loc.GetString("Cut open the skin..."));
|
||||||
|
|
||||||
|
// TODO do_after: Delay
|
||||||
|
_skinOpened = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClampVesselsSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer)
|
||||||
|
{
|
||||||
|
performer.PopupMessage(performer, Loc.GetString("Clamp the vessels..."));
|
||||||
|
|
||||||
|
// TODO do_after: Delay
|
||||||
|
_vesselsClamped = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RetractSkinSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer)
|
||||||
|
{
|
||||||
|
performer.PopupMessage(performer, Loc.GetString("Retract the skin..."));
|
||||||
|
|
||||||
|
// TODO do_after: Delay
|
||||||
|
_skinRetracted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CauterizeIncisionSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer)
|
||||||
|
{
|
||||||
|
performer.PopupMessage(performer, Loc.GetString("Cauterize the incision..."));
|
||||||
|
|
||||||
|
// TODO do_after: Delay
|
||||||
|
_skinOpened = false;
|
||||||
|
_vesselsClamped = false;
|
||||||
|
_skinRetracted = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoosenOrganSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer)
|
||||||
|
{
|
||||||
|
if (Parent.Mechanisms.Count <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var toSend = new List<Mechanism>();
|
||||||
|
foreach (var mechanism in Parent.Mechanisms)
|
||||||
|
{
|
||||||
|
if (!_disconnectedOrgans.Contains(mechanism))
|
||||||
|
{
|
||||||
|
toSend.Add(mechanism);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toSend.Count > 0)
|
||||||
|
{
|
||||||
|
surgeon.RequestMechanism(toSend, LoosenOrganSurgeryCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoosenOrganSurgeryCallback(Mechanism target, IBodyPartContainer container, ISurgeon surgeon,
|
||||||
|
IEntity performer)
|
||||||
|
{
|
||||||
|
if (target == null || !Parent.Mechanisms.Contains(target))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
performer.PopupMessage(performer, Loc.GetString("Loosen the organ..."));
|
||||||
|
|
||||||
|
// TODO do_after: Delay
|
||||||
|
_disconnectedOrgans.Add(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveOrganSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer)
|
||||||
|
{
|
||||||
|
if (_disconnectedOrgans.Count <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_disconnectedOrgans.Count == 1)
|
||||||
|
{
|
||||||
|
RemoveOrganSurgeryCallback(_disconnectedOrgans[0], container, surgeon, performer);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
surgeon.RequestMechanism(_disconnectedOrgans, RemoveOrganSurgeryCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveOrganSurgeryCallback(Mechanism target, IBodyPartContainer container,
|
||||||
|
ISurgeon surgeon,
|
||||||
|
IEntity performer)
|
||||||
|
{
|
||||||
|
if (target == null || !Parent.Mechanisms.Contains(target))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
performer.PopupMessage(performer, Loc.GetString("Remove the organ..."));
|
||||||
|
|
||||||
|
// TODO do_after: Delay
|
||||||
|
Parent.TryDropMechanism(performer, target, out _);
|
||||||
|
_disconnectedOrgans.Remove(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveBodyPartSurgery(IBodyPartContainer container, ISurgeon surgeon, IEntity performer)
|
||||||
|
{
|
||||||
|
// This surgery requires a DroppedBodyPartComponent.
|
||||||
|
if (!(container is BodyManagerComponent))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bmTarget = (BodyManagerComponent) container;
|
||||||
|
performer.PopupMessage(performer, Loc.GetString("Saw off the limb!"));
|
||||||
|
|
||||||
|
// TODO do_after: Delay
|
||||||
|
bmTarget.DisconnectBodyPart(Parent, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
34
Content.Server/Body/Surgery/ISurgeon.cs
Normal file
34
Content.Server/Body/Surgery/ISurgeon.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Content.Server.Body.Mechanisms;
|
||||||
|
using Content.Server.GameObjects.Components.Body;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.Body.Surgery
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interface representing an entity capable of performing surgery (performing operations on an
|
||||||
|
/// <see cref="SurgeryData"/> class).
|
||||||
|
/// For an example see <see cref="SurgeryToolComponent"/>, which inherits from this class.
|
||||||
|
/// </summary>
|
||||||
|
public interface ISurgeon
|
||||||
|
{
|
||||||
|
public delegate void MechanismRequestCallback(
|
||||||
|
Mechanism target,
|
||||||
|
IBodyPartContainer container,
|
||||||
|
ISurgeon surgeon,
|
||||||
|
IEntity performer);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How long it takes to perform a single surgery step (in seconds).
|
||||||
|
/// </summary>
|
||||||
|
public float BaseOperationTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When performing a surgery, the <see cref="SurgeryData"/> may sometimes require selecting from a set of Mechanisms
|
||||||
|
/// to operate on.
|
||||||
|
/// This function is called in that scenario, and it is expected that you call the callback with one mechanism from the
|
||||||
|
/// provided list.
|
||||||
|
/// </summary>
|
||||||
|
public void RequestMechanism(IEnumerable<Mechanism> options, MechanismRequestCallback callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
91
Content.Server/Body/Surgery/SurgeryData.cs
Normal file
91
Content.Server/Body/Surgery/SurgeryData.cs
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
#nullable enable
|
||||||
|
using Content.Server.Body.Mechanisms;
|
||||||
|
using Content.Shared.GameObjects.Components.Body;
|
||||||
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Server.Body.Surgery
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// This data class represents the state of a <see cref="BodyPart"/> in regards to everything surgery related -
|
||||||
|
/// whether there's an incision on it, whether the bone is broken, etc.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class SurgeryData
|
||||||
|
{
|
||||||
|
protected delegate void SurgeryAction(IBodyPartContainer container, ISurgeon surgeon, IEntity performer);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="BodyPart"/> this surgeryData is attached to.
|
||||||
|
/// The <see cref="SurgeryData"/> class should not exist without a
|
||||||
|
/// <see cref="BodyPart"/> that it represents, and will throw errors if it
|
||||||
|
/// is null.
|
||||||
|
/// </summary>
|
||||||
|
protected readonly BodyPart Parent;
|
||||||
|
|
||||||
|
protected SurgeryData(BodyPart parent)
|
||||||
|
{
|
||||||
|
Parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="BodyPartType"/> of the parent <see cref="BodyPart"/>.
|
||||||
|
/// </summary>
|
||||||
|
protected BodyPartType ParentType => Parent.PartType;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the description of this current <see cref="BodyPart"/> to be shown
|
||||||
|
/// upon observing the given entity.
|
||||||
|
/// </summary>
|
||||||
|
public abstract string GetDescription(IEntity target);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns whether a <see cref="Mechanism"/> can be installed into the
|
||||||
|
/// <see cref="BodyPart"/> this <see cref="SurgeryData"/> represents.
|
||||||
|
/// </summary>
|
||||||
|
public abstract bool CanInstallMechanism(Mechanism mechanism);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns whether the given <see cref="BodyPart"/> can be connected to the
|
||||||
|
/// <see cref="BodyPart"/> this <see cref="SurgeryData"/> represents.
|
||||||
|
/// </summary>
|
||||||
|
public abstract bool CanAttachBodyPart(BodyPart part);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the delegate corresponding to the surgery step using the given
|
||||||
|
/// <see cref="SurgeryType"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// The corresponding surgery action or null if no step can be performed.
|
||||||
|
/// </returns>
|
||||||
|
protected abstract SurgeryAction? GetSurgeryStep(SurgeryType toolType);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns whether the given <see cref="SurgeryType"/> can be used to perform a surgery on the BodyPart this
|
||||||
|
/// <see cref="SurgeryData"/> represents.
|
||||||
|
/// </summary>
|
||||||
|
public bool CheckSurgery(SurgeryType toolType)
|
||||||
|
{
|
||||||
|
return GetSurgeryStep(toolType) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to perform surgery of the given <see cref="SurgeryType"/>. Returns whether the operation was successful.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="surgeryType">The <see cref="SurgeryType"/> used for this surgery.</param>
|
||||||
|
/// <param name="container">The container where the surgery is being done.</param>
|
||||||
|
/// <param name="surgeon">The entity being used to perform the surgery.</param>
|
||||||
|
/// <param name="performer">The entity performing the surgery.</param>
|
||||||
|
public bool PerformSurgery(SurgeryType surgeryType, IBodyPartContainer container, ISurgeon surgeon,
|
||||||
|
IEntity performer)
|
||||||
|
{
|
||||||
|
var step = GetSurgeryStep(surgeryType);
|
||||||
|
|
||||||
|
if (step == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
step(container, surgeon, performer);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
using System.Linq;
|
using System;
|
||||||
using Content.Server.GameObjects.Components.Damage;
|
using System.Linq;
|
||||||
using Content.Server.GameObjects.Components.GUI;
|
using Content.Server.GameObjects.Components.GUI;
|
||||||
using Content.Server.GameObjects.Components.Items.Storage;
|
using Content.Server.GameObjects.Components.Items.Storage;
|
||||||
using Content.Server.GameObjects.Components.Observer;
|
using Content.Server.GameObjects.Components.Observer;
|
||||||
using Content.Server.Interfaces.Chat;
|
using Content.Server.Interfaces.Chat;
|
||||||
using Content.Server.Interfaces.GameObjects;
|
using Content.Server.Interfaces.GameObjects;
|
||||||
|
using Content.Server.Observer;
|
||||||
using Content.Server.Players;
|
using Content.Server.Players;
|
||||||
using Content.Shared.GameObjects.Components.Damage;
|
using Content.Shared.GameObjects.Components.Damage;
|
||||||
using Robust.Server.Interfaces.Console;
|
using Robust.Server.Interfaces.Console;
|
||||||
@@ -13,6 +14,7 @@ using Robust.Shared.Enums;
|
|||||||
using Robust.Shared.Interfaces.GameObjects;
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Shared.IoC;
|
||||||
using Robust.Shared.Localization;
|
using Robust.Shared.Localization;
|
||||||
|
using Content.Shared.Damage;
|
||||||
|
|
||||||
namespace Content.Server.Chat
|
namespace Content.Server.Chat
|
||||||
{
|
{
|
||||||
@@ -30,9 +32,11 @@ namespace Content.Server.Chat
|
|||||||
if (args.Length < 1)
|
if (args.Length < 1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var chat = IoCManager.Resolve<IChatManager>();
|
var message = string.Join(" ", args).Trim();
|
||||||
|
if (string.IsNullOrEmpty(message))
|
||||||
|
return;
|
||||||
|
|
||||||
var message = string.Join(" ", args);
|
var chat = IoCManager.Resolve<IChatManager>();
|
||||||
|
|
||||||
if (player.AttachedEntity.HasComponent<GhostComponent>())
|
if (player.AttachedEntity.HasComponent<GhostComponent>())
|
||||||
chat.SendDeadChat(player, message);
|
chat.SendDeadChat(player, message);
|
||||||
@@ -59,9 +63,11 @@ namespace Content.Server.Chat
|
|||||||
if (args.Length < 1)
|
if (args.Length < 1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var chat = IoCManager.Resolve<IChatManager>();
|
var action = string.Join(" ", args).Trim();
|
||||||
|
if (string.IsNullOrEmpty(action))
|
||||||
|
return;
|
||||||
|
|
||||||
var action = string.Join(" ", args);
|
var chat = IoCManager.Resolve<IChatManager>();
|
||||||
|
|
||||||
var mindComponent = player.ContentData().Mind;
|
var mindComponent = player.ContentData().Mind;
|
||||||
chat.EntityMe(mindComponent.OwnedEntity, action);
|
chat.EntityMe(mindComponent.OwnedEntity, action);
|
||||||
@@ -76,8 +82,15 @@ namespace Content.Server.Chat
|
|||||||
|
|
||||||
public void Execute(IConsoleShell shell, IPlayerSession player, string[] args)
|
public void Execute(IConsoleShell shell, IPlayerSession player, string[] args)
|
||||||
{
|
{
|
||||||
|
if (args.Length < 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var message = string.Join(" ", args).Trim();
|
||||||
|
if (string.IsNullOrEmpty(message))
|
||||||
|
return;
|
||||||
|
|
||||||
var chat = IoCManager.Resolve<IChatManager>();
|
var chat = IoCManager.Resolve<IChatManager>();
|
||||||
chat.SendOOC(player, string.Join(" ", args));
|
chat.SendOOC(player, message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,8 +102,15 @@ namespace Content.Server.Chat
|
|||||||
|
|
||||||
public void Execute(IConsoleShell shell, IPlayerSession player, string[] args)
|
public void Execute(IConsoleShell shell, IPlayerSession player, string[] args)
|
||||||
{
|
{
|
||||||
|
if (args.Length < 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var message = string.Join(" ", args).Trim();
|
||||||
|
if (string.IsNullOrEmpty(message))
|
||||||
|
return;
|
||||||
|
|
||||||
var chat = IoCManager.Resolve<IChatManager>();
|
var chat = IoCManager.Resolve<IChatManager>();
|
||||||
chat.SendAdminChat(player, string.Join(" ", args));
|
chat.SendAdminChat(player, message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,24 +125,24 @@ namespace Content.Server.Chat
|
|||||||
"If that fails, it will attempt to use an object in the environment.\n" +
|
"If that fails, it will attempt to use an object in the environment.\n" +
|
||||||
"Finally, if neither of the above worked, you will die by biting your tongue.";
|
"Finally, if neither of the above worked, you will die by biting your tongue.";
|
||||||
|
|
||||||
private void DealDamage(ISuicideAct suicide, IChatManager chat, DamageableComponent damageableComponent, IEntity source, IEntity target)
|
private void DealDamage(ISuicideAct suicide, IChatManager chat, IDamageableComponent damageableComponent, IEntity source, IEntity target)
|
||||||
{
|
{
|
||||||
SuicideKind kind = suicide.Suicide(target, chat);
|
SuicideKind kind = suicide.Suicide(target, chat);
|
||||||
if (kind != SuicideKind.Special)
|
if (kind != SuicideKind.Special)
|
||||||
{
|
{
|
||||||
damageableComponent.TakeDamage(kind switch
|
damageableComponent.ChangeDamage(kind switch
|
||||||
{
|
{
|
||||||
SuicideKind.Brute => DamageType.Brute,
|
SuicideKind.Blunt => DamageType.Blunt,
|
||||||
SuicideKind.Heat => DamageType.Heat,
|
SuicideKind.Piercing => DamageType.Piercing,
|
||||||
SuicideKind.Cold => DamageType.Cold,
|
SuicideKind.Heat => DamageType.Heat,
|
||||||
SuicideKind.Acid => DamageType.Acid,
|
SuicideKind.Disintegration => DamageType.Disintegration,
|
||||||
SuicideKind.Toxic => DamageType.Toxic,
|
SuicideKind.Cellular => DamageType.Cellular,
|
||||||
SuicideKind.Electric => DamageType.Electric,
|
SuicideKind.DNA => DamageType.DNA,
|
||||||
_ => DamageType.Brute
|
SuicideKind.Asphyxiation => DamageType.Asphyxiation,
|
||||||
},
|
_ => DamageType.Blunt
|
||||||
500, //TODO: needs to be a max damage of some sorts
|
},
|
||||||
source,
|
500,
|
||||||
target);
|
true, source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +153,7 @@ namespace Content.Server.Chat
|
|||||||
|
|
||||||
var chat = IoCManager.Resolve<IChatManager>();
|
var chat = IoCManager.Resolve<IChatManager>();
|
||||||
var owner = player.ContentData().Mind.OwnedMob.Owner;
|
var owner = player.ContentData().Mind.OwnedMob.Owner;
|
||||||
var dmgComponent = owner.GetComponent<DamageableComponent>();
|
var dmgComponent = owner.GetComponent<IDamageableComponent>();
|
||||||
//TODO: needs to check if the mob is actually alive
|
//TODO: needs to check if the mob is actually alive
|
||||||
//TODO: maybe set a suicided flag to prevent ressurection?
|
//TODO: maybe set a suicided flag to prevent ressurection?
|
||||||
|
|
||||||
@@ -167,7 +187,11 @@ namespace Content.Server.Chat
|
|||||||
}
|
}
|
||||||
// Default suicide, bite your tongue
|
// Default suicide, bite your tongue
|
||||||
chat.EntityMe(owner, Loc.GetString("is attempting to bite {0:their} own tongue, looks like {0:theyre} trying to commit suicide!", owner)); //TODO: theyre macro
|
chat.EntityMe(owner, Loc.GetString("is attempting to bite {0:their} own tongue, looks like {0:theyre} trying to commit suicide!", owner)); //TODO: theyre macro
|
||||||
dmgComponent.TakeDamage(DamageType.Brute, 500, owner, owner); //TODO: dmg value needs to be a max damage of some sorts
|
dmgComponent.ChangeDamage(DamageType.Piercing, 500, true, owner);
|
||||||
|
|
||||||
|
// Prevent the player from returning to the body. Yes, this is an ugly hack.
|
||||||
|
var ghost = new Ghost(){CanReturn = false};
|
||||||
|
ghost.Execute(shell, player, Array.Empty<string>());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ using Content.Server.Interfaces;
|
|||||||
using Content.Server.Interfaces.Chat;
|
using Content.Server.Interfaces.Chat;
|
||||||
using Content.Shared.Chat;
|
using Content.Shared.Chat;
|
||||||
using Content.Shared.GameObjects.EntitySystems;
|
using Content.Shared.GameObjects.EntitySystems;
|
||||||
|
using NFluidsynth;
|
||||||
using Robust.Server.Console;
|
using Robust.Server.Console;
|
||||||
|
using Robust.Server.Interfaces.GameObjects;
|
||||||
using Robust.Server.Interfaces.Player;
|
using Robust.Server.Interfaces.Player;
|
||||||
using Robust.Shared.Interfaces.GameObjects;
|
using Robust.Shared.Interfaces.GameObjects;
|
||||||
using Robust.Shared.Interfaces.Network;
|
using Robust.Shared.Interfaces.Network;
|
||||||
@@ -19,8 +21,18 @@ namespace Content.Server.Chat
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed class ChatManager : IChatManager
|
internal sealed class ChatManager : IChatManager
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum length a player-sent message can be sent
|
||||||
|
/// </summary>
|
||||||
|
public int MaxMessageLength = 1000;
|
||||||
|
|
||||||
private const int VoiceRange = 7; // how far voice goes in world units
|
private const int VoiceRange = 7; // how far voice goes in world units
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The message displayed to the player when it exceeds the chat character limit
|
||||||
|
/// </summary>
|
||||||
|
private const string MaxLengthExceededMessage = "Your message exceeded {0} character limit";
|
||||||
|
|
||||||
#pragma warning disable 649
|
#pragma warning disable 649
|
||||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager;
|
[Dependency] private readonly IEntitySystemManager _entitySystemManager;
|
||||||
[Dependency] private readonly IServerNetManager _netManager;
|
[Dependency] private readonly IServerNetManager _netManager;
|
||||||
@@ -33,6 +45,12 @@ namespace Content.Server.Chat
|
|||||||
public void Initialize()
|
public void Initialize()
|
||||||
{
|
{
|
||||||
_netManager.RegisterNetMessage<MsgChatMessage>(MsgChatMessage.NAME);
|
_netManager.RegisterNetMessage<MsgChatMessage>(MsgChatMessage.NAME);
|
||||||
|
_netManager.RegisterNetMessage<ChatMaxMsgLengthMessage>(ChatMaxMsgLengthMessage.NAME, _onMaxLengthRequest);
|
||||||
|
|
||||||
|
// Tell all the connected players the chat's character limit
|
||||||
|
var msg = _netManager.CreateNetMessage<ChatMaxMsgLengthMessage>();
|
||||||
|
msg.MaxMessageLength = MaxMessageLength;
|
||||||
|
_netManager.ServerSendToAll(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DispatchServerAnnouncement(string message)
|
public void DispatchServerAnnouncement(string message)
|
||||||
@@ -69,6 +87,17 @@ namespace Content.Server.Chat
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get entity's PlayerSession
|
||||||
|
IPlayerSession playerSession = source.GetComponent<IActorComponent>().playerSession;
|
||||||
|
|
||||||
|
// Check if message exceeds the character limit if the sender is a player
|
||||||
|
if (playerSession != null)
|
||||||
|
if (message.Length > MaxMessageLength)
|
||||||
|
{
|
||||||
|
DispatchServerMessage(playerSession, Loc.GetString(MaxLengthExceededMessage, MaxMessageLength));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var pos = source.Transform.GridPosition;
|
var pos = source.Transform.GridPosition;
|
||||||
var clients = _playerManager.GetPlayersInRange(pos, VoiceRange).Select(p => p.ConnectedClient);
|
var clients = _playerManager.GetPlayersInRange(pos, VoiceRange).Select(p => p.ConnectedClient);
|
||||||
|
|
||||||
@@ -90,6 +119,17 @@ namespace Content.Server.Chat
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if entity is a player
|
||||||
|
IPlayerSession playerSession = source.GetComponent<IActorComponent>().playerSession;
|
||||||
|
|
||||||
|
// Check if message exceeds the character limit
|
||||||
|
if (playerSession != null)
|
||||||
|
if (action.Length > MaxMessageLength)
|
||||||
|
{
|
||||||
|
DispatchServerMessage(playerSession, Loc.GetString(MaxLengthExceededMessage, MaxMessageLength));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var pos = source.Transform.GridPosition;
|
var pos = source.Transform.GridPosition;
|
||||||
var clients = _playerManager.GetPlayersInRange(pos, VoiceRange).Select(p => p.ConnectedClient);
|
var clients = _playerManager.GetPlayersInRange(pos, VoiceRange).Select(p => p.ConnectedClient);
|
||||||
|
|
||||||
@@ -103,6 +143,13 @@ namespace Content.Server.Chat
|
|||||||
|
|
||||||
public void SendOOC(IPlayerSession player, string message)
|
public void SendOOC(IPlayerSession player, string message)
|
||||||
{
|
{
|
||||||
|
// Check if message exceeds the character limi
|
||||||
|
if (message.Length > MaxMessageLength)
|
||||||
|
{
|
||||||
|
DispatchServerMessage(player, Loc.GetString(MaxLengthExceededMessage, MaxMessageLength));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var msg = _netManager.CreateNetMessage<MsgChatMessage>();
|
var msg = _netManager.CreateNetMessage<MsgChatMessage>();
|
||||||
msg.Channel = ChatChannel.OOC;
|
msg.Channel = ChatChannel.OOC;
|
||||||
msg.Message = message;
|
msg.Message = message;
|
||||||
@@ -114,6 +161,13 @@ namespace Content.Server.Chat
|
|||||||
|
|
||||||
public void SendDeadChat(IPlayerSession player, string message)
|
public void SendDeadChat(IPlayerSession player, string message)
|
||||||
{
|
{
|
||||||
|
// Check if message exceeds the character limit
|
||||||
|
if (message.Length > MaxMessageLength)
|
||||||
|
{
|
||||||
|
DispatchServerMessage(player, Loc.GetString(MaxLengthExceededMessage, MaxMessageLength));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var clients = _playerManager.GetPlayersBy(x => x.AttachedEntity != null && x.AttachedEntity.HasComponent<GhostComponent>()).Select(p => p.ConnectedClient);;
|
var clients = _playerManager.GetPlayersBy(x => x.AttachedEntity != null && x.AttachedEntity.HasComponent<GhostComponent>()).Select(p => p.ConnectedClient);;
|
||||||
|
|
||||||
var msg = _netManager.CreateNetMessage<MsgChatMessage>();
|
var msg = _netManager.CreateNetMessage<MsgChatMessage>();
|
||||||
@@ -126,7 +180,14 @@ namespace Content.Server.Chat
|
|||||||
|
|
||||||
public void SendAdminChat(IPlayerSession player, string message)
|
public void SendAdminChat(IPlayerSession player, string message)
|
||||||
{
|
{
|
||||||
if(!_conGroupController.CanCommand(player, "asay"))
|
// Check if message exceeds the character limit
|
||||||
|
if (message.Length > MaxMessageLength)
|
||||||
|
{
|
||||||
|
DispatchServerMessage(player, Loc.GetString(MaxLengthExceededMessage, MaxMessageLength));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_conGroupController.CanCommand(player, "asay"))
|
||||||
{
|
{
|
||||||
SendOOC(player, message);
|
SendOOC(player, message);
|
||||||
return;
|
return;
|
||||||
@@ -149,5 +210,12 @@ namespace Content.Server.Chat
|
|||||||
msg.MessageWrap = $"OOC: (D){sender}: {{0}}";
|
msg.MessageWrap = $"OOC: (D){sender}: {{0}}";
|
||||||
_netManager.ServerSendToAll(msg);
|
_netManager.ServerSendToAll(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void _onMaxLengthRequest(ChatMaxMsgLengthMessage msg)
|
||||||
|
{
|
||||||
|
var response = _netManager.CreateNetMessage<ChatMaxMsgLengthMessage>();
|
||||||
|
response.MaxMessageLength = MaxMessageLength;
|
||||||
|
_netManager.ServerSendMessage(response, msg.MsgChannel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
<OutputPath>..\bin\Content.Server\</OutputPath>
|
<OutputPath>..\bin\Content.Server\</OutputPath>
|
||||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||||
<OutputType Condition="'$(FullRelease)' != 'True'">Exe</OutputType>
|
<OutputType Condition="'$(FullRelease)' != 'True'">Exe</OutputType>
|
||||||
|
<NoWarn>1998</NoWarn>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<Import Project="..\RobustToolbox\MSBuild\Robust.DefineConstants.targets" />
|
<Import Project="..\RobustToolbox\MSBuild\Robust.DefineConstants.targets" />
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
using Content.Server.AI.Utility.Considerations;
|
using Content.Server.AI.Utility.Considerations;
|
||||||
using Content.Server.AI.WorldState;
|
using Content.Server.AI.WorldState;
|
||||||
using Content.Server.GameObjects.Components.NodeContainer.NodeGroups;
|
using Content.Server.GameObjects.Components.NodeContainer.NodeGroups;
|
||||||
using Content.Server.Interfaces;
|
using Content.Server.Interfaces;
|
||||||
using Content.Server.Interfaces.Chat;
|
using Content.Server.Interfaces.Chat;
|
||||||
|
using Content.Server.Body.Network;
|
||||||
using Content.Server.Interfaces.GameTicking;
|
using Content.Server.Interfaces.GameTicking;
|
||||||
using Content.Server.Interfaces.PDA;
|
using Content.Server.Interfaces.PDA;
|
||||||
using Content.Server.Sandbox;
|
using Content.Server.Sandbox;
|
||||||
@@ -46,6 +47,8 @@ namespace Content.Server
|
|||||||
|
|
||||||
IoCManager.BuildGraph();
|
IoCManager.BuildGraph();
|
||||||
|
|
||||||
|
IoCManager.Resolve<IBodyNetworkFactory>().DoAutoRegistrations();
|
||||||
|
|
||||||
_gameTicker = IoCManager.Resolve<IGameTicker>();
|
_gameTicker = IoCManager.Resolve<IGameTicker>();
|
||||||
|
|
||||||
IoCManager.Resolve<IServerNotifyManager>().Initialize();
|
IoCManager.Resolve<IServerNotifyManager>().Initialize();
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user