Merge remote-tracking branch 'upstream/master' into 20-10-30-admins
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
using Content.Client.GameObjects.Components.Arcade;
|
||||
using System.Linq;
|
||||
using Content.Client.GameObjects.Components.Arcade;
|
||||
using Content.Shared.GameObjects.Components.Arcade;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Localization;
|
||||
using Vector2 = Robust.Shared.Maths.Vector2;
|
||||
|
||||
namespace Content.Client.Arcade
|
||||
@@ -16,17 +18,17 @@ namespace Content.Client.Arcade
|
||||
private Label _enemyInfoLabel;
|
||||
private Label _playerActionLabel;
|
||||
private Label _enemyActionLabel;
|
||||
|
||||
private Button[] _gameButtons = new Button[3]; //used to disable/enable all game buttons
|
||||
public SpaceVillainArcadeMenu(SpaceVillainArcadeBoundUserInterface owner)
|
||||
{
|
||||
Title = "Space Villain";
|
||||
Title = Loc.GetString("Space Villain");
|
||||
Owner = owner;
|
||||
|
||||
GridContainer grid = new GridContainer();
|
||||
grid.Columns = 1;
|
||||
var grid = new GridContainer {Columns = 1};
|
||||
|
||||
GridContainer infoGrid = new GridContainer();
|
||||
infoGrid.Columns = 3;
|
||||
infoGrid.AddChild(new Label{ Text = "Player", Align = Label.AlignMode.Center });
|
||||
var infoGrid = new GridContainer {Columns = 3};
|
||||
infoGrid.AddChild(new Label{ Text = Loc.GetString("Player"), Align = Label.AlignMode.Center });
|
||||
infoGrid.AddChild(new Label{ Text = "|", Align = Label.AlignMode.Center });
|
||||
_enemyNameLabel = new Label{ Align = Label.AlignMode.Center};
|
||||
infoGrid.AddChild(_enemyNameLabel);
|
||||
@@ -36,38 +38,35 @@ namespace Content.Client.Arcade
|
||||
infoGrid.AddChild(new Label{ Text = "|", Align = Label.AlignMode.Center });
|
||||
_enemyInfoLabel = new Label {Align = Label.AlignMode.Center};
|
||||
infoGrid.AddChild(_enemyInfoLabel);
|
||||
CenterContainer centerContainer = new CenterContainer();
|
||||
var centerContainer = new CenterContainer();
|
||||
centerContainer.AddChild(infoGrid);
|
||||
grid.AddChild(centerContainer);
|
||||
|
||||
_playerActionLabel = new Label();
|
||||
_playerActionLabel.Align = Label.AlignMode.Center;
|
||||
_playerActionLabel = new Label {Align = Label.AlignMode.Center};
|
||||
grid.AddChild(_playerActionLabel);
|
||||
|
||||
_enemyActionLabel = new Label();
|
||||
_enemyActionLabel.Align = Label.AlignMode.Center;
|
||||
_enemyActionLabel = new Label {Align = Label.AlignMode.Center};
|
||||
grid.AddChild(_enemyActionLabel);
|
||||
|
||||
GridContainer buttonGrid = new GridContainer();
|
||||
buttonGrid.Columns = 3;
|
||||
Button attack = new ActionButton(Owner, SharedSpaceVillainArcadeComponent.PlayerAction.Attack);
|
||||
attack.Text = "ATTACK";
|
||||
buttonGrid.AddChild(attack);
|
||||
var buttonGrid = new GridContainer {Columns = 3};
|
||||
_gameButtons[0] = new ActionButton(Owner, SharedSpaceVillainArcadeComponent.PlayerAction.Attack)
|
||||
{Text = Loc.GetString("ATTACK")};
|
||||
buttonGrid.AddChild(_gameButtons[0]);
|
||||
|
||||
Button heal = new ActionButton(Owner, SharedSpaceVillainArcadeComponent.PlayerAction.Heal);
|
||||
heal.Text = "HEAL";
|
||||
buttonGrid.AddChild(heal);
|
||||
_gameButtons[1] = new ActionButton(Owner, SharedSpaceVillainArcadeComponent.PlayerAction.Heal)
|
||||
{Text = Loc.GetString("HEAL")};
|
||||
buttonGrid.AddChild(_gameButtons[1]);
|
||||
|
||||
Button recharge = new ActionButton(Owner, SharedSpaceVillainArcadeComponent.PlayerAction.Recharge);
|
||||
recharge.Text = "RECHARGE";
|
||||
buttonGrid.AddChild(recharge);
|
||||
_gameButtons[2] = new ActionButton(Owner, SharedSpaceVillainArcadeComponent.PlayerAction.Recharge)
|
||||
{Text = Loc.GetString("RECHARGE")};
|
||||
buttonGrid.AddChild(_gameButtons[2]);
|
||||
|
||||
centerContainer = new CenterContainer();
|
||||
centerContainer.AddChild(buttonGrid);
|
||||
grid.AddChild(centerContainer);
|
||||
|
||||
Button newGame = new ActionButton(Owner, SharedSpaceVillainArcadeComponent.PlayerAction.NewGame);
|
||||
newGame.Text = "New Game";
|
||||
var newGame = new ActionButton(Owner, SharedSpaceVillainArcadeComponent.PlayerAction.NewGame)
|
||||
{Text = Loc.GetString("New Game")};
|
||||
grid.AddChild(newGame);
|
||||
|
||||
centerContainer = new CenterContainer();
|
||||
@@ -79,6 +78,11 @@ namespace Content.Client.Arcade
|
||||
{
|
||||
Title = message.GameTitle;
|
||||
_enemyNameLabel.Text = message.EnemyName;
|
||||
|
||||
foreach (var gameButton in _gameButtons)
|
||||
{
|
||||
gameButton.Disabled = message.ButtonsDisabled;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateInfo(SharedSpaceVillainArcadeComponent.SpaceVillainArcadeDataUpdateMessage message)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Client.Interfaces;
|
||||
@@ -54,27 +55,23 @@ namespace Content.Client
|
||||
|
||||
private void DoNotifyEntity(MsgDoNotifyEntity message)
|
||||
{
|
||||
if (!_entityManager.TryGetEntity(message.Entity, out var entity))
|
||||
if (_playerManager.LocalPlayer?.ControlledEntity == null ||
|
||||
!_entityManager.TryGetEntity(message.Entity, out var entity))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PopupMessage(_eyeManager.CoordinatesToScreen(entity.Transform.Coordinates), message.Message);
|
||||
PopupMessage(entity, _playerManager.LocalPlayer.ControlledEntity, message.Message);
|
||||
}
|
||||
|
||||
public override void PopupMessage(IEntity source, IEntity viewer, string message)
|
||||
{
|
||||
if (viewer != _playerManager.LocalPlayer?.ControlledEntity)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PopupMessage(_eyeManager.CoordinatesToScreen(source.Transform.Coordinates), message);
|
||||
PopupMessage(_eyeManager.CoordinatesToScreen(source.Transform.Coordinates), message, source);
|
||||
}
|
||||
|
||||
public override void PopupMessage(EntityCoordinates coordinates, IEntity viewer, string message)
|
||||
{
|
||||
if (viewer != _playerManager.LocalPlayer.ControlledEntity)
|
||||
if (viewer != _playerManager.LocalPlayer?.ControlledEntity)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -84,7 +81,7 @@ namespace Content.Client
|
||||
|
||||
public override void PopupMessageCursor(IEntity viewer, string message)
|
||||
{
|
||||
if (viewer != _playerManager.LocalPlayer.ControlledEntity)
|
||||
if (viewer != _playerManager.LocalPlayer?.ControlledEntity)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -94,13 +91,21 @@ namespace Content.Client
|
||||
|
||||
public void PopupMessage(ScreenCoordinates coordinates, string message)
|
||||
{
|
||||
var label = new PopupLabel
|
||||
PopupMessage(coordinates, message, null);
|
||||
}
|
||||
|
||||
public void PopupMessage(ScreenCoordinates coordinates, string message, IEntity? entity)
|
||||
{
|
||||
var label = new PopupLabel(_eyeManager)
|
||||
{
|
||||
Entity = entity,
|
||||
Text = message,
|
||||
StyleClasses = { StyleNano.StyleClassPopupMessage },
|
||||
};
|
||||
|
||||
_userInterfaceManager.PopupRoot.AddChild(label);
|
||||
var minimumSize = label.CombinedMinimumSize;
|
||||
|
||||
LayoutContainer.SetPosition(label, label.InitialPos = coordinates.Position - minimumSize / 2);
|
||||
_aliveLabels.Add(label);
|
||||
}
|
||||
@@ -125,20 +130,30 @@ namespace Content.Client
|
||||
|
||||
private class PopupLabel : Label
|
||||
{
|
||||
private readonly IEyeManager _eyeManager;
|
||||
|
||||
public float TimeLeft { get; private set; }
|
||||
public Vector2 InitialPos { get; set; }
|
||||
public IEntity? Entity { get; set; }
|
||||
|
||||
public PopupLabel()
|
||||
public PopupLabel(IEyeManager eyeManager)
|
||||
{
|
||||
_eyeManager = eyeManager;
|
||||
ShadowOffsetXOverride = 1;
|
||||
ShadowOffsetYOverride = 1;
|
||||
FontColorShadowOverride = Color.Black;
|
||||
}
|
||||
|
||||
protected override void Update(FrameEventArgs eventArgs)
|
||||
protected override void FrameUpdate(FrameEventArgs eventArgs)
|
||||
{
|
||||
TimeLeft += eventArgs.DeltaSeconds;
|
||||
LayoutContainer.SetPosition(this, InitialPos - (0, 20 * (TimeLeft * TimeLeft + TimeLeft)));
|
||||
|
||||
var position = Entity == null
|
||||
? InitialPos
|
||||
: _eyeManager.CoordinatesToScreen(Entity.Transform.Coordinates).Position - CombinedMinimumSize / 2;
|
||||
|
||||
LayoutContainer.SetPosition(this, position - (0, 20 * (TimeLeft * TimeLeft + TimeLeft)));
|
||||
|
||||
if (TimeLeft > 0.5f)
|
||||
{
|
||||
Modulate = Color.White.WithAlpha(1f - 0.2f * (float)Math.Pow(TimeLeft - 0.5f, 3f));
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<OutputPath>..\bin\Content.Client\</OutputPath>
|
||||
<OutputType Condition="'$(FullRelease)' != 'True'">Exe</OutputType>
|
||||
<WarningsAsErrors>CS8604;CS8765</WarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
<Import Project="..\RobustToolbox\MSBuild\Robust.DefineConstants.targets" />
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#nullable enable
|
||||
#nullable enable
|
||||
using Content.Client.GameObjects.Components.Disposal;
|
||||
using Content.Client.GameObjects.Components.MedicalScanner;
|
||||
using Content.Shared.GameObjects.Components.Body;
|
||||
@@ -14,7 +14,8 @@ namespace Content.Client.GameObjects.Components.Body
|
||||
public bool CanDrop(CanDropEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.Target.HasComponent<DisposalUnitComponent>() ||
|
||||
eventArgs.Target.HasComponent<MedicalScannerComponent>())
|
||||
eventArgs.Target.HasComponent<MedicalScannerComponent>() ||
|
||||
eventArgs.Target.HasComponent<DisposalMailingUnitComponent>())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
using Robust.Client.GameObjects.Components.UserInterface;
|
||||
using Robust.Shared.GameObjects.Components.UserInterface;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using static Content.Shared.GameObjects.Components.SharedConfigurationComponent;
|
||||
|
||||
namespace Content.Client.GameObjects.Components.Wires
|
||||
{
|
||||
public class ConfigurationBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
public Regex Validation { get; internal set; }
|
||||
|
||||
public ConfigurationBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
|
||||
private ConfigurationMenu _menu;
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
_menu = new ConfigurationMenu(this);
|
||||
|
||||
_menu.OnClose += Close;
|
||||
_menu.OpenCentered();
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
_menu.Populate(state as ConfigurationBoundUserInterfaceState);
|
||||
}
|
||||
|
||||
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
|
||||
{
|
||||
base.ReceiveMessage(message);
|
||||
if (message is ValidationUpdateMessage msg)
|
||||
{
|
||||
Validation = new Regex(msg.ValidationString, RegexOptions.Compiled);
|
||||
}
|
||||
}
|
||||
|
||||
public void SendConfiguration(Dictionary<string, string> config)
|
||||
{
|
||||
SendMessage(new ConfigurationUpdatedMessage(config));
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
_menu.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
using System.Collections.Generic;
|
||||
using Namotion.Reflection;
|
||||
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.SharedConfigurationComponent;
|
||||
using static Robust.Client.UserInterface.Controls.BaseButton;
|
||||
|
||||
namespace Content.Client.GameObjects.Components.Wires
|
||||
{
|
||||
public class ConfigurationMenu : SS14Window
|
||||
{
|
||||
public ConfigurationBoundUserInterface Owner { get; }
|
||||
|
||||
private readonly VBoxContainer _baseContainer;
|
||||
private readonly VBoxContainer _column;
|
||||
private readonly HBoxContainer _row;
|
||||
|
||||
private readonly List<(string name, LineEdit input)> _inputs;
|
||||
|
||||
protected override Vector2? CustomSize => (300, 250);
|
||||
|
||||
public ConfigurationMenu(ConfigurationBoundUserInterface owner)
|
||||
{
|
||||
Owner = owner;
|
||||
|
||||
_inputs = new List<(string name, LineEdit input)>();
|
||||
|
||||
Title = Loc.GetString("Device Configuration");
|
||||
|
||||
var margin = new MarginContainer
|
||||
{
|
||||
MarginBottomOverride = 8,
|
||||
MarginLeftOverride = 8,
|
||||
MarginRightOverride = 8,
|
||||
MarginTopOverride = 8
|
||||
};
|
||||
|
||||
_baseContainer = new VBoxContainer
|
||||
{
|
||||
SizeFlagsVertical = SizeFlags.FillExpand,
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand
|
||||
};
|
||||
|
||||
_column = new VBoxContainer
|
||||
{
|
||||
SeparationOverride = 16,
|
||||
SizeFlagsVertical = SizeFlags.Fill
|
||||
};
|
||||
|
||||
_row = new HBoxContainer
|
||||
{
|
||||
SeparationOverride = 16,
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand
|
||||
};
|
||||
|
||||
var buttonRow = new HBoxContainer
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand
|
||||
};
|
||||
|
||||
var spacer1 = new HBoxContainer
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.Expand
|
||||
};
|
||||
|
||||
var spacer2 = new HBoxContainer()
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.Expand
|
||||
};
|
||||
|
||||
var confirmButton = new Button
|
||||
{
|
||||
Text = Loc.GetString("Confirm"),
|
||||
SizeFlagsHorizontal = SizeFlags.ShrinkCenter,
|
||||
SizeFlagsVertical = SizeFlags.ShrinkCenter
|
||||
};
|
||||
|
||||
confirmButton.OnButtonUp += OnConfirm;
|
||||
buttonRow.AddChild(spacer1);
|
||||
buttonRow.AddChild(confirmButton);
|
||||
buttonRow.AddChild(spacer2);
|
||||
|
||||
var outerColumn = new ScrollContainer
|
||||
{
|
||||
SizeFlagsVertical = SizeFlags.FillExpand,
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
ModulateSelfOverride = Color.FromHex("#202025")
|
||||
};
|
||||
|
||||
margin.AddChild(_column);
|
||||
outerColumn.AddChild(margin);
|
||||
_baseContainer.AddChild(outerColumn);
|
||||
_baseContainer.AddChild(buttonRow);
|
||||
Contents.AddChild(_baseContainer);
|
||||
}
|
||||
|
||||
public void Populate(ConfigurationBoundUserInterfaceState state)
|
||||
{
|
||||
_column.Children.Clear();
|
||||
_inputs.Clear();
|
||||
|
||||
foreach (var field in state.Config)
|
||||
{
|
||||
var margin = new MarginContainer
|
||||
{
|
||||
MarginRightOverride = 8
|
||||
};
|
||||
|
||||
var label = new Label
|
||||
{
|
||||
Name = field.Key,
|
||||
Text = field.Key + ":",
|
||||
SizeFlagsVertical = SizeFlags.ShrinkCenter,
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
SizeFlagsStretchRatio = .2f,
|
||||
CustomMinimumSize = new Vector2(60, 0)
|
||||
};
|
||||
|
||||
var input = new LineEdit
|
||||
{
|
||||
Name = field.Key + "-input",
|
||||
Text = field.Value,
|
||||
IsValid = Validate,
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
SizeFlagsStretchRatio = .8f
|
||||
};
|
||||
|
||||
_inputs.Add((field.Key, input));
|
||||
|
||||
var row = new HBoxContainer();
|
||||
CopyProperties(_row, row);
|
||||
|
||||
margin.AddChild(label);
|
||||
row.AddChild(margin);
|
||||
row.AddChild(input);
|
||||
_column.AddChild(row);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnConfirm(ButtonEventArgs args)
|
||||
{
|
||||
var config = GenerateDictionary<string, LineEdit>(_inputs, "Text");
|
||||
|
||||
Owner.SendConfiguration(config);
|
||||
Close();
|
||||
}
|
||||
|
||||
private bool Validate(string value)
|
||||
{
|
||||
return Owner.Validation == null || Owner.Validation.IsMatch(value);
|
||||
}
|
||||
|
||||
private Dictionary<string, TConfig> GenerateDictionary<TConfig, TInput>(List<(string name, TInput input)> inputs, string propertyName) where TInput : Control
|
||||
{
|
||||
var dictionary = new Dictionary<string, TConfig>();
|
||||
foreach (var input in inputs)
|
||||
{
|
||||
var value = input.input.TryGetPropertyValue<TConfig>(propertyName);
|
||||
dictionary.Add(input.name, value);
|
||||
}
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
private static void CopyProperties<T>(T from, T to) where T : Control
|
||||
{
|
||||
foreach (var property in from.AllAttachedProperties)
|
||||
{
|
||||
to.SetValue(property.Key, property.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
#nullable enable
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects.Components.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.GameObjects.Components.UserInterface;
|
||||
using static Content.Shared.GameObjects.Components.Disposal.SharedDisposalMailingUnitComponent;
|
||||
|
||||
namespace Content.Client.GameObjects.Components.Disposal
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a <see cref="DisposalMailingUnitWindow"/> and updates it when new server messages are received.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public class DisposalMailingUnitBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
private DisposalMailingUnitWindow? _window;
|
||||
|
||||
public DisposalMailingUnitBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
|
||||
private void ButtonPressed(UiButton button)
|
||||
{
|
||||
SendMessage(new UiButtonPressedMessage(button));
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_window = new DisposalMailingUnitWindow();
|
||||
|
||||
_window.OpenCentered();
|
||||
_window.OnClose += Close;
|
||||
|
||||
_window.Eject.OnPressed += _ => ButtonPressed(UiButton.Eject);
|
||||
_window.Engage.OnPressed += _ => ButtonPressed(UiButton.Engage);
|
||||
_window.Power.OnPressed += _ => ButtonPressed(UiButton.Power);
|
||||
_window.TargetListContainer.OnItemSelected += TargetSelected;
|
||||
|
||||
}
|
||||
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
|
||||
if (!(state is DisposalMailingUnitBoundUserInterfaceState cast))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_window?.UpdateState(cast);
|
||||
}
|
||||
|
||||
private void TargetSelected(ItemList.ItemListSelectedEventArgs item)
|
||||
{
|
||||
SendMessage(new UiTargetUpdateMessage(_window?.TargetList[item.ItemIndex]));
|
||||
//(ノ°Д°)ノ︵ ┻━┻
|
||||
if (_window != null) _window.Engage.Disabled = false;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_window?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using Content.Shared.GameObjects.Components.Disposal;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Client.GameObjects.Components.Disposal
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(SharedDisposalMailingUnitComponent))]
|
||||
public class DisposalMailingUnitComponent : SharedDisposalMailingUnitComponent
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
using Content.Shared.GameObjects.Components.Disposal;
|
||||
using Robust.Client.Graphics.Drawing;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using System.Collections.Generic;
|
||||
using static Content.Shared.GameObjects.Components.Disposal.SharedDisposalMailingUnitComponent;
|
||||
|
||||
namespace Content.Client.GameObjects.Components.Disposal
|
||||
{
|
||||
/// <summary>
|
||||
/// Client-side UI used to control a <see cref="SharedDisposalMailingUnitComponent"/>
|
||||
/// </summary>
|
||||
public class DisposalMailingUnitWindow : SS14Window
|
||||
{
|
||||
private readonly Label _unitState;
|
||||
private readonly ProgressBar _pressureBar;
|
||||
private readonly Label _pressurePercentage;
|
||||
public readonly Button Engage;
|
||||
public readonly Button Eject;
|
||||
public readonly Button Power;
|
||||
|
||||
public readonly ItemList TargetListContainer;
|
||||
public List<string> TargetList;
|
||||
private readonly Label _tagLabel;
|
||||
|
||||
protected override Vector2? CustomSize => (460, 220);
|
||||
|
||||
public DisposalMailingUnitWindow()
|
||||
{
|
||||
TargetList = new List<string>();
|
||||
Contents.AddChild(new HBoxContainer
|
||||
{
|
||||
Children =
|
||||
{
|
||||
new MarginContainer
|
||||
{
|
||||
MarginLeftOverride = 8,
|
||||
MarginRightOverride = 8,
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
Children =
|
||||
{
|
||||
new VBoxContainer
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
Children =
|
||||
{
|
||||
new HBoxContainer
|
||||
{
|
||||
Children =
|
||||
{
|
||||
new Label {Text = Loc.GetString("State: ")},
|
||||
new Control {CustomMinimumSize = (4, 0)},
|
||||
(_unitState = new Label {Text = Loc.GetString("Ready")})
|
||||
}
|
||||
},
|
||||
new Control {CustomMinimumSize = (0, 10)},
|
||||
new HBoxContainer
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
Children =
|
||||
{
|
||||
new Label {Text = Loc.GetString("Pressure:")},
|
||||
new Control {CustomMinimumSize = (4, 0)},
|
||||
(_pressureBar = new ProgressBar
|
||||
{
|
||||
CustomMinimumSize = (100, 20),
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
MinValue = 0,
|
||||
MaxValue = 1,
|
||||
Page = 0,
|
||||
Value = 0.5f,
|
||||
Children =
|
||||
{
|
||||
(_pressurePercentage = new Label())
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
new Control {CustomMinimumSize = (0, 10)},
|
||||
new HBoxContainer
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
Children =
|
||||
{
|
||||
new Label {Text = Loc.GetString("Handle:")},
|
||||
new Control {
|
||||
CustomMinimumSize = (4, 0),
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand
|
||||
},
|
||||
(Engage = new Button
|
||||
{
|
||||
CustomMinimumSize = (16, 0),
|
||||
Text = Loc.GetString("Engage"),
|
||||
ToggleMode = true,
|
||||
Disabled = true
|
||||
})
|
||||
}
|
||||
},
|
||||
new Control {CustomMinimumSize = (0, 10)},
|
||||
new HBoxContainer
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
Children =
|
||||
{
|
||||
new Label {Text = Loc.GetString("Eject:")},
|
||||
new Control {
|
||||
CustomMinimumSize = (4, 0),
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand
|
||||
},
|
||||
(Eject = new Button {
|
||||
CustomMinimumSize = (16, 0),
|
||||
Text = Loc.GetString("Eject Contents"),
|
||||
//SizeFlagsHorizontal = SizeFlags.ShrinkEnd
|
||||
})
|
||||
}
|
||||
},
|
||||
new Control {CustomMinimumSize = (0, 10)},
|
||||
new HBoxContainer
|
||||
{
|
||||
Children =
|
||||
{
|
||||
(Power = new CheckButton {Text = Loc.GetString("Power")}),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
new MarginContainer
|
||||
{
|
||||
MarginLeftOverride = 12,
|
||||
MarginRightOverride = 8,
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
Children =
|
||||
{
|
||||
new VBoxContainer
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.Fill,
|
||||
Children =
|
||||
{
|
||||
new HBoxContainer
|
||||
{
|
||||
Children =
|
||||
{
|
||||
new Label
|
||||
{
|
||||
Text = Loc.GetString("Select a destination:")
|
||||
}
|
||||
}
|
||||
},
|
||||
new Control { CustomMinimumSize = new Vector2(0, 8) },
|
||||
new HBoxContainer
|
||||
{
|
||||
SizeFlagsVertical = SizeFlags.FillExpand,
|
||||
Children =
|
||||
{
|
||||
(TargetListContainer = new ItemList
|
||||
{
|
||||
SelectMode = ItemList.ItemListSelectMode.Single,
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
SizeFlagsVertical = SizeFlags.FillExpand
|
||||
})
|
||||
}
|
||||
},
|
||||
new PanelContainer
|
||||
{
|
||||
PanelOverride = new StyleBoxFlat
|
||||
{
|
||||
BackgroundColor = Color.FromHex("#ACBDBA")
|
||||
},
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
CustomMinimumSize = new Vector2(0, 1),
|
||||
},
|
||||
new HBoxContainer
|
||||
{
|
||||
Children =
|
||||
{
|
||||
new VBoxContainer
|
||||
{
|
||||
Children =
|
||||
{
|
||||
new MarginContainer
|
||||
{
|
||||
MarginLeftOverride = 4,
|
||||
Children =
|
||||
{
|
||||
new HBoxContainer
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
Children =
|
||||
{
|
||||
new Label
|
||||
{
|
||||
Text = Loc.GetString("This unit:")
|
||||
},
|
||||
new Control
|
||||
{
|
||||
CustomMinimumSize = new Vector2(4, 0)
|
||||
},
|
||||
(_tagLabel = new Label
|
||||
{
|
||||
Text = "-",
|
||||
SizeFlagsVertical = SizeFlags.ShrinkEnd
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void UpdatePressureBar(float pressure)
|
||||
{
|
||||
_pressureBar.Value = pressure;
|
||||
|
||||
var normalized = pressure / _pressureBar.MaxValue;
|
||||
|
||||
const float leftHue = 0.0f; // Red
|
||||
const float middleHue = 0.066f; // Orange
|
||||
const float rightHue = 0.33f; // Green
|
||||
const float saturation = 1.0f; // Uniform saturation
|
||||
const float value = 0.8f; // Uniform value / brightness
|
||||
const float alpha = 1.0f; // Uniform alpha
|
||||
|
||||
// These should add up to 1.0 or your transition won't be smooth
|
||||
const float leftSideSize = 0.5f; // Fraction of _chargeBar lerped from leftHue to middleHue
|
||||
const float rightSideSize = 0.5f; // Fraction of _chargeBar lerped from middleHue to rightHue
|
||||
|
||||
float finalHue;
|
||||
if (normalized <= leftSideSize)
|
||||
{
|
||||
normalized /= leftSideSize; // Adjust range to 0.0 to 1.0
|
||||
finalHue = MathHelper.Lerp(leftHue, middleHue, normalized);
|
||||
}
|
||||
else
|
||||
{
|
||||
normalized = (normalized - leftSideSize) / rightSideSize; // Adjust range to 0.0 to 1.0.
|
||||
finalHue = MathHelper.Lerp(middleHue, rightHue, normalized);
|
||||
}
|
||||
|
||||
// Check if null first to avoid repeatedly creating this.
|
||||
_pressureBar.ForegroundStyleBoxOverride ??= new StyleBoxFlat();
|
||||
|
||||
var foregroundStyleBoxOverride = (StyleBoxFlat) _pressureBar.ForegroundStyleBoxOverride;
|
||||
foregroundStyleBoxOverride.BackgroundColor =
|
||||
Color.FromHsv(new Vector4(finalHue, saturation, value, alpha));
|
||||
|
||||
var percentage = pressure / _pressureBar.MaxValue * 100;
|
||||
_pressurePercentage.Text = $" {percentage:0}%";
|
||||
}
|
||||
|
||||
public void UpdateState(DisposalMailingUnitBoundUserInterfaceState state)
|
||||
{
|
||||
Title = state.UnitName;
|
||||
_unitState.Text = state.UnitState;
|
||||
UpdatePressureBar(state.Pressure);
|
||||
Power.Pressed = state.Powered;
|
||||
Engage.Pressed = state.Engaged;
|
||||
PopulateTargetList(state.Tags);
|
||||
_tagLabel.Text = state.Tag;
|
||||
TargetList = state.Tags;
|
||||
}
|
||||
|
||||
private void PopulateTargetList(List<string> tags)
|
||||
{
|
||||
TargetListContainer.Clear();
|
||||
foreach (var target in tags)
|
||||
{
|
||||
TargetListContainer.AddItem(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,7 +82,10 @@ namespace Content.Client.GameObjects.Components.HUD.Inventory
|
||||
|
||||
foreach (var (slot, entityUid) in cast.Entities)
|
||||
{
|
||||
var entity = Owner.EntityManager.GetEntity(entityUid);
|
||||
if (!Owner.EntityManager.TryGetEntity(entityUid, out var entity))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!_slots.ContainsKey(slot) || _slots[slot] != entity)
|
||||
{
|
||||
_slots[slot] = entity;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using Content.Shared.Utility;
|
||||
using Robust.Client.Graphics.Drawing;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
@@ -88,23 +89,27 @@ namespace Content.Client.GameObjects.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
level = 1 + (int) MathF.Round(charge * 6);
|
||||
level = ContentHelpers.RoundToNearestLevels(charge, 1.0, 6) + 1;
|
||||
}
|
||||
|
||||
if (level == 1)
|
||||
if (level == 0)
|
||||
{
|
||||
_sections[0].PanelOverride = _styleBoxUnlit;
|
||||
}
|
||||
else if (level == 1)
|
||||
{
|
||||
// Flash the last light.
|
||||
_sections[0].PanelOverride = _timer > TimerCycle / 2 ? _styleBoxLit : _styleBoxUnlit;
|
||||
}
|
||||
else
|
||||
{
|
||||
_sections[0].PanelOverride = level > 2 ? _styleBoxLit : _styleBoxUnlit;
|
||||
_sections[0].PanelOverride = _styleBoxLit;
|
||||
}
|
||||
|
||||
_sections[1].PanelOverride = level > 3 ? _styleBoxLit : _styleBoxUnlit;
|
||||
_sections[2].PanelOverride = level > 4 ? _styleBoxLit : _styleBoxUnlit;
|
||||
_sections[3].PanelOverride = level > 5 ? _styleBoxLit : _styleBoxUnlit;
|
||||
_sections[4].PanelOverride = level > 6 ? _styleBoxLit : _styleBoxUnlit;
|
||||
_sections[1].PanelOverride = level >= 3 ? _styleBoxLit : _styleBoxUnlit;
|
||||
_sections[2].PanelOverride = level >= 4 ? _styleBoxLit : _styleBoxUnlit;
|
||||
_sections[3].PanelOverride = level >= 5 ? _styleBoxLit : _styleBoxUnlit;
|
||||
_sections[4].PanelOverride = level >= 6 ? _styleBoxLit : _styleBoxUnlit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using Content.Shared.Physics;
|
||||
using Robust.Client.Audio.Midi;
|
||||
using Robust.Shared.Audio.Midi;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components.Timers;
|
||||
using Robust.Shared.Interfaces.Network;
|
||||
using Robust.Shared.Interfaces.Timing;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -41,6 +42,10 @@ namespace Content.Client.GameObjects.Components.Instruments
|
||||
|
||||
private uint _sequenceStartTick;
|
||||
|
||||
private bool _allowPercussion;
|
||||
|
||||
private bool _allowProgramChange;
|
||||
|
||||
/// <summary>
|
||||
/// A queue of MidiEvents to be sent to the server.
|
||||
/// </summary>
|
||||
@@ -67,7 +72,7 @@ namespace Content.Client.GameObjects.Components.Instruments
|
||||
/// Changes the instrument the midi renderer will play.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public byte InstrumentProgram
|
||||
public override byte InstrumentProgram
|
||||
{
|
||||
get => _instrumentProgram;
|
||||
set
|
||||
@@ -84,7 +89,7 @@ namespace Content.Client.GameObjects.Components.Instruments
|
||||
/// Changes the instrument bank the midi renderer will use.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public byte InstrumentBank
|
||||
public override byte InstrumentBank
|
||||
{
|
||||
get => _instrumentBank;
|
||||
set
|
||||
@@ -97,6 +102,34 @@ namespace Content.Client.GameObjects.Components.Instruments
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public override bool AllowPercussion
|
||||
{
|
||||
get => _allowPercussion;
|
||||
set
|
||||
{
|
||||
_allowPercussion = value;
|
||||
if (_renderer != null)
|
||||
{
|
||||
_renderer.DisablePercussionChannel = !_allowPercussion;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public override bool AllowProgramChange
|
||||
{
|
||||
get => _allowProgramChange;
|
||||
set
|
||||
{
|
||||
_allowProgramChange = value;
|
||||
if (_renderer != null)
|
||||
{
|
||||
_renderer.DisableProgramChangeEvent = !_allowProgramChange;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether this instrument is handheld or not.
|
||||
/// </summary>
|
||||
@@ -127,7 +160,7 @@ namespace Content.Client.GameObjects.Components.Instruments
|
||||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
|
||||
protected void SetupRenderer(bool fromStateChange = false)
|
||||
protected virtual void SetupRenderer(bool fromStateChange = false)
|
||||
{
|
||||
if (IsRendererAlive) return;
|
||||
|
||||
@@ -141,6 +174,8 @@ namespace Content.Client.GameObjects.Components.Instruments
|
||||
_renderer.MidiBank = _instrumentBank;
|
||||
_renderer.MidiProgram = _instrumentProgram;
|
||||
_renderer.TrackingEntity = Owner;
|
||||
_renderer.DisablePercussionChannel = !_allowPercussion;
|
||||
_renderer.DisableProgramChangeEvent = !_allowProgramChange;
|
||||
_renderer.OnMidiPlayerFinished += () =>
|
||||
{
|
||||
OnMidiPlaybackEnded?.Invoke();
|
||||
@@ -173,7 +208,7 @@ namespace Content.Client.GameObjects.Components.Instruments
|
||||
var renderer = _renderer;
|
||||
|
||||
// We dispose of the synth two seconds from now to allow the last notes to stop from playing.
|
||||
Timer.Spawn(2000, () => { renderer?.Dispose(); });
|
||||
Owner.SpawnTimer(2000, () => { renderer?.Dispose(); });
|
||||
_renderer = null;
|
||||
_midiEventBuffer.Clear();
|
||||
|
||||
@@ -195,6 +230,8 @@ namespace Content.Client.GameObjects.Components.Instruments
|
||||
serializer.DataField(this, x => Handheld, "handheld", false);
|
||||
serializer.DataField(ref _instrumentProgram, "program", (byte) 1);
|
||||
serializer.DataField(ref _instrumentBank, "bank", (byte) 0);
|
||||
serializer.DataField(ref _allowPercussion, "allowPercussion", false);
|
||||
serializer.DataField(ref _allowProgramChange, "allowProgramChange", false);
|
||||
}
|
||||
|
||||
public override void HandleNetworkMessage(ComponentMessage message, INetChannel channel, ICommonSession? session = null)
|
||||
@@ -279,6 +316,11 @@ namespace Content.Client.GameObjects.Components.Instruments
|
||||
{
|
||||
EndRenderer(true);
|
||||
}
|
||||
|
||||
AllowPercussion = state.AllowPercussion;
|
||||
AllowProgramChange = state.AllowProgramChange;
|
||||
InstrumentBank = state.InstrumentBank;
|
||||
InstrumentProgram = state.InstrumentProgram;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="MidiRenderer.OpenInput"/>
|
||||
|
||||
@@ -21,7 +21,6 @@ namespace Content.Client.GameObjects.Components.Items
|
||||
|
||||
private HandsGui? _gui;
|
||||
|
||||
/// <inheritdoc />
|
||||
private readonly List<Hand> _hands = new List<Hand>();
|
||||
|
||||
[ViewVariables] public IReadOnlyList<Hand> Hands => _hands;
|
||||
@@ -90,7 +89,7 @@ namespace Content.Client.GameObjects.Components.Items
|
||||
{
|
||||
if (!TryHand(sharedHand.Name, out var hand))
|
||||
{
|
||||
hand = new Hand(sharedHand, Owner.EntityManager);
|
||||
hand = new Hand(this, sharedHand, Owner.EntityManager);
|
||||
AddHand(hand);
|
||||
}
|
||||
else
|
||||
@@ -102,6 +101,8 @@ namespace Content.Client.GameObjects.Components.Items
|
||||
: null;
|
||||
}
|
||||
|
||||
hand.Enabled = sharedHand.Enabled;
|
||||
|
||||
UpdateHandSprites(hand);
|
||||
}
|
||||
|
||||
@@ -197,10 +198,35 @@ namespace Content.Client.GameObjects.Components.Items
|
||||
_gameHud.HandsContainer.AddChild(_gui);
|
||||
_gui.UpdateHandIcons();
|
||||
break;
|
||||
|
||||
case PlayerDetachedMsg _:
|
||||
_gui?.Parent?.RemoveChild(_gui);
|
||||
break;
|
||||
case HandEnabledMsg msg:
|
||||
{
|
||||
var hand = GetHand(msg.Name);
|
||||
|
||||
if (hand?.Button == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
hand.Button.Blocked.Visible = false;
|
||||
|
||||
break;
|
||||
}
|
||||
case HandDisabledMsg msg:
|
||||
{
|
||||
var hand = GetHand(msg.Name);
|
||||
|
||||
if (hand?.Button == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
hand.Button.Blocked.Visible = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,9 +261,11 @@ namespace Content.Client.GameObjects.Components.Items
|
||||
|
||||
public class Hand
|
||||
{
|
||||
// TODO: Separate into server hand and client hand
|
||||
public Hand(SharedHand hand, IEntityManager manager, HandButton? button = null)
|
||||
private bool _enabled = true;
|
||||
|
||||
public Hand(HandsComponent parent, SharedHand hand, IEntityManager manager, HandButton? button = null)
|
||||
{
|
||||
Parent = parent;
|
||||
Index = hand.Index;
|
||||
Name = hand.Name;
|
||||
Location = hand.Location;
|
||||
@@ -252,10 +280,33 @@ namespace Content.Client.GameObjects.Components.Items
|
||||
Entity = entity;
|
||||
}
|
||||
|
||||
private HandsComponent Parent { get; }
|
||||
public int Index { get; }
|
||||
public string Name { get; }
|
||||
public HandLocation Location { get; set; }
|
||||
public IEntity? Entity { get; set; }
|
||||
public HandButton? Button { get; set; }
|
||||
|
||||
public bool Enabled
|
||||
{
|
||||
get => _enabled;
|
||||
set
|
||||
{
|
||||
if (_enabled == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_enabled = value;
|
||||
Parent.Dirty();
|
||||
|
||||
var message = value
|
||||
? (ComponentMessage) new HandEnabledMsg(Name)
|
||||
: new HandDisabledMsg(Name);
|
||||
|
||||
Parent.HandleMessage(message, Parent);
|
||||
Parent.Owner.SendMessage(Parent, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ namespace Content.Client.GameObjects.Components.Items
|
||||
[ComponentReference(typeof(IItemComponent))]
|
||||
public class ItemComponent : Component, IItemComponent, IDraggable
|
||||
{
|
||||
[Dependency] private IResourceCache _resourceCache = default!;
|
||||
|
||||
public override string Name => "Item";
|
||||
public override uint? NetID => ContentNetIDs.ITEM;
|
||||
|
||||
@@ -72,8 +74,7 @@ namespace Content.Client.GameObjects.Components.Items
|
||||
|
||||
protected RSI GetRSI()
|
||||
{
|
||||
var resourceCache = IoCManager.Resolve<IResourceCache>();
|
||||
return resourceCache.GetResource<RSIResource>(SharedSpriteComponent.TextureRoot / RsiPath).RSI;
|
||||
return _resourceCache.GetResource<RSIResource>(SharedSpriteComponent.TextureRoot / RsiPath).RSI;
|
||||
}
|
||||
|
||||
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
|
||||
|
||||
@@ -35,6 +35,8 @@ namespace Content.Client.GameObjects.Components.Mobs
|
||||
[ViewVariables]
|
||||
private Dictionary<StatusEffect, CooldownGraphic> _cooldown = new Dictionary<StatusEffect, CooldownGraphic>();
|
||||
|
||||
public override IReadOnlyDictionary<StatusEffect, StatusEffectStatus> Statuses => _status;
|
||||
|
||||
/// <summary>
|
||||
/// Allows calculating if we need to act due to this component being controlled by the current mob
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
#nullable enable
|
||||
using Content.Shared.GameObjects.Components.Morgue;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Interfaces.GameObjects.Components;
|
||||
|
||||
namespace Content.Client.GameObjects.Components.Morgue
|
||||
{
|
||||
public sealed class BodyBagVisualizer : AppearanceVisualizer
|
||||
{
|
||||
public override void OnChangeData(AppearanceComponent component)
|
||||
{
|
||||
base.OnChangeData(component);
|
||||
|
||||
if (!component.Owner.TryGetComponent(out ISpriteComponent? sprite))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (component.TryGetData(BodyBagVisuals.Label, out bool labelVal))
|
||||
{
|
||||
sprite.LayerSetVisible(BodyBagVisualLayers.Label, labelVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum BodyBagVisualLayers
|
||||
{
|
||||
Label,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
#nullable enable
|
||||
using Content.Shared.GameObjects.Components.Morgue;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Interfaces.GameObjects.Components;
|
||||
using Robust.Shared.Utility;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Content.Client.GameObjects.Components.Storage
|
||||
{
|
||||
public sealed class CrematoriumVisualizer : AppearanceVisualizer
|
||||
{
|
||||
private string _stateOpen = "";
|
||||
private string _stateClosed = "";
|
||||
|
||||
private string _lightContents = "";
|
||||
private string _lightBurning = "";
|
||||
|
||||
public override void LoadData(YamlMappingNode node)
|
||||
{
|
||||
base.LoadData(node);
|
||||
|
||||
if (node.TryGetNode("state_open", out var child))
|
||||
{
|
||||
_stateOpen = child.AsString();
|
||||
}
|
||||
if (node.TryGetNode("state_closed", out child))
|
||||
{
|
||||
_stateClosed = child.AsString();
|
||||
}
|
||||
|
||||
if (node.TryGetNode("light_contents", out child))
|
||||
{
|
||||
_lightContents = child.AsString();
|
||||
}
|
||||
if (node.TryGetNode("light_burning", out child))
|
||||
{
|
||||
_lightBurning = child.AsString();
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnChangeData(AppearanceComponent component)
|
||||
{
|
||||
base.OnChangeData(component);
|
||||
|
||||
if (!component.Owner.TryGetComponent(out ISpriteComponent? sprite)) return;
|
||||
|
||||
sprite.LayerSetState(
|
||||
CrematoriumVisualLayers.Base,
|
||||
component.GetData<bool>(MorgueVisuals.Open)
|
||||
? _stateOpen
|
||||
: _stateClosed
|
||||
);
|
||||
|
||||
var lightState = "";
|
||||
if (component.TryGetData(MorgueVisuals.HasContents, out bool hasContents) && hasContents) lightState = _lightContents;
|
||||
if (component.TryGetData(CrematoriumVisuals.Burning, out bool isBurning) && isBurning) lightState = _lightBurning;
|
||||
|
||||
if (!string.IsNullOrEmpty(lightState))
|
||||
{
|
||||
sprite.LayerSetState(CrematoriumVisualLayers.Light, lightState);
|
||||
sprite.LayerSetVisible(CrematoriumVisualLayers.Light, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
sprite.LayerSetVisible(CrematoriumVisualLayers.Light, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum CrematoriumVisualLayers
|
||||
{
|
||||
Base,
|
||||
Light,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
#nullable enable
|
||||
using Content.Shared.GameObjects.Components.Morgue;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Interfaces.GameObjects.Components;
|
||||
using Robust.Shared.Utility;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Content.Client.GameObjects.Components.Storage
|
||||
{
|
||||
public sealed class MorgueVisualizer : AppearanceVisualizer
|
||||
{
|
||||
private string _stateOpen = "";
|
||||
private string _stateClosed = "";
|
||||
|
||||
private string _lightContents = "";
|
||||
private string _lightMob = "";
|
||||
private string _lightSoul = "";
|
||||
|
||||
public override void LoadData(YamlMappingNode node)
|
||||
{
|
||||
base.LoadData(node);
|
||||
|
||||
if (node.TryGetNode("state_open", out var child))
|
||||
{
|
||||
_stateOpen = child.AsString();
|
||||
}
|
||||
if (node.TryGetNode("state_closed", out child))
|
||||
{
|
||||
_stateClosed = child.AsString();
|
||||
}
|
||||
|
||||
if (node.TryGetNode("light_contents", out child))
|
||||
{
|
||||
_lightContents = child.AsString();
|
||||
}
|
||||
if (node.TryGetNode("light_mob", out child))
|
||||
{
|
||||
_lightMob = child.AsString();
|
||||
}
|
||||
if (node.TryGetNode("light_soul", out child))
|
||||
{
|
||||
_lightSoul = child.AsString();
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnChangeData(AppearanceComponent component)
|
||||
{
|
||||
base.OnChangeData(component);
|
||||
|
||||
if (!component.Owner.TryGetComponent(out ISpriteComponent? sprite)) return;
|
||||
|
||||
sprite.LayerSetState(
|
||||
MorgueVisualLayers.Base,
|
||||
component.GetData<bool>(MorgueVisuals.Open)
|
||||
? _stateOpen
|
||||
: _stateClosed
|
||||
);
|
||||
|
||||
var lightState = "";
|
||||
if (component.TryGetData(MorgueVisuals.HasContents, out bool hasContents) && hasContents) lightState = _lightContents;
|
||||
if (component.TryGetData(MorgueVisuals.HasMob, out bool hasMob) && hasMob) lightState = _lightMob;
|
||||
if (component.TryGetData(MorgueVisuals.HasSoul, out bool hasSoul) && hasSoul) lightState = _lightSoul;
|
||||
|
||||
if (!string.IsNullOrEmpty(lightState))
|
||||
{
|
||||
sprite.LayerSetState(MorgueVisualLayers.Light, lightState);
|
||||
sprite.LayerSetVisible(MorgueVisualLayers.Light, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
sprite.LayerSetVisible(MorgueVisualLayers.Light, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum MorgueVisualLayers
|
||||
{
|
||||
Base,
|
||||
Light,
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,11 @@ namespace Content.Client.GameObjects.Components.Nutrition
|
||||
|
||||
public override void OnChangeData(AppearanceComponent component)
|
||||
{
|
||||
var sprite = component.Owner.GetComponent<ISpriteComponent>();
|
||||
if(!component.Owner.TryGetComponent<ISpriteComponent>(out var sprite))
|
||||
{
|
||||
return;
|
||||
};
|
||||
|
||||
if (!component.TryGetData<float>(SharedFoodComponent.FoodVisuals.MaxUses, out var maxUses))
|
||||
{
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using Content.Shared.GameObjects.Components.Singularity;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Interfaces.GameObjects.Components;
|
||||
using Robust.Shared.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.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Content.Client.GameObjects.Components
|
||||
{
|
||||
public class ParticleAcceleratorPartVisualizer : AppearanceVisualizer
|
||||
{
|
||||
private Dictionary<ParticleAcceleratorVisualState, string> _states = new Dictionary<ParticleAcceleratorVisualState, string>();
|
||||
|
||||
public override void LoadData(YamlMappingNode node)
|
||||
{
|
||||
base.LoadData(node);
|
||||
|
||||
var serializer = YamlObjectSerializer.NewReader(node);
|
||||
if (!serializer.TryReadDataField<string>("baseState", out var baseState))
|
||||
{
|
||||
throw new PrototypeLoadException("No baseState property specified for ParticleAcceleratorPartVisualizer");
|
||||
}
|
||||
|
||||
_states.Add(ParticleAcceleratorVisualState.Powered, baseState+"p");
|
||||
_states.Add(ParticleAcceleratorVisualState.Level0, baseState+"p0");
|
||||
_states.Add(ParticleAcceleratorVisualState.Level1, baseState+"p1");
|
||||
_states.Add(ParticleAcceleratorVisualState.Level2, baseState+"p2");
|
||||
_states.Add(ParticleAcceleratorVisualState.Level3, baseState+"p3");
|
||||
}
|
||||
|
||||
public override void InitializeEntity(IEntity entity)
|
||||
{
|
||||
base.InitializeEntity(entity);
|
||||
if (!entity.TryGetComponent<SpriteComponent>(out var sprite))
|
||||
{
|
||||
throw new EntityCreationException("No sprite component found in entity that has ParticleAcceleratorPartVisualizer");
|
||||
}
|
||||
|
||||
if (!sprite.AllLayers.Any())
|
||||
{
|
||||
throw new EntityCreationException("No Layer set for entity that has ParticleAcceleratorPartVisualizer");
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnChangeData(AppearanceComponent component)
|
||||
{
|
||||
if (component.Owner.Deleted)
|
||||
return;
|
||||
|
||||
if (!component.Owner.TryGetComponent<ISpriteComponent>(out var sprite)) return;
|
||||
if (!component.TryGetData(ParticleAcceleratorVisuals.VisualState, out ParticleAcceleratorVisualState state))
|
||||
{
|
||||
state = ParticleAcceleratorVisualState.Unpowered;
|
||||
}
|
||||
|
||||
if (state != ParticleAcceleratorVisualState.Unpowered)
|
||||
{
|
||||
sprite.LayerSetVisible(ParticleAcceleratorVisualLayers.Unlit, true);
|
||||
sprite.LayerSetState(ParticleAcceleratorVisualLayers.Unlit, _states[state]);
|
||||
}
|
||||
else
|
||||
{
|
||||
sprite.LayerSetVisible(ParticleAcceleratorVisualLayers.Unlit, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,8 @@ namespace Content.Client.GameObjects.Components.Power
|
||||
var sprite = component.Owner.GetComponent<ISpriteComponent>();
|
||||
if (component.TryGetData(PowerCellVisuals.ChargeLevel, out float fraction))
|
||||
{
|
||||
sprite.LayerSetState(Layers.Charge, $"{_prefix}_{ContentHelpers.RoundToLevels(fraction, 1, 5) * 25}");
|
||||
int level = ContentHelpers.RoundToNearestLevels(fraction, 1, 4) * 25;
|
||||
sprite.LayerSetState(Layers.Charge, $"{_prefix}_{level}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
using System;
|
||||
using Content.Client.GameObjects.Components.Doors;
|
||||
using Content.Client.GameObjects.Components.Wires;
|
||||
using Content.Shared.GameObjects.Components.Doors;
|
||||
using Content.Shared.GameObjects.Components.Singularity;
|
||||
using Robust.Client.Animations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.GameObjects.Components.Animations;
|
||||
using Robust.Client.Interfaces.GameObjects.Components;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Content.Client.GameObjects.Components
|
||||
{
|
||||
public class RadiationCollectorVisualizer : AppearanceVisualizer
|
||||
{
|
||||
private const string AnimationKey = "radiationcollector_animation";
|
||||
|
||||
private Animation ActivateAnimation;
|
||||
private Animation DeactiveAnimation;
|
||||
|
||||
public override void LoadData(YamlMappingNode node)
|
||||
{
|
||||
ActivateAnimation = new Animation {Length = TimeSpan.FromSeconds(0.8f)};
|
||||
{
|
||||
var flick = new AnimationTrackSpriteFlick();
|
||||
ActivateAnimation.AnimationTracks.Add(flick);
|
||||
flick.LayerKey = RadiationCollectorVisualLayers.Main;
|
||||
flick.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("ca_active", 0f));
|
||||
|
||||
/*var sound = new AnimationTrackPlaySound();
|
||||
CloseAnimation.AnimationTracks.Add(sound);
|
||||
sound.KeyFrames.Add(new AnimationTrackPlaySound.KeyFrame(closeSound, 0));*/
|
||||
}
|
||||
|
||||
DeactiveAnimation = new Animation {Length = TimeSpan.FromSeconds(0.8f)};
|
||||
{
|
||||
var flick = new AnimationTrackSpriteFlick();
|
||||
DeactiveAnimation.AnimationTracks.Add(flick);
|
||||
flick.LayerKey = RadiationCollectorVisualLayers.Main;
|
||||
flick.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("ca_deactive", 0f));
|
||||
|
||||
/*var sound = new AnimationTrackPlaySound();
|
||||
CloseAnimation.AnimationTracks.Add(sound);
|
||||
sound.KeyFrames.Add(new AnimationTrackPlaySound.KeyFrame(closeSound, 0));*/
|
||||
}
|
||||
}
|
||||
|
||||
public override void InitializeEntity(IEntity entity)
|
||||
{
|
||||
if (!entity.HasComponent<AnimationPlayerComponent>())
|
||||
{
|
||||
entity.AddComponent<AnimationPlayerComponent>();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override void OnChangeData(AppearanceComponent component)
|
||||
{
|
||||
if (component.Owner.Deleted)
|
||||
return;
|
||||
|
||||
if (!component.Owner.TryGetComponent<ISpriteComponent>(out var sprite)) return;
|
||||
if (!component.Owner.TryGetComponent<AnimationPlayerComponent>(out var animPlayer)) return;
|
||||
if (!component.TryGetData(RadiationCollectorVisuals.VisualState, out RadiationCollectorVisualState state))
|
||||
{
|
||||
state = RadiationCollectorVisualState.Deactive;
|
||||
}
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case RadiationCollectorVisualState.Active:
|
||||
sprite.LayerSetState(RadiationCollectorVisualLayers.Main, "ca_on");
|
||||
break;
|
||||
case RadiationCollectorVisualState.Activating:
|
||||
if (!animPlayer.HasRunningAnimation(AnimationKey))
|
||||
{
|
||||
animPlayer.Play(ActivateAnimation, AnimationKey);
|
||||
animPlayer.AnimationCompleted += _ =>
|
||||
component.SetData(RadiationCollectorVisuals.VisualState,
|
||||
RadiationCollectorVisualState.Active);
|
||||
}
|
||||
break;
|
||||
case RadiationCollectorVisualState.Deactivating:
|
||||
if (!animPlayer.HasRunningAnimation(AnimationKey))
|
||||
{
|
||||
animPlayer.Play(DeactiveAnimation, AnimationKey);
|
||||
animPlayer.AnimationCompleted += _ =>
|
||||
component.SetData(RadiationCollectorVisuals.VisualState,
|
||||
RadiationCollectorVisualState.Deactive);
|
||||
}
|
||||
break;
|
||||
case RadiationCollectorVisualState.Deactive:
|
||||
sprite.LayerSetState(RadiationCollectorVisualLayers.Main, "ca_off");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
public enum RadiationCollectorVisualLayers
|
||||
{
|
||||
Main
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Interfaces.GameObjects.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Log;
|
||||
|
||||
namespace Content.Client.GameObjects.Components.Singularity
|
||||
{
|
||||
public class ContainmentFieldComponent : Component
|
||||
{
|
||||
public override string Name => "Containment Field";
|
||||
|
||||
private SpriteComponent _spriteComponent;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
if (!Owner.TryGetComponent(out _spriteComponent))
|
||||
{
|
||||
Logger.Error("Containmentfieldcomponent created without spritecomponent");
|
||||
}
|
||||
else
|
||||
{
|
||||
_spriteComponent.Directional = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using Content.Shared.GameObjects.Components.Singularity;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Interfaces.GameObjects.Components;
|
||||
|
||||
namespace Content.Client.GameObjects.Components.Singularity
|
||||
{
|
||||
public class EmitterVisualizer : AppearanceVisualizer
|
||||
{
|
||||
private const string OverlayBeam = "emitter-beam";
|
||||
private const string OverlayUnderPowered = "emitter-underpowered";
|
||||
|
||||
public override void OnChangeData(AppearanceComponent component)
|
||||
{
|
||||
base.OnChangeData(component);
|
||||
|
||||
if (!component.Owner.TryGetComponent(out ISpriteComponent sprite))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!component.TryGetData(EmitterVisuals.Locked, out bool locked))
|
||||
locked = false;
|
||||
|
||||
|
||||
if (!component.TryGetData(EmitterVisuals.VisualState, out EmitterVisualState state))
|
||||
state = EmitterVisualState.Off;
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case EmitterVisualState.On:
|
||||
sprite.LayerSetVisible(1, true);
|
||||
sprite.LayerSetState(1, OverlayBeam);
|
||||
break;
|
||||
case EmitterVisualState.Underpowered:
|
||||
sprite.LayerSetVisible(1, true);
|
||||
sprite.LayerSetState(1, OverlayUnderPowered);
|
||||
break;
|
||||
case EmitterVisualState.Off:
|
||||
sprite.LayerSetVisible(1, false);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
sprite.LayerSetVisible(2, locked);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Shared.GameObjects.Components.Sound;
|
||||
using Content.Shared.Physics;
|
||||
using Robust.Client.GameObjects.EntitySystems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components.Timers;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.Network;
|
||||
using Robust.Shared.Interfaces.Random;
|
||||
@@ -51,11 +54,19 @@ namespace Content.Client.GameObjects.Components.Sound
|
||||
{
|
||||
if (!schedule.Play) return;
|
||||
|
||||
Timer.Spawn((int) schedule.Delay + (_random.Next((int) schedule.RandomDelay)),() =>
|
||||
Owner.SpawnTimer((int) schedule.Delay + (_random.Next((int) schedule.RandomDelay)),() =>
|
||||
{
|
||||
if (!schedule.Play) return; // We make sure this hasn't changed.
|
||||
if (_audioSystem == null) _audioSystem = EntitySystem.Get<AudioSystem>();
|
||||
|
||||
if (!_audioStreams.ContainsKey(schedule))
|
||||
{
|
||||
_audioStreams.Add(schedule,_audioSystem.Play(schedule.Filename, Owner, schedule.AudioParams));
|
||||
}
|
||||
else
|
||||
{
|
||||
_audioStreams[schedule] = _audioSystem.Play(schedule.Filename, Owner, schedule.AudioParams);
|
||||
}
|
||||
|
||||
if (schedule.Times == 0) return;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Shared.GameObjects.Components.Storage;
|
||||
using Content.Shared.GameObjects.Components.Storage;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Interfaces.GameObjects.Components;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
@@ -74,12 +74,15 @@ namespace Content.Client.GameObjects.Components.Storage
|
||||
}
|
||||
}
|
||||
|
||||
if (component.TryGetData(StorageVisuals.CanWeld, out bool canWeld) && canWeld)
|
||||
{
|
||||
if (component.TryGetData(StorageVisuals.Welded, out bool weldedVal))
|
||||
{
|
||||
sprite.LayerSetVisible(StorageVisualLayers.Welded, weldedVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum StorageVisualLayers
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@ using Robust.Client.Interfaces.Graphics;
|
||||
using Robust.Client.Interfaces.Graphics.Overlays;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components.Timers;
|
||||
using Robust.Shared.Interfaces.Timing;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -83,7 +84,7 @@ namespace Content.Client.GameObjects.Components.Weapons
|
||||
}
|
||||
|
||||
_cancelToken = new CancellationTokenSource();
|
||||
Timer.Spawn((int) duration * 1000, DisableOverlay, _cancelToken.Token);
|
||||
Owner.SpawnTimer((int) duration * 1000, DisableOverlay, _cancelToken.Token);
|
||||
}
|
||||
|
||||
private void DisableOverlay()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Client.GameObjects.EntitySystems;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Client.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Interfaces.GameObjects.Components;
|
||||
@@ -34,14 +35,33 @@ namespace Content.Client.GameObjects.Components
|
||||
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new WindowSmoothDirtyEvent(Owner));
|
||||
|
||||
var state0 = $"{_stateBase}0";
|
||||
const string cracksRSIPath = "/Textures/Constructible/Structures/Windows/cracks.rsi";
|
||||
_sprite.LayerMapSet(CornerLayers.SE, _sprite.AddLayerState(state0));
|
||||
_sprite.LayerSetDirOffset(CornerLayers.SE, SpriteComponent.DirectionOffset.None);
|
||||
_sprite.LayerMapSet(WindowDamageLayers.DamageSE, _sprite.AddLayerState("0_1", cracksRSIPath));
|
||||
_sprite.LayerSetShader(WindowDamageLayers.DamageSE, "unshaded");
|
||||
_sprite.LayerSetVisible(WindowDamageLayers.DamageSE, false);
|
||||
|
||||
_sprite.LayerMapSet(CornerLayers.NE, _sprite.AddLayerState(state0));
|
||||
_sprite.LayerSetDirOffset(CornerLayers.NE, SpriteComponent.DirectionOffset.CounterClockwise);
|
||||
_sprite.LayerMapSet(WindowDamageLayers.DamageNE, _sprite.AddLayerState("0_1", cracksRSIPath));
|
||||
_sprite.LayerSetDirOffset(WindowDamageLayers.DamageNE, SpriteComponent.DirectionOffset.CounterClockwise);
|
||||
_sprite.LayerSetShader(WindowDamageLayers.DamageNE, "unshaded");
|
||||
_sprite.LayerSetVisible(WindowDamageLayers.DamageNE, false);
|
||||
|
||||
_sprite.LayerMapSet(CornerLayers.NW, _sprite.AddLayerState(state0));
|
||||
_sprite.LayerSetDirOffset(CornerLayers.NW, SpriteComponent.DirectionOffset.Flip);
|
||||
_sprite.LayerMapSet(WindowDamageLayers.DamageNW, _sprite.AddLayerState("0_1", cracksRSIPath));
|
||||
_sprite.LayerSetDirOffset(WindowDamageLayers.DamageNW, SpriteComponent.DirectionOffset.Flip);
|
||||
_sprite.LayerSetShader(WindowDamageLayers.DamageNW, "unshaded");
|
||||
_sprite.LayerSetVisible(WindowDamageLayers.DamageNW, false);
|
||||
|
||||
_sprite.LayerMapSet(CornerLayers.SW, _sprite.AddLayerState(state0));
|
||||
_sprite.LayerSetDirOffset(CornerLayers.SW, SpriteComponent.DirectionOffset.Clockwise);
|
||||
_sprite.LayerMapSet(WindowDamageLayers.DamageSW, _sprite.AddLayerState("0_1", cracksRSIPath));
|
||||
_sprite.LayerSetDirOffset(WindowDamageLayers.DamageSW, SpriteComponent.DirectionOffset.Clockwise);
|
||||
_sprite.LayerSetShader(WindowDamageLayers.DamageSW, "unshaded");
|
||||
_sprite.LayerSetVisible(WindowDamageLayers.DamageSW, false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -91,4 +111,13 @@ namespace Content.Client.GameObjects.Components
|
||||
serializer.DataField(ref _stateBase, "base", null);
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
public enum WindowDamageLayers
|
||||
{
|
||||
DamageSE,
|
||||
DamageNE,
|
||||
DamageNW,
|
||||
DamageSW
|
||||
}
|
||||
}
|
||||
|
||||
60
Content.Client/GameObjects/Components/WindowVisualizer.cs
Normal file
60
Content.Client/GameObjects/Components/WindowVisualizer.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using Content.Shared.Utility;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Interfaces.GameObjects.Components;
|
||||
using Robust.Shared.GameObjects.Components.Transform;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Client.GameObjects.Components
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class WindowVisualizer : AppearanceVisualizer
|
||||
{
|
||||
public override void OnChangeData(AppearanceComponent component)
|
||||
{
|
||||
base.OnChangeData(component);
|
||||
|
||||
var sprite = component.Owner.GetComponent<ISpriteComponent>();
|
||||
var snapGrid = component.Owner.GetComponent<SnapGridComponent>();
|
||||
var lowWall = FindLowWall(snapGrid);
|
||||
if (lowWall == null) return;
|
||||
|
||||
if (component.TryGetData(WindowVisuals.Damage, out float fraction))
|
||||
{
|
||||
var level = Math.Min(ContentHelpers.RoundToLevels(fraction, 1, 7), 5);
|
||||
if (level == 0)
|
||||
{
|
||||
foreach (WindowDamageLayers val in Enum.GetValues(typeof(WindowDamageLayers)))
|
||||
{
|
||||
sprite.LayerSetVisible(val, false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
foreach (WindowDamageLayers val in Enum.GetValues(typeof(WindowDamageLayers)))
|
||||
{
|
||||
sprite.LayerSetVisible(val, true);
|
||||
}
|
||||
|
||||
sprite.LayerSetState(WindowDamageLayers.DamageNE, $"{(int) lowWall.LastCornerNE}_{level}");
|
||||
sprite.LayerSetState(WindowDamageLayers.DamageSE, $"{(int) lowWall.LastCornerSE}_{level}");
|
||||
sprite.LayerSetState(WindowDamageLayers.DamageSW, $"{(int) lowWall.LastCornerSW}_{level}");
|
||||
sprite.LayerSetState(WindowDamageLayers.DamageNW, $"{(int) lowWall.LastCornerNW}_{level}");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static LowWallComponent FindLowWall(SnapGridComponent snapGrid)
|
||||
{
|
||||
foreach (var entity in snapGrid.GetLocal())
|
||||
{
|
||||
if (entity.TryGetComponent(out LowWallComponent lowWall))
|
||||
{
|
||||
return lowWall;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,8 @@ namespace Content.Client.GameObjects.Components.Wires
|
||||
{
|
||||
public class WiresMenu : BaseWindow
|
||||
{
|
||||
[Dependency] private IResourceCache _resourceCache = default!;
|
||||
|
||||
public WiresBoundUserInterface Owner { get; }
|
||||
|
||||
private readonly Control _wiresHBox;
|
||||
@@ -34,7 +36,7 @@ namespace Content.Client.GameObjects.Components.Wires
|
||||
|
||||
public WiresMenu(WiresBoundUserInterface owner)
|
||||
{
|
||||
var resourceCache = IoCManager.Resolve<IResourceCache>();
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
Owner = owner;
|
||||
var rootContainer = new LayoutContainer {Name = "WireRoot"};
|
||||
@@ -42,7 +44,7 @@ namespace Content.Client.GameObjects.Components.Wires
|
||||
|
||||
MouseFilter = MouseFilterMode.Stop;
|
||||
|
||||
var panelTex = resourceCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png");
|
||||
var panelTex = _resourceCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png");
|
||||
var back = new StyleBoxTexture
|
||||
{
|
||||
Texture = panelTex,
|
||||
@@ -135,8 +137,8 @@ namespace Content.Client.GameObjects.Components.Wires
|
||||
|
||||
LayoutContainer.SetAnchorPreset(topContainerWrap, LayoutContainer.LayoutPreset.Wide);
|
||||
|
||||
var font = resourceCache.GetFont("/Fonts/Boxfont-round/Boxfont Round.ttf", 13);
|
||||
var fontSmall = resourceCache.GetFont("/Fonts/Boxfont-round/Boxfont Round.ttf", 10);
|
||||
var font = _resourceCache.GetFont("/Fonts/Boxfont-round/Boxfont Round.ttf", 13);
|
||||
var fontSmall = _resourceCache.GetFont("/Fonts/Boxfont-round/Boxfont Round.ttf", 10);
|
||||
|
||||
Button helpButton;
|
||||
var topRow = new MarginContainer
|
||||
@@ -255,7 +257,7 @@ namespace Content.Client.GameObjects.Components.Wires
|
||||
var mirror = random.Next(2) == 0;
|
||||
var flip = random.Next(2) == 0;
|
||||
var type = random.Next(2);
|
||||
var control = new WireControl(wire.Color, wire.Letter, wire.IsCut, flip, mirror, type)
|
||||
var control = new WireControl(wire.Color, wire.Letter, wire.IsCut, flip, mirror, type, _resourceCache)
|
||||
{
|
||||
SizeFlagsVertical = SizeFlags.ShrinkEnd
|
||||
};
|
||||
@@ -278,7 +280,7 @@ namespace Content.Client.GameObjects.Components.Wires
|
||||
{
|
||||
if (status.Value is StatusLightData statusLightData)
|
||||
{
|
||||
_statusContainer.AddChild(new StatusLight(statusLightData));
|
||||
_statusContainer.AddChild(new StatusLight(statusLightData, _resourceCache));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -305,18 +307,20 @@ namespace Content.Client.GameObjects.Components.Wires
|
||||
|
||||
private sealed class WireControl : Control
|
||||
{
|
||||
private IResourceCache _resourceCache;
|
||||
|
||||
private const string TextureContact = "/Textures/Interface/WireHacking/contact.svg.96dpi.png";
|
||||
|
||||
public event Action WireClicked;
|
||||
public event Action ContactsClicked;
|
||||
|
||||
public WireControl(WireColor color, WireLetter letter, bool isCut, bool flip, bool mirror, int type)
|
||||
public WireControl(WireColor color, WireLetter letter, bool isCut, bool flip, bool mirror, int type, IResourceCache resourceCache)
|
||||
{
|
||||
_resourceCache = resourceCache;
|
||||
|
||||
SizeFlagsHorizontal = SizeFlags.ShrinkCenter;
|
||||
MouseFilter = MouseFilterMode.Stop;
|
||||
|
||||
var resourceCache = IoCManager.Resolve<IResourceCache>();
|
||||
|
||||
var layout = new LayoutContainer();
|
||||
AddChild(layout);
|
||||
|
||||
@@ -326,7 +330,7 @@ namespace Content.Client.GameObjects.Components.Wires
|
||||
SizeFlagsVertical = SizeFlags.ShrinkEnd,
|
||||
SizeFlagsHorizontal = SizeFlags.ShrinkCenter,
|
||||
Align = Label.AlignMode.Center,
|
||||
FontOverride = resourceCache.GetFont("/Fonts/NotoSansDisplay/NotoSansDisplay-Bold.ttf", 12),
|
||||
FontOverride = _resourceCache.GetFont("/Fonts/NotoSansDisplay/NotoSansDisplay-Bold.ttf", 12),
|
||||
FontColorOverride = Color.Gray,
|
||||
ToolTip = letter.Name(),
|
||||
MouseFilter = MouseFilterMode.Stop
|
||||
@@ -337,7 +341,7 @@ namespace Content.Client.GameObjects.Components.Wires
|
||||
LayoutContainer.SetGrowVertical(greek, LayoutContainer.GrowDirection.Begin);
|
||||
LayoutContainer.SetGrowHorizontal(greek, LayoutContainer.GrowDirection.Both);
|
||||
|
||||
var contactTexture = resourceCache.GetTexture(TextureContact);
|
||||
var contactTexture = _resourceCache.GetTexture(TextureContact);
|
||||
var contact1 = new TextureRect
|
||||
{
|
||||
Texture = contactTexture,
|
||||
@@ -356,7 +360,7 @@ namespace Content.Client.GameObjects.Components.Wires
|
||||
layout.AddChild(contact2);
|
||||
LayoutContainer.SetPosition(contact2, (0, 60));
|
||||
|
||||
var wire = new WireRender(color, isCut, flip, mirror, type);
|
||||
var wire = new WireRender(color, isCut, flip, mirror, type, _resourceCache);
|
||||
|
||||
layout.AddChild(wire);
|
||||
LayoutContainer.SetPosition(wire, (2, 16));
|
||||
@@ -420,8 +424,11 @@ namespace Content.Client.GameObjects.Components.Wires
|
||||
"/Textures/Interface/WireHacking/wire_2_copper.svg.96dpi.png"
|
||||
};
|
||||
|
||||
public WireRender(WireColor color, bool isCut, bool flip, bool mirror, int type)
|
||||
private IResourceCache _resourceCache;
|
||||
|
||||
public WireRender(WireColor color, bool isCut, bool flip, bool mirror, int type, IResourceCache resourceCache)
|
||||
{
|
||||
_resourceCache = resourceCache;
|
||||
_color = color;
|
||||
_isCut = isCut;
|
||||
_flip = flip;
|
||||
@@ -436,10 +443,8 @@ namespace Content.Client.GameObjects.Components.Wires
|
||||
|
||||
protected override void Draw(DrawingHandleScreen handle)
|
||||
{
|
||||
var resC = IoCManager.Resolve<IResourceCache>();
|
||||
|
||||
var colorValue = _color.ColorValue();
|
||||
var tex = resC.GetTexture(_isCut ? TextureCut[_type] : TextureNormal[_type]);
|
||||
var tex = _resourceCache.GetTexture(_isCut ? TextureCut[_type] : TextureNormal[_type]);
|
||||
|
||||
var l = 0f;
|
||||
var r = tex.Width + l;
|
||||
@@ -465,7 +470,7 @@ namespace Content.Client.GameObjects.Components.Wires
|
||||
if (_isCut)
|
||||
{
|
||||
var copper = Color.Orange;
|
||||
var copperTex = resC.GetTexture(TextureCopper[_type]);
|
||||
var copperTex = _resourceCache.GetTexture(TextureCopper[_type]);
|
||||
handle.DrawTextureRect(copperTex, rect, copper);
|
||||
}
|
||||
|
||||
@@ -516,9 +521,8 @@ namespace Content.Client.GameObjects.Components.Wires
|
||||
}
|
||||
};
|
||||
|
||||
public StatusLight(StatusLightData data)
|
||||
public StatusLight(StatusLightData data, IResourceCache resourceCache)
|
||||
{
|
||||
var resC = IoCManager.Resolve<IResourceCache>();
|
||||
var hsv = Color.ToHsv(data.Color);
|
||||
hsv.Z /= 2;
|
||||
var dimColor = Color.FromHsv(hsv);
|
||||
@@ -530,7 +534,7 @@ namespace Content.Client.GameObjects.Components.Wires
|
||||
{
|
||||
new TextureRect
|
||||
{
|
||||
Texture = resC.GetTexture(
|
||||
Texture = resourceCache.GetTexture(
|
||||
"/Textures/Interface/WireHacking/light_off_base.svg.96dpi.png"),
|
||||
Stretch = TextureRect.StretchMode.KeepCentered,
|
||||
ModulateSelfOverride = dimColor
|
||||
@@ -540,7 +544,7 @@ namespace Content.Client.GameObjects.Components.Wires
|
||||
ModulateSelfOverride = data.Color.WithAlpha(0.4f),
|
||||
Stretch = TextureRect.StretchMode.KeepCentered,
|
||||
Texture =
|
||||
resC.GetTexture("/Textures/Interface/WireHacking/light_on_base.svg.96dpi.png"),
|
||||
resourceCache.GetTexture("/Textures/Interface/WireHacking/light_on_base.svg.96dpi.png"),
|
||||
})
|
||||
}
|
||||
};
|
||||
@@ -577,7 +581,7 @@ namespace Content.Client.GameObjects.Components.Wires
|
||||
};
|
||||
}
|
||||
|
||||
var font = IoCManager.Resolve<IResourceCache>().GetFont("/Fonts/Boxfont-round/Boxfont Round.ttf", 12);
|
||||
var font = resourceCache.GetFont("/Fonts/Boxfont-round/Boxfont Round.ttf", 12);
|
||||
|
||||
var hBox = new HBoxContainer {SeparationOverride = 4};
|
||||
hBox.AddChild(new Label
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#nullable enable
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Client.GameObjects.Components;
|
||||
@@ -124,9 +124,7 @@ namespace Content.Client.GameObjects.EntitySystems.DoAfter
|
||||
|
||||
if (doAfter.BreakOnTargetMove)
|
||||
{
|
||||
var targetEntity = _entityManager.GetEntity(doAfter.TargetUid);
|
||||
|
||||
if (targetEntity.Transform.Coordinates != doAfter.TargetGrid)
|
||||
if (_entityManager.TryGetEntity(doAfter.TargetUid, out var targetEntity) && targetEntity.Transform.Coordinates != doAfter.TargetGrid)
|
||||
{
|
||||
comp.Cancel(id, currentTime);
|
||||
continue;
|
||||
|
||||
@@ -6,6 +6,7 @@ using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Interfaces.GameObjects.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components.Timers;
|
||||
using Robust.Shared.GameObjects.EntitySystemMessages;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.Timing;
|
||||
@@ -95,7 +96,7 @@ namespace Content.Client.GameObjects.EntitySystems
|
||||
var newColor = Color.Red * originalColor;
|
||||
sprite.Color = newColor;
|
||||
|
||||
Timer.Spawn(100, () =>
|
||||
hitEntity.SpawnTimer(100, () =>
|
||||
{
|
||||
// Only reset back to the original color if something else didn't change the color in the mean time.
|
||||
if (sprite.Color == newColor)
|
||||
|
||||
@@ -24,12 +24,6 @@ namespace Content.Client.GameObjects.EntitySystems
|
||||
appearance.SetData(RotationVisuals.RotationState, newState);
|
||||
}
|
||||
|
||||
if (playSound)
|
||||
{
|
||||
var file = AudioHelpers.GetRandomFileFromSoundCollection("bodyfall");
|
||||
Get<AudioSystem>().Play(file, entity, AudioHelpers.WithVariation(0.25f));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
"Door",
|
||||
"PoweredLight",
|
||||
"Smes",
|
||||
"Powercell",
|
||||
"LightBulb",
|
||||
"Healing",
|
||||
"Catwalk",
|
||||
@@ -42,6 +41,7 @@
|
||||
"HitscanWeaponCapacitor",
|
||||
"PowerCell",
|
||||
"PowerCellCharger",
|
||||
"PowerCellSlot",
|
||||
"WeaponCapacitorCharger",
|
||||
"AiController",
|
||||
"Computer",
|
||||
@@ -182,6 +182,7 @@
|
||||
"GasCanisterPort",
|
||||
"Lung",
|
||||
"Cleanable",
|
||||
"Configuration",
|
||||
"Brain",
|
||||
"PlantHolder",
|
||||
"SeedExtractor",
|
||||
@@ -190,7 +191,27 @@
|
||||
"Hoe",
|
||||
"Seed",
|
||||
"BotanySharp",
|
||||
"PlantSampleTaker"
|
||||
"PlantSampleTaker",
|
||||
"Internals",
|
||||
"GasTank",
|
||||
"BreathMask",
|
||||
"RadiationCollector",
|
||||
"ContainmentFieldGenerator",
|
||||
"ContainmentField",
|
||||
"Emitter",
|
||||
"Singularity",
|
||||
"SingularityGenerator",
|
||||
"EmitterBoltComponent",
|
||||
"ParticleProjectile",
|
||||
"ParticleAcceleratorControlBox",
|
||||
"ParticleAcceleratorEmitter",
|
||||
"ParticleAcceleratorEndCap",
|
||||
"ParticleAcceleratorFuelChamber",
|
||||
"ParticleAcceleratorPowerBox",
|
||||
"BodyBagEntityStorage",
|
||||
"MorgueEntityStorage",
|
||||
"MorgueTray",
|
||||
"CrematoriumEntityStorage",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Client.GameObjects.Components.Instruments;
|
||||
using Content.Client.UserInterface.Stylesheets;
|
||||
using Content.Client.Utility;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Robust.Client.Audio.Midi;
|
||||
using Robust.Client.Graphics.Drawing;
|
||||
using Robust.Client.Interfaces.UserInterface;
|
||||
@@ -176,34 +177,20 @@ namespace Content.Client.Instruments
|
||||
var filters = new FileDialogFilters(new FileDialogFilters.Group("mid", "midi"));
|
||||
var filename = await _fileDialogManager.OpenFile(filters);
|
||||
|
||||
var instrumentEnt = _owner.Instrument.Owner;
|
||||
var instrument = _owner.Instrument;
|
||||
|
||||
ContainerHelpers.TryGetContainerMan(_owner.Instrument.Owner, out var conMan);
|
||||
|
||||
var localPlayer = IoCManager.Resolve<IPlayerManager>().LocalPlayer;
|
||||
|
||||
// The following checks are only in place to prevent players from playing MIDI songs locally.
|
||||
// There are equivalents for these checks on the server.
|
||||
|
||||
if (string.IsNullOrEmpty(filename)) return;
|
||||
|
||||
// If we don't have a player or controlled entity, we return.
|
||||
if(localPlayer?.ControlledEntity == null) return;
|
||||
|
||||
// If the instrument is handheld and we're not holding it, we return.
|
||||
if((instrument.Handheld && (conMan == null
|
||||
|| conMan.Owner != localPlayer.ControlledEntity))) return;
|
||||
|
||||
// We check that we're in range unobstructed just in case.
|
||||
if (!localPlayer.InRangeUnobstructed(instrumentEnt)) return;
|
||||
|
||||
if (!_midiManager.IsMidiFile(filename))
|
||||
{
|
||||
Logger.Warning($"Not a midi file! Chosen file: {filename}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!PlayCheck())
|
||||
return;
|
||||
|
||||
MidiStopButtonOnPressed(null);
|
||||
await Timer.Delay(100);
|
||||
if (!_owner.Instrument.OpenMidi(filename)) return;
|
||||
@@ -216,6 +203,9 @@ namespace Content.Client.Instruments
|
||||
{
|
||||
if (obj.Pressed)
|
||||
{
|
||||
if (!PlayCheck())
|
||||
return;
|
||||
|
||||
MidiStopButtonOnPressed(null);
|
||||
_owner.Instrument.OpenInput();
|
||||
}
|
||||
@@ -223,6 +213,26 @@ namespace Content.Client.Instruments
|
||||
_owner.Instrument.CloseInput();
|
||||
}
|
||||
|
||||
private bool PlayCheck()
|
||||
{
|
||||
var instrumentEnt = _owner.Instrument.Owner;
|
||||
var instrument = _owner.Instrument;
|
||||
|
||||
ContainerHelpers.TryGetContainerMan(_owner.Instrument.Owner, out var conMan);
|
||||
|
||||
var localPlayer = IoCManager.Resolve<IPlayerManager>().LocalPlayer;
|
||||
|
||||
// If we don't have a player or controlled entity, we return.
|
||||
if(localPlayer?.ControlledEntity == null) return false;
|
||||
|
||||
// If the instrument is handheld and we're not holding it, we return.
|
||||
if((instrument.Handheld && (conMan == null
|
||||
|| conMan.Owner != localPlayer.ControlledEntity))) return false;
|
||||
|
||||
// We check that we're in range unobstructed just in case.
|
||||
return localPlayer.InRangeUnobstructed(instrumentEnt, predicate:(e) => e == instrumentEnt || e == localPlayer.ControlledEntity);
|
||||
}
|
||||
|
||||
private void MidiStopButtonOnPressed(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
MidiPlaybackSetButtonsDisabled(true);
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace Content.Client.UserInterface.AdminMenu
|
||||
{
|
||||
new KickCommandButton(),
|
||||
new DirectCommandButton("Admin Ghost", "aghost"),
|
||||
//TODO: teleport
|
||||
new TeleportCommandButton(),
|
||||
};
|
||||
private readonly List<CommandButton> _adminbusButtons = new List<CommandButton>
|
||||
{
|
||||
@@ -534,6 +534,30 @@ namespace Content.Client.UserInterface.AdminMenu
|
||||
}
|
||||
}
|
||||
|
||||
private class TeleportCommandButton : UICommandButton
|
||||
{
|
||||
public override string Name => "Teleport";
|
||||
public override string RequiredCommand => "tpto";
|
||||
|
||||
private readonly CommandUIDropDown _playerDropDown = new CommandUIDropDown
|
||||
{
|
||||
Name = "Player",
|
||||
GetData = () => IoCManager.Resolve<IPlayerManager>().Sessions.ToList<object>(),
|
||||
GetDisplayName = (obj) => $"{((IPlayerSession) obj).Name} ({((IPlayerSession) obj).AttachedEntity?.Name})",
|
||||
GetValueFromData = (obj) => ((IPlayerSession) obj).Name,
|
||||
};
|
||||
|
||||
public override List<CommandUIControl> UI => new List<CommandUIControl>
|
||||
{
|
||||
_playerDropDown
|
||||
};
|
||||
|
||||
public override void Submit()
|
||||
{
|
||||
IoCManager.Resolve<IClientConsole>().ProcessCommand($"tpto \"{_playerDropDown.GetValue()}\"");
|
||||
}
|
||||
}
|
||||
|
||||
private class AddAtmosCommandButton : UICommandButton
|
||||
{
|
||||
public override string Name => "Add Atmos";
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
using Content.Shared.GameObjects.Components.Atmos.GasTank;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects.Components.UserInterface;
|
||||
using Robust.Shared.GameObjects.Components.UserInterface;
|
||||
|
||||
namespace Content.Client.UserInterface.Atmos.GasTank
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class GasTankBoundUserInterface
|
||||
: BoundUserInterface
|
||||
{
|
||||
public GasTankBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) :
|
||||
base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
|
||||
private GasTankWindow _window;
|
||||
|
||||
public void SetOutputPressure(in float value)
|
||||
{
|
||||
SendMessage(new GasTankSetPressureMessage {Pressure = value});
|
||||
}
|
||||
|
||||
public void ToggleInternals()
|
||||
{
|
||||
SendMessage(new GasTankToggleInternalsMessage());
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
_window = new GasTankWindow(this);
|
||||
_window.OnClose += Close;
|
||||
_window.OpenCentered();
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
_window.UpdateState((GasTankBoundUserInterfaceState) state);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
_window.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
235
Content.Client/UserInterface/Atmos/GasTank/GasTankWindow.cs
Normal file
235
Content.Client/UserInterface/Atmos/GasTank/GasTankWindow.cs
Normal file
@@ -0,0 +1,235 @@
|
||||
using Content.Client.UserInterface.Stylesheets;
|
||||
using Content.Client.Utility;
|
||||
using Content.Shared.GameObjects.Components.Atmos.GasTank;
|
||||
using Robust.Client.Graphics.Drawing;
|
||||
using Robust.Client.Interfaces.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Content.Client.UserInterface.Atmos.GasTank
|
||||
{
|
||||
public class GasTankWindow
|
||||
: BaseWindow
|
||||
{
|
||||
private GasTankBoundUserInterface _owner;
|
||||
private readonly Label _lblName;
|
||||
private readonly VBoxContainer _topContainer;
|
||||
private readonly Control _contentContainer;
|
||||
|
||||
|
||||
private readonly IResourceCache _resourceCache = default!;
|
||||
private readonly RichTextLabel _lblPressure;
|
||||
private readonly FloatSpinBox _spbPressure;
|
||||
private readonly RichTextLabel _lblInternals;
|
||||
private readonly Button _btnInternals;
|
||||
|
||||
public GasTankWindow(GasTankBoundUserInterface owner)
|
||||
{
|
||||
TextureButton btnClose;
|
||||
_resourceCache = IoCManager.Resolve<IResourceCache>();
|
||||
_owner = owner;
|
||||
var rootContainer = new LayoutContainer {Name = "GasTankRoot"};
|
||||
AddChild(rootContainer);
|
||||
|
||||
MouseFilter = MouseFilterMode.Stop;
|
||||
|
||||
var panelTex = _resourceCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png");
|
||||
var back = new StyleBoxTexture
|
||||
{
|
||||
Texture = panelTex,
|
||||
Modulate = Color.FromHex("#25252A"),
|
||||
};
|
||||
|
||||
back.SetPatchMargin(StyleBox.Margin.All, 10);
|
||||
|
||||
var topPanel = new PanelContainer
|
||||
{
|
||||
PanelOverride = back,
|
||||
MouseFilter = MouseFilterMode.Pass
|
||||
};
|
||||
|
||||
var bottomWrap = new LayoutContainer
|
||||
{
|
||||
Name = "BottomWrap"
|
||||
};
|
||||
|
||||
rootContainer.AddChild(topPanel);
|
||||
rootContainer.AddChild(bottomWrap);
|
||||
|
||||
LayoutContainer.SetAnchorPreset(topPanel, LayoutContainer.LayoutPreset.Wide);
|
||||
LayoutContainer.SetMarginBottom(topPanel, -85);
|
||||
|
||||
LayoutContainer.SetAnchorPreset(bottomWrap, LayoutContainer.LayoutPreset.VerticalCenterWide);
|
||||
LayoutContainer.SetGrowHorizontal(bottomWrap, LayoutContainer.GrowDirection.Both);
|
||||
|
||||
|
||||
var topContainerWrap = new VBoxContainer
|
||||
{
|
||||
Children =
|
||||
{
|
||||
(_topContainer = new VBoxContainer()),
|
||||
new Control {CustomMinimumSize = (0, 110)}
|
||||
}
|
||||
};
|
||||
|
||||
rootContainer.AddChild(topContainerWrap);
|
||||
|
||||
LayoutContainer.SetAnchorPreset(topContainerWrap, LayoutContainer.LayoutPreset.Wide);
|
||||
|
||||
var font = _resourceCache.GetFont("/Fonts/Boxfont-round/Boxfont Round.ttf", 13);
|
||||
|
||||
var topRow = new MarginContainer
|
||||
{
|
||||
MarginLeftOverride = 4,
|
||||
MarginTopOverride = 2,
|
||||
MarginRightOverride = 12,
|
||||
MarginBottomOverride = 2,
|
||||
Children =
|
||||
{
|
||||
new HBoxContainer
|
||||
{
|
||||
Children =
|
||||
{
|
||||
(_lblName = new Label
|
||||
{
|
||||
Text = Loc.GetString("Gas Tank"),
|
||||
FontOverride = font,
|
||||
FontColorOverride = StyleNano.NanoGold,
|
||||
SizeFlagsVertical = SizeFlags.ShrinkCenter
|
||||
}),
|
||||
new Control
|
||||
{
|
||||
CustomMinimumSize = (20, 0),
|
||||
SizeFlagsHorizontal = SizeFlags.Expand
|
||||
},
|
||||
(btnClose = new TextureButton
|
||||
{
|
||||
StyleClasses = {SS14Window.StyleClassWindowCloseButton},
|
||||
SizeFlagsVertical = SizeFlags.ShrinkCenter
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var middle = new PanelContainer
|
||||
{
|
||||
PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#202025")},
|
||||
Children =
|
||||
{
|
||||
new MarginContainer
|
||||
{
|
||||
MarginLeftOverride = 8,
|
||||
MarginRightOverride = 8,
|
||||
MarginTopOverride = 4,
|
||||
MarginBottomOverride = 4,
|
||||
Children =
|
||||
{
|
||||
(_contentContainer = new VBoxContainer())
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_topContainer.AddChild(topRow);
|
||||
_topContainer.AddChild(new PanelContainer
|
||||
{
|
||||
CustomMinimumSize = (0, 2),
|
||||
PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#525252ff")}
|
||||
});
|
||||
_topContainer.AddChild(middle);
|
||||
_topContainer.AddChild(new PanelContainer
|
||||
{
|
||||
CustomMinimumSize = (0, 2),
|
||||
PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#525252ff")}
|
||||
});
|
||||
|
||||
|
||||
_lblPressure = new RichTextLabel();
|
||||
_contentContainer.AddChild(_lblPressure);
|
||||
|
||||
//internals
|
||||
_lblInternals = new RichTextLabel
|
||||
{CustomMinimumSize = (200, 0), SizeFlagsVertical = SizeFlags.ShrinkCenter};
|
||||
_btnInternals = new Button {Text = Loc.GetString("Toggle")};
|
||||
|
||||
_contentContainer.AddChild(
|
||||
new MarginContainer
|
||||
{
|
||||
MarginTopOverride = 7,
|
||||
Children =
|
||||
{
|
||||
new HBoxContainer
|
||||
{
|
||||
Children = {_lblInternals, _btnInternals}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Separator
|
||||
_contentContainer.AddChild(new Control
|
||||
{
|
||||
CustomMinimumSize = new Vector2(0, 10)
|
||||
});
|
||||
|
||||
_contentContainer.AddChild(new Label
|
||||
{
|
||||
Text = Loc.GetString("Output Pressure"),
|
||||
Align = Label.AlignMode.Center
|
||||
});
|
||||
_spbPressure = new FloatSpinBox {IsValid = f => f >= 0 || f <= 3000};
|
||||
_contentContainer.AddChild(
|
||||
new MarginContainer
|
||||
{
|
||||
MarginRightOverride = 25,
|
||||
MarginLeftOverride = 25,
|
||||
MarginBottomOverride = 7,
|
||||
Children =
|
||||
{
|
||||
_spbPressure
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Handlers
|
||||
_spbPressure.OnValueChanged += args =>
|
||||
{
|
||||
_owner.SetOutputPressure(args.Value);
|
||||
};
|
||||
|
||||
_btnInternals.OnPressed += args =>
|
||||
{
|
||||
_owner.ToggleInternals();
|
||||
};
|
||||
|
||||
btnClose.OnPressed += _ => Close();
|
||||
}
|
||||
|
||||
public void UpdateState(GasTankBoundUserInterfaceState state)
|
||||
{
|
||||
_lblPressure.SetMarkup(Loc.GetString("Pressure: {0:0.##} kPa", state.TankPressure));
|
||||
_btnInternals.Disabled = !state.CanConnectInternals;
|
||||
_lblInternals.SetMarkup(Loc.GetString("Internals: [color={0}]{1}[/color]",
|
||||
state.InternalsConnected ? "green" : "red",
|
||||
state.InternalsConnected ? "Connected" : "Disconnected"));
|
||||
if (state.OutputPressure.HasValue)
|
||||
{
|
||||
_spbPressure.Value = state.OutputPressure.Value;
|
||||
}
|
||||
}
|
||||
|
||||
protected override DragMode GetDragModeFor(Vector2 relativeMousePos)
|
||||
{
|
||||
return DragMode.Move;
|
||||
}
|
||||
|
||||
protected override bool HasPoint(Vector2 point)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,25 @@
|
||||
using Content.Shared.GameObjects.Components.Items;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
|
||||
namespace Content.Client.UserInterface
|
||||
{
|
||||
public class HandButton : ItemSlotButton
|
||||
{
|
||||
public HandButton(Texture texture, Texture storageTexture, HandLocation location) : base(texture, storageTexture)
|
||||
public HandButton(Texture texture, Texture storageTexture, Texture blockedTexture, HandLocation location) : base(texture, storageTexture)
|
||||
{
|
||||
Location = location;
|
||||
|
||||
AddChild(Blocked = new TextureRect
|
||||
{
|
||||
Texture = blockedTexture,
|
||||
TextureScale = (2, 2),
|
||||
MouseFilter = MouseFilterMode.Stop,
|
||||
Visible = false
|
||||
});
|
||||
}
|
||||
|
||||
public HandLocation Location { get; }
|
||||
public TextureRect Blocked { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +111,8 @@ namespace Content.Client.UserInterface
|
||||
{
|
||||
var buttonTexture = HandTexture(buttonLocation);
|
||||
var storageTexture = _resourceCache.GetTexture("/Textures/Interface/Inventory/back.png");
|
||||
var button = new HandButton(buttonTexture, storageTexture, buttonLocation);
|
||||
var blockedTexture = _resourceCache.GetTexture("/Textures/Interface/Inventory/blocked.png");
|
||||
var button = new HandButton(buttonTexture, storageTexture, blockedTexture, buttonLocation);
|
||||
var slot = hand.Name;
|
||||
|
||||
button.OnPressed += args => HandKeyBindDown(args, slot);
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects.Components.UserInterface;
|
||||
using Robust.Shared.GameObjects.Components.UserInterface;
|
||||
|
||||
namespace Content.Client.ParticleAccelerator
|
||||
{
|
||||
public class ParticleAcceleratorBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
private ParticleAcceleratorControlMenu _menu;
|
||||
|
||||
public ParticleAcceleratorBoundUserInterface([NotNull] ClientUserInterfaceComponent owner, [NotNull] object uiKey) : base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = new ParticleAcceleratorControlMenu(this);
|
||||
_menu.OnClose += Close;
|
||||
_menu.OpenCentered();
|
||||
}
|
||||
|
||||
public void SendEnableMessage(bool enable)
|
||||
{
|
||||
SendMessage(new ParticleAcceleratorSetEnableMessage(enable));
|
||||
}
|
||||
|
||||
public void SendPowerStateMessage(ParticleAcceleratorPowerState state)
|
||||
{
|
||||
SendMessage(new ParticleAcceleratorSetPowerStateMessage(state));
|
||||
}
|
||||
|
||||
public void SendScanPartsMessage()
|
||||
{
|
||||
SendMessage(new ParticleAcceleratorRescanPartsMessage());
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
_menu.DataUpdate((ParticleAcceleratorUIState) state);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
_menu.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,545 @@
|
||||
using System;
|
||||
using Content.Client.Animations;
|
||||
using Content.Client.UserInterface;
|
||||
using Content.Client.UserInterface.Stylesheets;
|
||||
using Content.Client.Utility;
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using Robust.Client.Animations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Graphics.Drawing;
|
||||
using Robust.Client.Graphics.Shaders;
|
||||
using Robust.Client.Interfaces.ResourceManagement;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Noise;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.ParticleAccelerator
|
||||
{
|
||||
public sealed class ParticleAcceleratorControlMenu : BaseWindow
|
||||
{
|
||||
private ShaderInstance _greyScaleShader;
|
||||
|
||||
private readonly ParticleAcceleratorBoundUserInterface Owner;
|
||||
|
||||
private readonly Label _drawLabel;
|
||||
private readonly NoiseGenerator _drawNoiseGenerator;
|
||||
private readonly Button _onButton;
|
||||
private readonly Button _offButton;
|
||||
private readonly Button _scanButton;
|
||||
private readonly Label _statusLabel;
|
||||
private readonly SpinBox _stateSpinBox;
|
||||
|
||||
private readonly VBoxContainer _alarmControl;
|
||||
private readonly Animation _alarmControlAnimation;
|
||||
|
||||
private readonly PASegmentControl _endCapTexture;
|
||||
private readonly PASegmentControl _fuelChamberTexture;
|
||||
private readonly PASegmentControl _controlBoxTexture;
|
||||
private readonly PASegmentControl _powerBoxTexture;
|
||||
private readonly PASegmentControl _emitterCenterTexture;
|
||||
private readonly PASegmentControl _emitterRightTexture;
|
||||
private readonly PASegmentControl _emitterLeftTexture;
|
||||
|
||||
private float _time;
|
||||
private int _lastDraw;
|
||||
private int _lastReceive;
|
||||
|
||||
private bool _blockSpinBox;
|
||||
private bool _assembled;
|
||||
private bool _shouldContinueAnimating;
|
||||
|
||||
public ParticleAcceleratorControlMenu(ParticleAcceleratorBoundUserInterface owner)
|
||||
{
|
||||
_greyScaleShader = IoCManager.Resolve<IPrototypeManager>().Index<ShaderPrototype>("Greyscale").Instance();
|
||||
|
||||
Owner = owner;
|
||||
_drawNoiseGenerator = new NoiseGenerator(NoiseGenerator.NoiseType.Fbm);
|
||||
_drawNoiseGenerator.SetFrequency(0.5f);
|
||||
|
||||
var resourceCache = IoCManager.Resolve<IResourceCache>();
|
||||
var font = resourceCache.GetFont("/Fonts/Boxfont-round/Boxfont Round.ttf", 13);
|
||||
var panelTex = resourceCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png");
|
||||
|
||||
MouseFilter = MouseFilterMode.Stop;
|
||||
|
||||
_alarmControlAnimation = new Animation
|
||||
{
|
||||
Length = TimeSpan.FromSeconds(1),
|
||||
AnimationTracks =
|
||||
{
|
||||
new AnimationTrackControlProperty
|
||||
{
|
||||
Property = nameof(Control.Visible),
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackProperty.KeyFrame(true, 0),
|
||||
new AnimationTrackProperty.KeyFrame(false, 0.75f),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var back = new StyleBoxTexture
|
||||
{
|
||||
Texture = panelTex,
|
||||
Modulate = Color.FromHex("#25252A"),
|
||||
};
|
||||
back.SetPatchMargin(StyleBox.Margin.All, 10);
|
||||
|
||||
var back2 = new StyleBoxTexture(back)
|
||||
{
|
||||
Modulate = Color.FromHex("#202023")
|
||||
};
|
||||
|
||||
AddChild(new PanelContainer
|
||||
{
|
||||
PanelOverride = back,
|
||||
MouseFilter = MouseFilterMode.Pass
|
||||
});
|
||||
|
||||
_stateSpinBox = new SpinBox
|
||||
{
|
||||
Value = 0,
|
||||
};
|
||||
_stateSpinBox.IsValid = StrengthSpinBoxValid;
|
||||
_stateSpinBox.InitDefaultButtons();
|
||||
_stateSpinBox.ValueChanged += PowerStateChanged;
|
||||
_stateSpinBox.LineEditDisabled = true;
|
||||
|
||||
_offButton = new Button
|
||||
{
|
||||
ToggleMode = false,
|
||||
Text = "Off",
|
||||
StyleClasses = {StyleBase.ButtonOpenRight},
|
||||
};
|
||||
_offButton.OnPressed += args => owner.SendEnableMessage(false);
|
||||
|
||||
_onButton = new Button
|
||||
{
|
||||
ToggleMode = false,
|
||||
Text = "On",
|
||||
StyleClasses = {StyleBase.ButtonOpenLeft},
|
||||
};
|
||||
_onButton.OnPressed += args => owner.SendEnableMessage(true);
|
||||
|
||||
var closeButton = new TextureButton
|
||||
{
|
||||
StyleClasses = {"windowCloseButton"},
|
||||
SizeFlagsHorizontal = SizeFlags.ShrinkEnd
|
||||
};
|
||||
closeButton.OnPressed += args => Close();
|
||||
|
||||
var serviceManual = new Label
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.ShrinkCenter,
|
||||
StyleClasses = {StyleBase.StyleClassLabelSubText},
|
||||
Text = Loc.GetString("Refer to p.132 of service manual")
|
||||
};
|
||||
_drawLabel = new Label();
|
||||
var imgSize = new Vector2(32, 32);
|
||||
AddChild(new VBoxContainer
|
||||
{
|
||||
Children =
|
||||
{
|
||||
new MarginContainer
|
||||
{
|
||||
MarginLeftOverride = 2,
|
||||
MarginTopOverride = 2,
|
||||
Children =
|
||||
{
|
||||
new Label
|
||||
{
|
||||
Text = Loc.GetString("Mark 2 Particle Accelerator"),
|
||||
FontOverride = font,
|
||||
FontColorOverride = StyleNano.NanoGold,
|
||||
},
|
||||
new MarginContainer
|
||||
{
|
||||
MarginRightOverride = 8,
|
||||
Children =
|
||||
{
|
||||
closeButton
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
new PanelContainer
|
||||
{
|
||||
PanelOverride = new StyleBoxFlat {BackgroundColor = StyleNano.NanoGold},
|
||||
CustomMinimumSize = (0, 2),
|
||||
},
|
||||
new Control
|
||||
{
|
||||
CustomMinimumSize = (0, 4)
|
||||
},
|
||||
|
||||
new HBoxContainer
|
||||
{
|
||||
SizeFlagsVertical = SizeFlags.FillExpand,
|
||||
Children =
|
||||
{
|
||||
new MarginContainer
|
||||
{
|
||||
MarginLeftOverride = 4,
|
||||
Children =
|
||||
{
|
||||
new VBoxContainer
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
Children =
|
||||
{
|
||||
new HBoxContainer
|
||||
{
|
||||
Children =
|
||||
{
|
||||
new Label
|
||||
{
|
||||
Text = Loc.GetString("Power: "),
|
||||
SizeFlagsHorizontal = SizeFlags.Expand
|
||||
},
|
||||
_offButton,
|
||||
_onButton
|
||||
}
|
||||
},
|
||||
new HBoxContainer
|
||||
{
|
||||
Children =
|
||||
{
|
||||
new Label
|
||||
{
|
||||
Text = Loc.GetString("Strength: "),
|
||||
SizeFlagsHorizontal = SizeFlags.Expand
|
||||
},
|
||||
_stateSpinBox
|
||||
}
|
||||
},
|
||||
new Control
|
||||
{
|
||||
CustomMinimumSize = (0, 10),
|
||||
},
|
||||
_drawLabel,
|
||||
new Control
|
||||
{
|
||||
SizeFlagsVertical = SizeFlags.Expand
|
||||
},
|
||||
(_alarmControl = new VBoxContainer
|
||||
{
|
||||
Children =
|
||||
{
|
||||
new Label
|
||||
{
|
||||
Text = Loc.GetString("PARTICLE STRENGTH\nLIMITER FAILURE"),
|
||||
FontColorOverride = Color.Red,
|
||||
Align = Label.AlignMode.Center
|
||||
},
|
||||
serviceManual
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
new VBoxContainer
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
Children =
|
||||
{
|
||||
(_statusLabel = new Label
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.ShrinkCenter
|
||||
}),
|
||||
new Control
|
||||
{
|
||||
CustomMinimumSize = (0, 20)
|
||||
},
|
||||
new PanelContainer
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.ShrinkCenter,
|
||||
PanelOverride = back2,
|
||||
Children =
|
||||
{
|
||||
new GridContainer
|
||||
{
|
||||
Columns = 3,
|
||||
VSeparationOverride = 0,
|
||||
HSeparationOverride = 0,
|
||||
Children =
|
||||
{
|
||||
new Control {CustomMinimumSize = imgSize},
|
||||
(_endCapTexture = Segment("end_cap")),
|
||||
new Control {CustomMinimumSize = imgSize},
|
||||
(_controlBoxTexture = Segment("control_box")),
|
||||
(_fuelChamberTexture = Segment("fuel_chamber")),
|
||||
new Control {CustomMinimumSize = imgSize},
|
||||
new Control {CustomMinimumSize = imgSize},
|
||||
(_powerBoxTexture = Segment("power_box")),
|
||||
new Control {CustomMinimumSize = imgSize},
|
||||
(_emitterLeftTexture = Segment("emitter_left")),
|
||||
(_emitterCenterTexture = Segment("emitter_center")),
|
||||
(_emitterRightTexture = Segment("emitter_right")),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
(_scanButton = new Button
|
||||
{
|
||||
Text = Loc.GetString("Scan Parts"),
|
||||
SizeFlagsHorizontal = SizeFlags.ShrinkCenter
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
new StripeBack
|
||||
{
|
||||
Children =
|
||||
{
|
||||
new MarginContainer
|
||||
{
|
||||
MarginLeftOverride = 4,
|
||||
MarginTopOverride = 4,
|
||||
MarginBottomOverride = 4,
|
||||
Children =
|
||||
{
|
||||
new Label
|
||||
{
|
||||
Text = Loc.GetString("Ensure containment field is active before operation"),
|
||||
SizeFlagsHorizontal = SizeFlags.ShrinkCenter,
|
||||
StyleClasses = {StyleBase.StyleClassLabelSubText},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
new MarginContainer
|
||||
{
|
||||
MarginLeftOverride = 12,
|
||||
Children =
|
||||
{
|
||||
new HBoxContainer
|
||||
{
|
||||
Children =
|
||||
{
|
||||
new Label
|
||||
{
|
||||
Text = "FOO-BAR-BAZ",
|
||||
StyleClasses = {StyleBase.StyleClassLabelSubText}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
_scanButton.OnPressed += args => Owner.SendScanPartsMessage();
|
||||
|
||||
_alarmControl.AnimationCompleted += s =>
|
||||
{
|
||||
if (_shouldContinueAnimating)
|
||||
{
|
||||
_alarmControl.PlayAnimation(_alarmControlAnimation, "warningAnim");
|
||||
}
|
||||
else
|
||||
{
|
||||
_alarmControl.Visible = false;
|
||||
}
|
||||
};
|
||||
|
||||
PASegmentControl Segment(string name)
|
||||
{
|
||||
return new PASegmentControl(this, resourceCache, name);
|
||||
}
|
||||
}
|
||||
|
||||
private bool StrengthSpinBoxValid(int n)
|
||||
{
|
||||
return (n >= 0 && n <= 4 && !_blockSpinBox);
|
||||
}
|
||||
|
||||
private void PowerStateChanged(object sender, ValueChangedEventArgs e)
|
||||
{
|
||||
ParticleAcceleratorPowerState newState;
|
||||
switch (e.Value)
|
||||
{
|
||||
case 0:
|
||||
newState = ParticleAcceleratorPowerState.Standby;
|
||||
break;
|
||||
case 1:
|
||||
newState = ParticleAcceleratorPowerState.Level0;
|
||||
break;
|
||||
case 2:
|
||||
newState = ParticleAcceleratorPowerState.Level1;
|
||||
break;
|
||||
case 3:
|
||||
newState = ParticleAcceleratorPowerState.Level2;
|
||||
break;
|
||||
case 4:
|
||||
newState = ParticleAcceleratorPowerState.Level3;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
Owner.SendPowerStateMessage(newState);
|
||||
}
|
||||
|
||||
protected override DragMode GetDragModeFor(Vector2 relativeMousePos)
|
||||
{
|
||||
return DragMode.Move;
|
||||
}
|
||||
|
||||
protected override Vector2 CalculateMinimumSize()
|
||||
{
|
||||
return (400, 300);
|
||||
}
|
||||
|
||||
public void DataUpdate(ParticleAcceleratorUIState uiState)
|
||||
{
|
||||
_assembled = uiState.Assembled;
|
||||
UpdateUI(uiState.Assembled, uiState.InterfaceBlock, uiState.Enabled,
|
||||
uiState.WirePowerBlock);
|
||||
_statusLabel.Text = Loc.GetString($"Status: {(uiState.Assembled ? "Operational" : "Incomplete")}");
|
||||
UpdatePowerState(uiState.State, uiState.Enabled, uiState.Assembled,
|
||||
uiState.MaxLevel);
|
||||
UpdatePreview(uiState);
|
||||
_lastDraw = uiState.PowerDraw;
|
||||
_lastReceive = uiState.PowerReceive;
|
||||
}
|
||||
|
||||
private void UpdatePowerState(ParticleAcceleratorPowerState state, bool enabled, bool assembled,
|
||||
ParticleAcceleratorPowerState maxState)
|
||||
{
|
||||
_stateSpinBox.OverrideValue(state switch
|
||||
{
|
||||
ParticleAcceleratorPowerState.Standby => 0,
|
||||
ParticleAcceleratorPowerState.Level0 => 1,
|
||||
ParticleAcceleratorPowerState.Level1 => 2,
|
||||
ParticleAcceleratorPowerState.Level2 => 3,
|
||||
ParticleAcceleratorPowerState.Level3 => 4,
|
||||
_ => 0
|
||||
});
|
||||
|
||||
|
||||
_shouldContinueAnimating = false;
|
||||
_alarmControl.StopAnimation("warningAnim");
|
||||
_alarmControl.Visible = false;
|
||||
if (maxState == ParticleAcceleratorPowerState.Level3 && enabled == true && assembled == true)
|
||||
{
|
||||
_shouldContinueAnimating = true;
|
||||
_alarmControl.PlayAnimation(_alarmControlAnimation, "warningAnim");
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateUI(bool assembled, bool blocked, bool enabled, bool powerBlock)
|
||||
{
|
||||
_onButton.Pressed = enabled;
|
||||
_offButton.Pressed = !enabled;
|
||||
|
||||
var cantUse = !assembled || blocked || powerBlock;
|
||||
_onButton.Disabled = cantUse;
|
||||
_offButton.Disabled = cantUse;
|
||||
_scanButton.Disabled = blocked;
|
||||
|
||||
var cantChangeLevel = !assembled || blocked;
|
||||
_stateSpinBox.SetButtonDisabled(cantChangeLevel);
|
||||
_blockSpinBox = cantChangeLevel;
|
||||
}
|
||||
|
||||
private void UpdatePreview(ParticleAcceleratorUIState updateMessage)
|
||||
{
|
||||
_endCapTexture.SetPowerState(updateMessage, updateMessage.EndCapExists);
|
||||
_fuelChamberTexture.SetPowerState(updateMessage, updateMessage.FuelChamberExists);
|
||||
_controlBoxTexture.SetPowerState(updateMessage, true);
|
||||
_powerBoxTexture.SetPowerState(updateMessage, updateMessage.PowerBoxExists);
|
||||
_emitterCenterTexture.SetPowerState(updateMessage, updateMessage.EmitterCenterExists);
|
||||
_emitterLeftTexture.SetPowerState(updateMessage, updateMessage.EmitterLeftExists);
|
||||
_emitterRightTexture.SetPowerState(updateMessage, updateMessage.EmitterRightExists);
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
if (!_assembled)
|
||||
{
|
||||
_drawLabel.Text = Loc.GetString("Draw: N/A");
|
||||
return;
|
||||
}
|
||||
|
||||
_time += args.DeltaSeconds;
|
||||
|
||||
var watts = 0;
|
||||
if (_lastDraw != 0)
|
||||
{
|
||||
var val = _drawNoiseGenerator.GetNoise(_time);
|
||||
watts = (int) (_lastDraw + val * 5);
|
||||
}
|
||||
|
||||
_drawLabel.Text = Loc.GetString("Draw: {0:##,##0}/{1:##,##0} W", watts, _lastReceive);
|
||||
}
|
||||
|
||||
private sealed class PASegmentControl : Control
|
||||
{
|
||||
private readonly ParticleAcceleratorControlMenu _menu;
|
||||
private readonly string _baseState;
|
||||
private readonly TextureRect _base;
|
||||
private readonly TextureRect _unlit;
|
||||
private readonly RSI _rsi;
|
||||
|
||||
public PASegmentControl(ParticleAcceleratorControlMenu menu, IResourceCache cache, string name)
|
||||
{
|
||||
_menu = menu;
|
||||
_baseState = name;
|
||||
_rsi = cache.GetResource<RSIResource>($"/Textures/Constructible/Power/PA/{name}.rsi").RSI;
|
||||
|
||||
AddChild(_base = new TextureRect {Texture = _rsi[$"{name}c"].Frame0});
|
||||
AddChild(_unlit = new TextureRect());
|
||||
}
|
||||
|
||||
public void SetPowerState(ParticleAcceleratorUIState state, bool exists)
|
||||
{
|
||||
_base.ShaderOverride = exists ? null : _menu._greyScaleShader;
|
||||
_base.ModulateSelfOverride = exists ? (Color?)null : new Color(127, 127, 127);
|
||||
|
||||
if (!state.Enabled || !exists)
|
||||
{
|
||||
_unlit.Visible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
_unlit.Visible = true;
|
||||
|
||||
var suffix = state.State switch
|
||||
{
|
||||
ParticleAcceleratorPowerState.Standby => "_unlitp",
|
||||
ParticleAcceleratorPowerState.Level0 => "_unlitp0",
|
||||
ParticleAcceleratorPowerState.Level1 => "_unlitp1",
|
||||
ParticleAcceleratorPowerState.Level2 => "_unlitp2",
|
||||
ParticleAcceleratorPowerState.Level3 => "_unlitp3",
|
||||
_ => ""
|
||||
};
|
||||
|
||||
if (!_rsi.TryGetState(_baseState + suffix, out var rState))
|
||||
{
|
||||
_unlit.Visible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
_unlit.Texture = rState.Frame0;
|
||||
}
|
||||
|
||||
protected override Vector2 CalculateMinimumSize()
|
||||
{
|
||||
return _rsi.Size;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -127,19 +127,26 @@ namespace Content.IntegrationTests
|
||||
return grid;
|
||||
}
|
||||
|
||||
protected async Task WaitUntil(IntegrationInstance instance, Func<bool> func, int tickStep = 10, int maxTicks = 600)
|
||||
protected async Task WaitUntil(IntegrationInstance instance, Func<bool> func, int maxTicks = 600, int tickStep = 1)
|
||||
{
|
||||
var ticksAwaited = 0;
|
||||
bool passed;
|
||||
|
||||
await instance.WaitIdleAsync();
|
||||
|
||||
while (!(passed = func()) && ticksAwaited < maxTicks)
|
||||
{
|
||||
await instance.WaitIdleAsync();
|
||||
instance.RunTicks(tickStep);
|
||||
ticksAwaited += tickStep;
|
||||
var ticksToRun = tickStep;
|
||||
|
||||
if (ticksAwaited + tickStep > maxTicks)
|
||||
{
|
||||
ticksToRun = maxTicks - ticksAwaited;
|
||||
}
|
||||
|
||||
await instance.WaitIdleAsync();
|
||||
await instance.WaitRunTicks(ticksToRun);
|
||||
|
||||
ticksAwaited += ticksToRun;
|
||||
}
|
||||
|
||||
Assert.That(passed);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Atmos;
|
||||
@@ -46,7 +47,7 @@ namespace Content.IntegrationTests.Tests.Body
|
||||
|
||||
var originalOxygen = 2;
|
||||
var originalNitrogen = 8;
|
||||
var breathedPercentage = Atmospherics.BreathPercentage;
|
||||
var breathedPercentage = Atmospherics.BreathVolume / gas.Volume;
|
||||
|
||||
gas.AdjustMoles(Gas.Oxygen, originalOxygen);
|
||||
gas.AdjustMoles(Gas.Nitrogen, originalNitrogen);
|
||||
@@ -76,7 +77,7 @@ namespace Content.IntegrationTests.Tests.Body
|
||||
lung.Exhale(1, gas);
|
||||
|
||||
var lungOxygenAfterExhale = lung.Air.GetMoles(Gas.Oxygen);
|
||||
var exhaledOxygen = lungOxygenBeforeExhale - lungOxygenAfterExhale;
|
||||
var exhaledOxygen = Math.Abs(lungOxygenBeforeExhale - lungOxygenAfterExhale);
|
||||
|
||||
// Not completely empty
|
||||
Assert.Positive(lung.Air.Gases.Sum());
|
||||
|
||||
@@ -14,7 +14,7 @@ using Robust.Shared.Interfaces.Map;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.IntegrationTests.Tests
|
||||
namespace Content.IntegrationTests.Tests.Buckle
|
||||
{
|
||||
[TestFixture]
|
||||
[TestOf(typeof(BuckleComponent))]
|
||||
@@ -22,7 +22,7 @@ namespace Content.IntegrationTests.Tests
|
||||
public class BuckleTest : ContentIntegrationTest
|
||||
{
|
||||
[Test]
|
||||
public async Task Test()
|
||||
public async Task BuckleUnbuckleCooldownRangeTest()
|
||||
{
|
||||
var server = StartServerDummyTicker();
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace Content.IntegrationTests.Tests
|
||||
BuckleComponent buckle = null;
|
||||
StrapComponent strap = null;
|
||||
|
||||
server.Assert(() =>
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var mapManager = IoCManager.Resolve<IMapManager>();
|
||||
|
||||
@@ -82,9 +82,9 @@ namespace Content.IntegrationTests.Tests
|
||||
});
|
||||
|
||||
// Wait enough ticks for the unbuckling cooldown to run out
|
||||
server.RunTicks(60);
|
||||
await server.WaitRunTicks(60);
|
||||
|
||||
server.Assert(() =>
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
// Still buckled
|
||||
Assert.True(buckle.Buckled);
|
||||
@@ -115,9 +115,9 @@ namespace Content.IntegrationTests.Tests
|
||||
});
|
||||
|
||||
// Wait enough ticks for the unbuckling cooldown to run out
|
||||
server.RunTicks(60);
|
||||
await server.WaitRunTicks(60);
|
||||
|
||||
server.Assert(() =>
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
// Still buckled
|
||||
Assert.True(buckle.Buckled);
|
||||
@@ -159,17 +159,15 @@ namespace Content.IntegrationTests.Tests
|
||||
human.Transform.WorldPosition += (1, 0);
|
||||
});
|
||||
|
||||
server.RunTicks(1);
|
||||
await server.WaitRunTicks(1);
|
||||
|
||||
server.Assert(() =>
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
// No longer buckled
|
||||
Assert.False(buckle.Buckled);
|
||||
Assert.Null(buckle.BuckledTo);
|
||||
Assert.IsEmpty(strap.BuckledEntities);
|
||||
});
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -177,14 +175,13 @@ namespace Content.IntegrationTests.Tests
|
||||
{
|
||||
var server = StartServer();
|
||||
|
||||
IEntity human = null;
|
||||
IEntity chair = null;
|
||||
IEntity human;
|
||||
IEntity chair;
|
||||
BuckleComponent buckle = null;
|
||||
StrapComponent strap = null;
|
||||
HandsComponent hands = null;
|
||||
IBody body = null;
|
||||
|
||||
server.Assert(() =>
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var mapManager = IoCManager.Resolve<IMapManager>();
|
||||
|
||||
@@ -206,7 +203,7 @@ namespace Content.IntegrationTests.Tests
|
||||
|
||||
// Component sanity check
|
||||
Assert.True(human.TryGetComponent(out buckle));
|
||||
Assert.True(chair.TryGetComponent(out strap));
|
||||
Assert.True(chair.HasComponent<StrapComponent>());
|
||||
Assert.True(human.TryGetComponent(out hands));
|
||||
Assert.True(human.TryGetComponent(out body));
|
||||
|
||||
@@ -226,9 +223,9 @@ namespace Content.IntegrationTests.Tests
|
||||
}
|
||||
});
|
||||
|
||||
server.RunTicks(10);
|
||||
await server.WaitRunTicks(10);
|
||||
|
||||
server.Assert(() =>
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
// Still buckled
|
||||
Assert.True(buckle.Buckled);
|
||||
@@ -248,9 +245,9 @@ namespace Content.IntegrationTests.Tests
|
||||
}
|
||||
});
|
||||
|
||||
server.RunTicks(10);
|
||||
await server.WaitRunTicks(10);
|
||||
|
||||
server.Assert(() =>
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
// Still buckled
|
||||
Assert.True(buckle.Buckled);
|
||||
@@ -261,8 +258,73 @@ namespace Content.IntegrationTests.Tests
|
||||
Assert.Null(hands.GetItem(slot));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
[Test]
|
||||
public async Task ForceUnbuckleBuckleTest()
|
||||
{
|
||||
var server = StartServer();
|
||||
|
||||
IEntity human = null;
|
||||
IEntity chair = null;
|
||||
BuckleComponent buckle = null;
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var mapManager = IoCManager.Resolve<IMapManager>();
|
||||
|
||||
var mapId = new MapId(1);
|
||||
mapManager.CreateNewMapEntity(mapId);
|
||||
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
var gridId = new GridId(1);
|
||||
var grid = mapManager.CreateGrid(mapId, gridId);
|
||||
var coordinates = grid.GridEntityId.ToCoordinates();
|
||||
var tileManager = IoCManager.Resolve<ITileDefinitionManager>();
|
||||
var tileId = tileManager["underplating"].TileId;
|
||||
var tile = new Tile(tileId);
|
||||
|
||||
grid.SetTile(coordinates, tile);
|
||||
|
||||
human = entityManager.SpawnEntity("HumanMob_Content", coordinates);
|
||||
chair = entityManager.SpawnEntity("ChairWood", coordinates);
|
||||
|
||||
// Component sanity check
|
||||
Assert.True(human.TryGetComponent(out buckle));
|
||||
Assert.True(chair.HasComponent<StrapComponent>());
|
||||
|
||||
// Buckle
|
||||
Assert.True(buckle.TryBuckle(human, chair));
|
||||
Assert.NotNull(buckle.BuckledTo);
|
||||
Assert.True(buckle.Buckled);
|
||||
|
||||
// Move the buckled entity away
|
||||
human.Transform.LocalPosition += (100, 0);
|
||||
});
|
||||
|
||||
await WaitUntil(server, () => !buckle.Buckled, maxTicks: 10);
|
||||
|
||||
Assert.False(buckle.Buckled);
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
// Move the now unbuckled entity back onto the chair
|
||||
human.Transform.LocalPosition -= (100, 0);
|
||||
|
||||
// Buckle
|
||||
Assert.True(buckle.TryBuckle(human, chair));
|
||||
Assert.NotNull(buckle.BuckledTo);
|
||||
Assert.True(buckle.Buckled);
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(60);
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
// Still buckled
|
||||
Assert.NotNull(buckle.BuckledTo);
|
||||
Assert.True(buckle.Buckled);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
51
Content.IntegrationTests/Tests/Commands/RejuvenateTest.cs
Normal file
51
Content.IntegrationTests/Tests/Commands/RejuvenateTest.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GlobalVerbs;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Map;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Commands
|
||||
{
|
||||
[TestFixture]
|
||||
[TestOf(typeof(RejuvenateVerb))]
|
||||
public class RejuvenateTest : ContentIntegrationTest
|
||||
{
|
||||
[Test]
|
||||
public async Task RejuvenateDeadTest()
|
||||
{
|
||||
var server = StartServerDummyTicker();
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var mapManager = IoCManager.Resolve<IMapManager>();
|
||||
|
||||
mapManager.CreateNewMapEntity(MapId.Nullspace);
|
||||
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
|
||||
var human = entityManager.SpawnEntity("HumanMob_Content", MapCoordinates.Nullspace);
|
||||
|
||||
// Sanity check
|
||||
Assert.True(human.TryGetComponent(out IDamageableComponent damageable));
|
||||
Assert.That(damageable.CurrentState, Is.EqualTo(DamageState.Alive));
|
||||
|
||||
// Kill the entity
|
||||
damageable.ChangeDamage(DamageClass.Brute, 10000000, true);
|
||||
|
||||
// Check that it is dead
|
||||
Assert.That(damageable.CurrentState, Is.EqualTo(DamageState.Dead));
|
||||
|
||||
// Rejuvenate them
|
||||
RejuvenateVerb.PerformRejuvenate(human);
|
||||
|
||||
// Check that it is alive and with no damage
|
||||
Assert.That(damageable.CurrentState, Is.EqualTo(DamageState.Alive));
|
||||
Assert.That(damageable.TotalDamage, Is.Zero);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components.Gravity;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.Components.Gravity;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Utility;
|
||||
using NUnit.Framework;
|
||||
using Robust.Server.Interfaces.Timing;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Map;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Gravity
|
||||
{
|
||||
[TestFixture]
|
||||
[TestOf(typeof(WeightlessSystem))]
|
||||
[TestOf(typeof(GravityGeneratorComponent))]
|
||||
public class WeightlessStatusTests : ContentIntegrationTest
|
||||
{
|
||||
[Test]
|
||||
public async Task WeightlessStatusTest()
|
||||
{
|
||||
var server = StartServer();
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
|
||||
var mapManager = server.ResolveDependency<IMapManager>();
|
||||
var entityManager = server.ResolveDependency<IEntityManager>();
|
||||
var pauseManager = server.ResolveDependency<IPauseManager>();
|
||||
var tileDefinitionManager = server.ResolveDependency<ITileDefinitionManager>();
|
||||
|
||||
IEntity human = null;
|
||||
SharedStatusEffectsComponent statusEffects = null;
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var mapId = mapManager.CreateMap();
|
||||
|
||||
pauseManager.AddUninitializedMap(mapId);
|
||||
|
||||
var gridId = new GridId(1);
|
||||
|
||||
if (!mapManager.TryGetGrid(gridId, out var grid))
|
||||
{
|
||||
grid = mapManager.CreateGrid(mapId, gridId);
|
||||
}
|
||||
|
||||
var tileDefinition = tileDefinitionManager["underplating"];
|
||||
var tile = new Tile(tileDefinition.TileId);
|
||||
var coordinates = grid.ToCoordinates();
|
||||
|
||||
grid.SetTile(coordinates, tile);
|
||||
|
||||
pauseManager.DoMapInitialize(mapId);
|
||||
|
||||
human = entityManager.SpawnEntity("HumanMob_Content", coordinates);
|
||||
|
||||
Assert.True(human.TryGetComponent(out statusEffects));
|
||||
});
|
||||
|
||||
// Let WeightlessSystem and GravitySystem tick
|
||||
await server.WaitRunTicks(1);
|
||||
|
||||
GravityGeneratorComponent gravityGenerator = null;
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
// No gravity without a gravity generator
|
||||
Assert.True(statusEffects.Statuses.ContainsKey(StatusEffect.Weightless));
|
||||
|
||||
gravityGenerator = human.EnsureComponent<GravityGeneratorComponent>();
|
||||
});
|
||||
|
||||
// Let WeightlessSystem and GravitySystem tick
|
||||
await server.WaitRunTicks(1);
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
Assert.False(statusEffects.Statuses.ContainsKey(StatusEffect.Weightless));
|
||||
|
||||
// Disable the gravity generator
|
||||
var args = new BreakageEventArgs {Owner = human};
|
||||
gravityGenerator.OnBreak(args);
|
||||
});
|
||||
|
||||
await server.WaitRunTicks(1);
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
Assert.False(statusEffects.Statuses.ContainsKey(StatusEffect.Weightless));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components;
|
||||
using Content.Server.GameObjects.Components.Power;
|
||||
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
|
||||
using Content.Server.GameObjects.Components.Power.PowerNetComponents;
|
||||
@@ -34,6 +35,11 @@ namespace Content.IntegrationTests.Tests
|
||||
var consumerEnt1 = entityMan.SpawnEntity("DebugConsumer", grid.ToCoordinates(0, 1));
|
||||
var consumerEnt2 = entityMan.SpawnEntity("DebugConsumer", grid.ToCoordinates(0, 2));
|
||||
|
||||
if (generatorEnt.TryGetComponent(out AnchorableComponent anchorable))
|
||||
{
|
||||
anchorable.TryAnchor(null, force:true);
|
||||
}
|
||||
|
||||
Assert.That(generatorEnt.TryGetComponent(out supplier));
|
||||
Assert.That(consumerEnt1.TryGetComponent(out consumer1));
|
||||
Assert.That(consumerEnt2.TryGetComponent(out consumer2));
|
||||
|
||||
@@ -65,6 +65,38 @@ namespace Content.Server.Atmos
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Heat capacity ratio of gas mixture
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public float HeatCapacityRatio
|
||||
{
|
||||
get
|
||||
{
|
||||
var delimiterSum = 0f;
|
||||
for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
|
||||
{
|
||||
delimiterSum += _moles[i] / (_atmosphereSystem.GetGas(i).HeatCapacityRatio - 1);
|
||||
}
|
||||
return 1 + TotalMoles / delimiterSum;
|
||||
}
|
||||
}
|
||||
|
||||
public float MolarMass
|
||||
{
|
||||
get
|
||||
{
|
||||
var molarMass = 0f;
|
||||
var totalMoles = TotalMoles;
|
||||
for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
|
||||
{
|
||||
molarMass += _atmosphereSystem.GetGas(i).MolarMass * (_moles[i] / totalMoles);
|
||||
}
|
||||
|
||||
return molarMass;
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables]
|
||||
public float HeatCapacityArchived
|
||||
{
|
||||
@@ -136,14 +168,15 @@ namespace Content.Server.Atmos
|
||||
public GasMixture(AtmosphereSystem? atmosphereSystem)
|
||||
{
|
||||
_atmosphereSystem = atmosphereSystem ?? EntitySystem.Get<AtmosphereSystem>();
|
||||
_moles = new float[_atmosphereSystem.Gases.Count()];
|
||||
_molesArchived = new float[_moles.Length];
|
||||
}
|
||||
|
||||
public GasMixture(float volume, AtmosphereSystem? atmosphereSystem = null)
|
||||
public GasMixture(float volume, AtmosphereSystem? atmosphereSystem = null): this(atmosphereSystem)
|
||||
{
|
||||
if (volume < 0)
|
||||
volume = 0;
|
||||
Volume = volume;
|
||||
_atmosphereSystem = atmosphereSystem ?? EntitySystem.Get<AtmosphereSystem>();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System;
|
||||
using Content.Server.GameObjects.Components.Atmos;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Physics;
|
||||
using Robust.Shared.GameObjects.Components;
|
||||
using Robust.Shared.Interfaces.Physics;
|
||||
using Robust.Shared.Interfaces.Random;
|
||||
@@ -13,7 +14,7 @@ using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Atmos
|
||||
{
|
||||
public class HighPressureMovementController : VirtualController
|
||||
public class HighPressureMovementController : FrictionController
|
||||
{
|
||||
[Dependency] private IRobustRandom _robustRandom = default!;
|
||||
[Dependency] private IPhysicsManager _physicsManager = default!;
|
||||
@@ -69,17 +70,5 @@ namespace Content.Server.Atmos
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateAfterProcessing()
|
||||
{
|
||||
base.UpdateAfterProcessing();
|
||||
|
||||
if (ControlledComponent != null && !_physicsManager.IsWeightless(ControlledComponent.Owner.Transform.Coordinates))
|
||||
{
|
||||
LinearVelocity *= 0.85f;
|
||||
if (MathF.Abs(LinearVelocity.Length) < 1f)
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1127,6 +1127,10 @@ namespace Content.Server.Atmos
|
||||
|
||||
UpdateVisuals();
|
||||
|
||||
if (!Excited)
|
||||
{
|
||||
_gridAtmosphereComponent.AddActiveTile(this);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<OutputType Condition="'$(FullRelease)' != 'True'">Exe</OutputType>
|
||||
<NoWarn>1998</NoWarn>
|
||||
<WarningsAsErrors>CS8604;CS8765</WarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
<Import Project="..\RobustToolbox\MSBuild\Robust.DefineConstants.targets" />
|
||||
<ItemGroup>
|
||||
|
||||
198
Content.Server/DeviceNetwork/DeviceNetwork.cs
Normal file
198
Content.Server/DeviceNetwork/DeviceNetwork.cs
Normal file
@@ -0,0 +1,198 @@
|
||||
using Content.Server.Interfaces;
|
||||
using Robust.Shared.Interfaces.Random;
|
||||
using Robust.Shared.IoC;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Content.Server.GameObjects.EntitySystems.DeviceNetwork
|
||||
{
|
||||
public delegate void OnReceiveNetMessage(int frequency, string sender, IReadOnlyDictionary<string, string> payload, Metadata metadata, bool broadcast);
|
||||
|
||||
public class DeviceNetwork : IDeviceNetwork
|
||||
{
|
||||
private const int PACKAGES_PER_TICK = 30;
|
||||
|
||||
private readonly IRobustRandom _random = IoCManager.Resolve<IRobustRandom>();
|
||||
private readonly Dictionary<int, List<NetworkDevice>> _devices = new Dictionary<int, List<NetworkDevice>>();
|
||||
private readonly Queue<NetworkPackage> _packages = new Queue<NetworkPackage>();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DeviceNetworkConnection Register(int netId, int frequency, OnReceiveNetMessage messageHandler, bool receiveAll = false)
|
||||
{
|
||||
var address = GenerateValidAddress(netId, frequency);
|
||||
|
||||
var device = new NetworkDevice
|
||||
{
|
||||
Address = address,
|
||||
Frequency = frequency,
|
||||
ReceiveAll = receiveAll,
|
||||
ReceiveNetMessage = messageHandler
|
||||
};
|
||||
|
||||
AddDevice(netId, device);
|
||||
|
||||
return new DeviceNetworkConnection(this, netId, address, frequency);
|
||||
}
|
||||
|
||||
public DeviceNetworkConnection Register(int netId, OnReceiveNetMessage messageHandler, bool receiveAll = false)
|
||||
{
|
||||
return Register(netId, 0, messageHandler, receiveAll);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
var i = PACKAGES_PER_TICK;
|
||||
while (_packages.Count > 0 && i > 0)
|
||||
{
|
||||
i--;
|
||||
|
||||
var package = _packages.Dequeue();
|
||||
|
||||
if (package.Broadcast)
|
||||
{
|
||||
BroadcastPackage(package);
|
||||
continue;
|
||||
}
|
||||
|
||||
SendPackage(package);
|
||||
}
|
||||
}
|
||||
|
||||
public bool EnqueuePackage(int netId, int frequency, string address, IReadOnlyDictionary<string, string> data, string sender, Metadata metadata, bool broadcast = false)
|
||||
{
|
||||
if (!_devices.ContainsKey(netId))
|
||||
return false;
|
||||
|
||||
var package = new NetworkPackage()
|
||||
{
|
||||
NetId = netId,
|
||||
Frequency = frequency,
|
||||
Address = address,
|
||||
Broadcast = broadcast,
|
||||
Data = data,
|
||||
Sender = sender,
|
||||
Metadata = metadata
|
||||
};
|
||||
|
||||
_packages.Enqueue(package);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void RemoveDevice(int netId, int frequency, string address)
|
||||
{
|
||||
var device = DeviceWithAddress(netId, frequency, address);
|
||||
_devices[netId].Remove(device);
|
||||
}
|
||||
|
||||
public void SetDeviceReceiveAll(int netId, int frequency, string address, bool receiveAll)
|
||||
{
|
||||
var device = DeviceWithAddress(netId, frequency, address);
|
||||
device.ReceiveAll = receiveAll;
|
||||
}
|
||||
|
||||
public bool GetDeviceReceiveAll(int netId, int frequency, string address)
|
||||
{
|
||||
var device = DeviceWithAddress(netId, frequency, address);
|
||||
return device.ReceiveAll;
|
||||
}
|
||||
|
||||
private string GenerateValidAddress(int netId, int frequency)
|
||||
{
|
||||
var unique = false;
|
||||
var devices = DevicesForFrequency(netId, frequency);
|
||||
var address = "";
|
||||
|
||||
while (!unique)
|
||||
{
|
||||
address = _random.Next().ToString("x");
|
||||
unique = !devices.Exists(device => device.Address == address);
|
||||
}
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
private void AddDevice(int netId, NetworkDevice networkDevice)
|
||||
{
|
||||
if(!_devices.ContainsKey(netId))
|
||||
_devices[netId] = new List<NetworkDevice>();
|
||||
|
||||
_devices[netId].Add(networkDevice);
|
||||
}
|
||||
|
||||
private List<NetworkDevice> DevicesForFrequency(int netId, int frequency)
|
||||
{
|
||||
if (!_devices.ContainsKey(netId))
|
||||
return new List<NetworkDevice>();
|
||||
|
||||
var result = _devices[netId].FindAll(device => device.Frequency == frequency);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private NetworkDevice DeviceWithAddress(int netId, int frequency, string address)
|
||||
{
|
||||
var devices = DevicesForFrequency(netId, frequency);
|
||||
|
||||
var device = devices.Find(device => device.Address == address);
|
||||
|
||||
return device;
|
||||
}
|
||||
|
||||
private List<NetworkDevice> DevicesWithReceiveAll(int netId, int frequency)
|
||||
{
|
||||
if (!_devices.ContainsKey(netId))
|
||||
return new List<NetworkDevice>();
|
||||
|
||||
var result = _devices[netId].FindAll(device => device.Frequency == frequency && device.ReceiveAll);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void BroadcastPackage(NetworkPackage package)
|
||||
{
|
||||
var devices = DevicesForFrequency(package.NetId, package.Frequency);
|
||||
SendToDevices(devices, package, true);
|
||||
}
|
||||
|
||||
private void SendPackage(NetworkPackage package)
|
||||
{
|
||||
var devices = DevicesWithReceiveAll(package.NetId, package.Frequency);
|
||||
var device = DeviceWithAddress(package.NetId, package.Frequency, package.Address);
|
||||
|
||||
devices.Add(device);
|
||||
|
||||
SendToDevices(devices, package, false);
|
||||
}
|
||||
|
||||
private void SendToDevices(List<NetworkDevice> devices, NetworkPackage package, bool broadcast)
|
||||
{
|
||||
for (var index = 0; index < devices.Count; index++)
|
||||
{
|
||||
var device = devices[index];
|
||||
if (device.Address == package.Sender)
|
||||
continue;
|
||||
|
||||
device.ReceiveNetMessage(package.Frequency, package.Sender, package.Data, package.Metadata, broadcast);
|
||||
}
|
||||
}
|
||||
|
||||
internal class NetworkDevice
|
||||
{
|
||||
public int Frequency;
|
||||
public string Address;
|
||||
public OnReceiveNetMessage ReceiveNetMessage;
|
||||
public bool ReceiveAll;
|
||||
}
|
||||
|
||||
internal class NetworkPackage
|
||||
{
|
||||
public int NetId;
|
||||
public int Frequency;
|
||||
public string Address;
|
||||
public bool Broadcast;
|
||||
public IReadOnlyDictionary<string, string> Data { get; set; }
|
||||
public Metadata Metadata;
|
||||
public string Sender;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
72
Content.Server/DeviceNetwork/DeviceNetworkConnection.cs
Normal file
72
Content.Server/DeviceNetwork/DeviceNetworkConnection.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using Content.Server.Interfaces;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Content.Server.GameObjects.EntitySystems.DeviceNetwork
|
||||
{
|
||||
public class DeviceNetworkConnection : IDeviceNetworkConnection
|
||||
{
|
||||
private readonly DeviceNetwork _network;
|
||||
[ViewVariables]
|
||||
private readonly int _netId;
|
||||
|
||||
[ViewVariables]
|
||||
public bool Open { get; internal set; }
|
||||
[ViewVariables]
|
||||
public string Address { get; internal set; }
|
||||
[ViewVariables]
|
||||
public int Frequency { get; internal set; }
|
||||
|
||||
[ViewVariables]
|
||||
public bool RecieveAll
|
||||
{
|
||||
get => _network.GetDeviceReceiveAll(_netId, Frequency, Address);
|
||||
set => _network.SetDeviceReceiveAll(_netId, Frequency, Address, value);
|
||||
}
|
||||
|
||||
public DeviceNetworkConnection(DeviceNetwork network, int netId, string address, int frequency)
|
||||
{
|
||||
_network = network;
|
||||
_netId = netId;
|
||||
Open = true;
|
||||
Address = address;
|
||||
Frequency = frequency;
|
||||
}
|
||||
|
||||
public bool Send(int frequency, string address, IReadOnlyDictionary<string, string> payload, Metadata metadata)
|
||||
{
|
||||
return Open && _network.EnqueuePackage(_netId, frequency, address, payload, Address, metadata);
|
||||
}
|
||||
|
||||
public bool Send(int frequency, string address, Dictionary<string, string> payload)
|
||||
{
|
||||
return Send(frequency, address, payload);
|
||||
}
|
||||
|
||||
public bool Send(string address, Dictionary<string, string> payload)
|
||||
{
|
||||
return Send(0, address, payload);
|
||||
}
|
||||
|
||||
public bool Broadcast(int frequency, IReadOnlyDictionary<string, string> payload, Metadata metadata)
|
||||
{
|
||||
return Open && _network.EnqueuePackage(_netId, frequency, "", payload, Address, metadata, true);
|
||||
}
|
||||
|
||||
public bool Broadcast(int frequency, Dictionary<string, string> payload)
|
||||
{
|
||||
return Broadcast(frequency, payload);
|
||||
}
|
||||
|
||||
public bool Broadcast(Dictionary<string, string> payload)
|
||||
{
|
||||
return Broadcast(0, payload);
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
_network.RemoveDevice(_netId, Frequency, Address);
|
||||
Open = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
20
Content.Server/DeviceNetwork/Metadata.cs
Normal file
20
Content.Server/DeviceNetwork/Metadata.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Content.Server.GameObjects.EntitySystems.DeviceNetwork
|
||||
{
|
||||
public class Metadata : Dictionary<string, object>
|
||||
{
|
||||
public bool TryParseMetadata<T>(string key, [NotNullWhen(true)] out T data)
|
||||
{
|
||||
if(TryGetValue(key, out var value) && value is T typedValue)
|
||||
{
|
||||
data = typedValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
data = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using Content.Server.Interfaces;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Content.Server.GameObjects.EntitySystems.DeviceNetwork
|
||||
{
|
||||
public abstract class BaseNetworkConnection : IDeviceNetworkConnection
|
||||
{
|
||||
protected readonly DeviceNetworkConnection Connection;
|
||||
|
||||
protected OnReceiveNetMessage MessageHandler;
|
||||
|
||||
[ViewVariables]
|
||||
public bool Open => Connection.Open;
|
||||
[ViewVariables]
|
||||
public string Address => Connection.Address;
|
||||
[ViewVariables]
|
||||
public int Frequency => Connection.Frequency;
|
||||
|
||||
protected BaseNetworkConnection(int netId, int frequency, OnReceiveNetMessage onReceive, bool receiveAll)
|
||||
{
|
||||
var network = IoCManager.Resolve<IDeviceNetwork>();
|
||||
Connection = network.Register(netId, frequency, OnReceiveNetMessage, receiveAll);
|
||||
MessageHandler = onReceive;
|
||||
|
||||
}
|
||||
|
||||
public bool Send(int frequency, string address, Dictionary<string, string> payload)
|
||||
{
|
||||
var data = ManipulatePayload(payload);
|
||||
var metadata = GetMetadata();
|
||||
return Connection.Send(frequency, address, data, metadata);
|
||||
}
|
||||
|
||||
public bool Send(string address, Dictionary<string, string> payload)
|
||||
{
|
||||
return Send(0, address, payload);
|
||||
}
|
||||
|
||||
public bool Broadcast(int frequency, Dictionary<string, string> payload)
|
||||
{
|
||||
var data = ManipulatePayload(payload);
|
||||
var metadata = GetMetadata();
|
||||
return Connection.Broadcast(frequency, data, metadata);
|
||||
}
|
||||
|
||||
public bool Broadcast(Dictionary<string, string> payload)
|
||||
{
|
||||
return Broadcast(0, payload);
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
Connection.Close();
|
||||
}
|
||||
|
||||
private void OnReceiveNetMessage(int frequency, string sender, IReadOnlyDictionary<string, string> payload, Metadata metadata, bool broadcast)
|
||||
{
|
||||
if (CanReceive(frequency, sender, payload, metadata, broadcast))
|
||||
{
|
||||
MessageHandler(frequency, sender, payload, metadata, broadcast);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract bool CanReceive(int frequency, string sender, IReadOnlyDictionary<string, string> payload, Metadata metadata, bool broadcast);
|
||||
protected abstract Dictionary<string, string> ManipulatePayload(Dictionary<string, string> payload);
|
||||
protected abstract Metadata GetMetadata();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
using Content.Server.GameObjects.Components.NodeContainer;
|
||||
using Content.Server.GameObjects.Components.NodeContainer.NodeGroups;
|
||||
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Content.Server.GameObjects.EntitySystems.DeviceNetwork
|
||||
{
|
||||
public class WiredNetworkConnection : BaseNetworkConnection
|
||||
{
|
||||
public const string WIRENET = "powernet";
|
||||
|
||||
private readonly IEntity _owner;
|
||||
|
||||
public WiredNetworkConnection(OnReceiveNetMessage onReceive, bool receiveAll, IEntity owner) : base(NetworkUtils.WIRED, 0, onReceive, receiveAll)
|
||||
{
|
||||
_owner = owner;
|
||||
}
|
||||
|
||||
protected override bool CanReceive(int frequency, string sender, IReadOnlyDictionary<string, string> payload, Metadata metadata, bool broadcast)
|
||||
{
|
||||
if (_owner.Deleted)
|
||||
{
|
||||
Connection.Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_owner.TryGetComponent<PowerReceiverComponent>(out var powerReceiver)
|
||||
&& TryGetWireNet(powerReceiver, out var ownNet)
|
||||
&& metadata.TryParseMetadata<INodeGroup>(WIRENET, out var senderNet))
|
||||
{
|
||||
return ownNet.Equals(senderNet);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override Metadata GetMetadata()
|
||||
{
|
||||
if (_owner.Deleted)
|
||||
{
|
||||
Connection.Close();
|
||||
return new Metadata();
|
||||
}
|
||||
|
||||
if (_owner.TryGetComponent<PowerReceiverComponent>(out var powerReceiver)
|
||||
&& TryGetWireNet(powerReceiver, out var net))
|
||||
{
|
||||
var metadata = new Metadata
|
||||
{
|
||||
{WIRENET, net }
|
||||
};
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
return new Metadata();
|
||||
}
|
||||
|
||||
protected override Dictionary<string, string> ManipulatePayload(Dictionary<string, string> payload)
|
||||
{
|
||||
return payload;
|
||||
}
|
||||
|
||||
private bool TryGetWireNet(PowerReceiverComponent powerReceiver, out INodeGroup net)
|
||||
{
|
||||
if (powerReceiver.Provider is PowerProviderComponent && powerReceiver.Provider.ProviderOwner.TryGetComponent<NodeContainerComponent>(out var nodeContainer))
|
||||
{
|
||||
var nodes = nodeContainer.Nodes;
|
||||
for (var index = 0; index < nodes.Count; index++)
|
||||
{
|
||||
if (nodes[index].NodeGroupID == NodeGroupID.WireNet)
|
||||
{
|
||||
net = nodes[index].NodeGroup;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
net = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Content.Server.GameObjects.EntitySystems.DeviceNetwork
|
||||
{
|
||||
public class WirelessNetworkConnection : BaseNetworkConnection
|
||||
{
|
||||
public const string WIRELESS_POSITION = "position";
|
||||
|
||||
private readonly IEntity _owner;
|
||||
|
||||
private float _range;
|
||||
public float Range { get => _range; set => _range = Math.Abs(value); }
|
||||
|
||||
public WirelessNetworkConnection(int frequency, OnReceiveNetMessage onReceive, bool receiveAll, IEntity owner, float range) : base(NetworkUtils.WIRELESS, frequency, onReceive, receiveAll)
|
||||
{
|
||||
_owner = owner;
|
||||
Range = range;
|
||||
}
|
||||
|
||||
protected override bool CanReceive(int frequency, string sender, IReadOnlyDictionary<string, string> payload, Metadata metadata, bool broadcast)
|
||||
{
|
||||
if (_owner.Deleted)
|
||||
{
|
||||
Connection.Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (metadata.TryParseMetadata<Vector2>(WIRELESS_POSITION, out var position))
|
||||
{
|
||||
var ownPosition = _owner.Transform.WorldPosition;
|
||||
var distance = (ownPosition - position).Length;
|
||||
return distance <= Range;
|
||||
}
|
||||
//Only receive packages with the same frequency
|
||||
return frequency == Frequency;
|
||||
}
|
||||
|
||||
protected override Metadata GetMetadata()
|
||||
{
|
||||
if (_owner.Deleted)
|
||||
{
|
||||
Connection.Close();
|
||||
return new Metadata();
|
||||
}
|
||||
|
||||
var position = _owner.Transform.WorldPosition;
|
||||
var metadata = new Metadata
|
||||
{
|
||||
{WIRELESS_POSITION, position}
|
||||
};
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
protected override Dictionary<string, string> ManipulatePayload(Dictionary<string, string> payload)
|
||||
{
|
||||
return payload;
|
||||
}
|
||||
}
|
||||
}
|
||||
38
Content.Server/DeviceNetwork/NetworkUtils.cs
Normal file
38
Content.Server/DeviceNetwork/NetworkUtils.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using Content.Server.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Content.Server.GameObjects.EntitySystems.DeviceNetwork
|
||||
{
|
||||
/// <summary>
|
||||
/// A collection of utilities to help with using device networks
|
||||
/// </summary>
|
||||
public static class NetworkUtils
|
||||
{
|
||||
public const int PRIVATE = 0;
|
||||
public const int WIRED = 1;
|
||||
public const int WIRELESS = 2;
|
||||
|
||||
public const string COMMAND = "command";
|
||||
public const string MESSAGE = "message";
|
||||
public const string PING = "ping";
|
||||
|
||||
/// <summary>
|
||||
/// Handles responding to pings.
|
||||
/// </summary>
|
||||
public static void PingResponse<T>(T connection, string sender, IReadOnlyDictionary<string, string> payload, string message = "") where T : IDeviceNetworkConnection
|
||||
{
|
||||
if (payload.TryGetValue(COMMAND, out var command) && command == PING)
|
||||
{
|
||||
var response = new Dictionary<string, string>
|
||||
{
|
||||
{COMMAND, "ping_response"},
|
||||
{MESSAGE, message}
|
||||
};
|
||||
|
||||
connection.Send(connection.Frequency, sender, response);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ using Robust.Server.GameObjects.Components.UserInterface;
|
||||
using Robust.Server.Interfaces.GameObjects;
|
||||
using Robust.Server.Interfaces.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.ComponentDependencies;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.Random;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -29,10 +30,11 @@ namespace Content.Server.GameObjects.Components.Arcade
|
||||
|
||||
public override string Name => "BlockGameArcade";
|
||||
public override uint? NetID => ContentNetIDs.BLOCKGAME_ARCADE;
|
||||
private bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered;
|
||||
[ComponentDependency] private PowerReceiverComponent? _powerReceiverComponent = default!;
|
||||
private bool Powered => _powerReceiverComponent?.Powered ?? false;
|
||||
private BoundUserInterface? UserInterface => Owner.GetUIOrNull(BlockGameUiKey.Key);
|
||||
|
||||
private BlockGame _game = null!;
|
||||
private BlockGame? _game;
|
||||
|
||||
private IPlayerSession? _player;
|
||||
private List<IPlayerSession> _spectators = new List<IPlayerSession>();
|
||||
@@ -47,7 +49,7 @@ namespace Content.Server.GameObjects.Components.Arcade
|
||||
{
|
||||
return;
|
||||
}
|
||||
if(!ActionBlockerSystem.CanInteract(Owner)) return;
|
||||
if(!ActionBlockerSystem.CanInteract(actor.playerSession.AttachedEntity)) return;
|
||||
|
||||
UserInterface?.Toggle(actor.playerSession);
|
||||
RegisterPlayerSession(actor.playerSession);
|
||||
@@ -59,7 +61,7 @@ namespace Content.Server.GameObjects.Components.Arcade
|
||||
else _spectators.Add(session);
|
||||
|
||||
UpdatePlayerStatus(session);
|
||||
_game.UpdateNewPlayerUI(session);
|
||||
_game?.UpdateNewPlayerUI(session);
|
||||
}
|
||||
|
||||
private void DeactivePlayer(IPlayerSession session)
|
||||
@@ -104,38 +106,56 @@ namespace Content.Server.GameObjects.Components.Arcade
|
||||
{
|
||||
UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
|
||||
}
|
||||
|
||||
if (_powerReceiverComponent != null)
|
||||
{
|
||||
_powerReceiverComponent.OnPowerStateChanged += OnPowerStateChanged;
|
||||
}
|
||||
_game = new BlockGame(this);
|
||||
}
|
||||
|
||||
private void OnPowerStateChanged(object? sender, PowerStateEventArgs e)
|
||||
{
|
||||
if (e.Powered) return;
|
||||
|
||||
UserInterface?.CloseAll();
|
||||
_player = null;
|
||||
_spectators.Clear();
|
||||
}
|
||||
|
||||
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage obj)
|
||||
{
|
||||
if (obj.Message is BlockGameMessages.BlockGameUserUnregisterMessage unregisterMessage)
|
||||
switch (obj.Message)
|
||||
{
|
||||
case BlockGameMessages.BlockGameUserUnregisterMessage unregisterMessage:
|
||||
UnRegisterPlayerSession(obj.Session);
|
||||
return;
|
||||
}
|
||||
if (obj.Session != _player) return;
|
||||
break;
|
||||
case BlockGameMessages.BlockGamePlayerActionMessage playerActionMessage:
|
||||
if (obj.Session != _player) break;
|
||||
|
||||
if (!ActionBlockerSystem.CanInteract(Owner))
|
||||
{
|
||||
DeactivePlayer(obj.Session);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!(obj.Message is BlockGameMessages.BlockGamePlayerActionMessage message)) return;
|
||||
if (message.PlayerAction == BlockGamePlayerAction.NewGame)
|
||||
if (playerActionMessage.PlayerAction == BlockGamePlayerAction.NewGame)
|
||||
{
|
||||
if(_game.Started) _game = new BlockGame(this);
|
||||
_game.StartGame();
|
||||
if(_game?.Started == true) _game = new BlockGame(this);
|
||||
_game?.StartGame();
|
||||
}
|
||||
else
|
||||
{
|
||||
_game.ProcessInput(message.PlayerAction);
|
||||
_game?.ProcessInput(playerActionMessage.PlayerAction);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void DoGameTick(float frameTime)
|
||||
{
|
||||
_game.GameTick(frameTime);
|
||||
_game?.GameTick(frameTime);
|
||||
}
|
||||
|
||||
private class BlockGame
|
||||
@@ -196,13 +216,12 @@ namespace Content.Server.GameObjects.Components.Arcade
|
||||
|
||||
private Vector2i _currentPiecePosition;
|
||||
private BlockGamePieceRotation _currentRotation;
|
||||
private float _softDropOverride = 0.1f;
|
||||
private float _softDropModifier = 0.1f;
|
||||
|
||||
private float Speed => !_softDropPressed
|
||||
? -0.03f * Level + 1
|
||||
: _softDropOverride;
|
||||
private float Speed =>
|
||||
-0.03f * Level + 1 * (!_softDropPressed ? 1 : _softDropModifier);
|
||||
|
||||
private float _pressCheckSpeed = 0.08f;
|
||||
private const float _pressCheckSpeed = 0.08f;
|
||||
|
||||
private bool _running;
|
||||
public bool Paused => !(_running && _started);
|
||||
@@ -278,7 +297,8 @@ namespace Content.Server.GameObjects.Components.Arcade
|
||||
public BlockGame(BlockGameArcadeComponent component)
|
||||
{
|
||||
_component = component;
|
||||
_internalNextPiece = BlockGamePiece.GetRandom(_component._random);
|
||||
_allBlockGamePieces = (BlockGamePieceType[]) Enum.GetValues(typeof(BlockGamePieceType));
|
||||
_internalNextPiece = GetRandomBlockGamePiece(_component._random);
|
||||
}
|
||||
|
||||
private void SendHighscoreUpdate()
|
||||
@@ -343,7 +363,7 @@ namespace Content.Server.GameObjects.Components.Arcade
|
||||
{
|
||||
_accumulatedLeftPressTime += frameTime;
|
||||
|
||||
if (_accumulatedLeftPressTime >= _pressCheckSpeed)
|
||||
while (_accumulatedLeftPressTime >= _pressCheckSpeed)
|
||||
{
|
||||
|
||||
if (_currentPiece.Positions(_currentPiecePosition.AddToX(-1), _currentRotation)
|
||||
@@ -361,7 +381,7 @@ namespace Content.Server.GameObjects.Components.Arcade
|
||||
{
|
||||
_accumulatedRightPressTime += frameTime;
|
||||
|
||||
if (_accumulatedRightPressTime >= _pressCheckSpeed)
|
||||
while (_accumulatedRightPressTime >= _pressCheckSpeed)
|
||||
{
|
||||
if (_currentPiece.Positions(_currentPiecePosition.AddToX(1), _currentRotation)
|
||||
.All(MoveCheck))
|
||||
@@ -384,14 +404,15 @@ namespace Content.Server.GameObjects.Components.Arcade
|
||||
|
||||
var checkTime = Speed;
|
||||
|
||||
if (_accumulatedFieldFrameTime < checkTime) return;
|
||||
|
||||
if(_softDropPressed) AddPoints(1);
|
||||
while (_accumulatedFieldFrameTime >= checkTime)
|
||||
{
|
||||
if (_softDropPressed) AddPoints(1);
|
||||
|
||||
InternalFieldTick();
|
||||
|
||||
_accumulatedFieldFrameTime -= checkTime;
|
||||
}
|
||||
}
|
||||
|
||||
private void InternalFieldTick()
|
||||
{
|
||||
@@ -490,7 +511,7 @@ namespace Content.Server.GameObjects.Components.Arcade
|
||||
private void InitializeNewBlock()
|
||||
{
|
||||
InitializeNewBlock(_nextPiece);
|
||||
_nextPiece = BlockGamePiece.GetRandom(_component._random);
|
||||
_nextPiece = GetRandomBlockGamePiece(_component._random);
|
||||
_holdBlock = false;
|
||||
|
||||
_component.UserInterface?.SendMessage(new BlockGameMessages.BlockGameVisualUpdateMessage(_nextPiece.BlocksForPreview(), BlockGameMessages.BlockGameVisualType.NextBlock));
|
||||
@@ -538,6 +559,7 @@ namespace Content.Server.GameObjects.Components.Arcade
|
||||
break;
|
||||
case BlockGamePlayerAction.SoftdropStart:
|
||||
_softDropPressed = true;
|
||||
if (_accumulatedFieldFrameTime > Speed) _accumulatedFieldFrameTime = Speed; //to prevent jumps
|
||||
break;
|
||||
case BlockGamePlayerAction.SoftdropEnd:
|
||||
_softDropPressed = false;
|
||||
@@ -707,6 +729,22 @@ namespace Content.Server.GameObjects.Components.Arcade
|
||||
};
|
||||
}
|
||||
|
||||
private readonly BlockGamePieceType[] _allBlockGamePieces;
|
||||
|
||||
private List<BlockGamePieceType> _blockGamePiecesBuffer = new List<BlockGamePieceType>();
|
||||
|
||||
private BlockGamePiece GetRandomBlockGamePiece(IRobustRandom random)
|
||||
{
|
||||
if (_blockGamePiecesBuffer.Count == 0)
|
||||
{
|
||||
_blockGamePiecesBuffer = _allBlockGamePieces.ToList();
|
||||
}
|
||||
|
||||
var chosenPiece = random.Pick(_blockGamePiecesBuffer);
|
||||
_blockGamePiecesBuffer.Remove(chosenPiece);
|
||||
return BlockGamePiece.GetPiece(chosenPiece);
|
||||
}
|
||||
|
||||
private struct BlockGamePiece
|
||||
{
|
||||
public Vector2i[] Offsets;
|
||||
@@ -770,13 +808,6 @@ namespace Content.Server.GameObjects.Components.Arcade
|
||||
return Blocks(new Vector2i(-xOffset, -yOffset), BlockGamePieceRotation.North);
|
||||
}
|
||||
|
||||
public static BlockGamePiece GetRandom(IRobustRandom random)
|
||||
{
|
||||
var pieces = (BlockGamePieceType[])Enum.GetValues(typeof(BlockGamePieceType));
|
||||
var choice = random.Pick(pieces);
|
||||
return GetPiece(choice);
|
||||
}
|
||||
|
||||
public static BlockGamePiece GetPiece(BlockGamePieceType type)
|
||||
{
|
||||
//switch statement, hardcoded offsets
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using Robust.Server.Interfaces.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Random;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Arcade
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class RandomArcadeGameComponent : Component, IMapInit
|
||||
{
|
||||
public override string Name => "RandomArcade";
|
||||
|
||||
public void MapInit()
|
||||
{
|
||||
var arcades = new[]
|
||||
{
|
||||
"BlockGameArcade",
|
||||
"SpaceVillainArcade"
|
||||
};
|
||||
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
|
||||
entityManager.SpawnEntity(
|
||||
IoCManager.Resolve<IRobustRandom>().Pick(arcades),
|
||||
Owner.Transform.Coordinates);
|
||||
|
||||
Owner.Delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,16 +5,20 @@ using Content.Server.GameObjects.Components.VendingMachines;
|
||||
using Content.Server.Utility;
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using Content.Shared.GameObjects.Components.Arcade;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Robust.Server.GameObjects.Components.UserInterface;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
using Robust.Server.Interfaces.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.ComponentDependencies;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Random;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
@@ -27,7 +31,10 @@ namespace Content.Server.GameObjects.Components.Arcade
|
||||
{
|
||||
[Dependency] private IRobustRandom _random = null!;
|
||||
|
||||
private bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered;
|
||||
[ComponentDependency] private PowerReceiverComponent? _powerReceiverComponent = default!;
|
||||
[ComponentDependency] private WiresComponent? _wiresComponent = default!;
|
||||
|
||||
private bool Powered => _powerReceiverComponent != null && _powerReceiverComponent.Powered;
|
||||
|
||||
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(SpaceVillainArcadeUiKey.Key);
|
||||
[ViewVariables] private bool _overflowFlag;
|
||||
@@ -73,11 +80,11 @@ namespace Content.Server.GameObjects.Components.Arcade
|
||||
{
|
||||
return;
|
||||
}
|
||||
if(!ActionBlockerSystem.CanInteract(actor.playerSession.AttachedEntity)) return;
|
||||
|
||||
var wires = Owner.GetComponent<WiresComponent>();
|
||||
if (wires.IsPanelOpen)
|
||||
if (_wiresComponent?.IsPanelOpen == true)
|
||||
{
|
||||
wires.OpenInterface(actor.playerSession);
|
||||
_wiresComponent.OpenInterface(actor.playerSession);
|
||||
} else
|
||||
{
|
||||
UserInterface?.Toggle(actor.playerSession);
|
||||
@@ -92,6 +99,18 @@ namespace Content.Server.GameObjects.Components.Arcade
|
||||
{
|
||||
UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
|
||||
}
|
||||
|
||||
if (_powerReceiverComponent != null)
|
||||
{
|
||||
_powerReceiverComponent.OnPowerStateChanged += OnOnPowerStateChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnOnPowerStateChanged(object? sender, PowerStateEventArgs e)
|
||||
{
|
||||
if(e.Powered) return;
|
||||
|
||||
UserInterface?.CloseAll();
|
||||
}
|
||||
|
||||
|
||||
@@ -145,6 +164,10 @@ namespace Content.Server.GameObjects.Components.Arcade
|
||||
builder.CreateWire(Wires.Overflow);
|
||||
builder.CreateWire(Wires.PlayerInvincible);
|
||||
builder.CreateWire(Wires.EnemyInvincible);
|
||||
builder.CreateWire(4);
|
||||
builder.CreateWire(5);
|
||||
builder.CreateWire(6);
|
||||
IndicatorUpdate();
|
||||
}
|
||||
|
||||
public void WiresUpdate(WiresUpdateEventArgs args)
|
||||
@@ -163,6 +186,24 @@ namespace Content.Server.GameObjects.Components.Arcade
|
||||
_enemyInvincibilityFlag = value;
|
||||
break;
|
||||
}
|
||||
|
||||
IndicatorUpdate();
|
||||
}
|
||||
|
||||
public void IndicatorUpdate()
|
||||
{
|
||||
_wiresComponent?.SetStatus(Indicators.HealthManager,
|
||||
new SharedWiresComponent.StatusLightData(Color.Purple,
|
||||
_playerInvincibilityFlag || _enemyInvincibilityFlag
|
||||
? SharedWiresComponent.StatusLightState.BlinkingSlow
|
||||
: SharedWiresComponent.StatusLightState.On,
|
||||
"MNGR"));
|
||||
_wiresComponent?.SetStatus(Indicators.HealthLimiter,
|
||||
new SharedWiresComponent.StatusLightData(Color.Red,
|
||||
_overflowFlag
|
||||
? SharedWiresComponent.StatusLightState.BlinkingSlow
|
||||
: SharedWiresComponent.StatusLightState.On,
|
||||
"LIMT"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -257,7 +298,7 @@ namespace Content.Server.GameObjects.Components.Arcade
|
||||
{
|
||||
case PlayerAction.Attack:
|
||||
var attackAmount = _random.Next(2, 6);
|
||||
_latestPlayerActionMessage = $"You attack {_enemyName} for {attackAmount}!";
|
||||
_latestPlayerActionMessage = Loc.GetString("You attack {0} for {1}!", _enemyName, attackAmount);
|
||||
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Effects/Arcade/player_attack.ogg", Owner.Owner, AudioParams.Default.WithVolume(-4f));
|
||||
if(!Owner._enemyInvincibilityFlag) _enemyHp -= attackAmount;
|
||||
_turtleTracker -= _turtleTracker > 0 ? 1 : 0;
|
||||
@@ -265,24 +306,23 @@ namespace Content.Server.GameObjects.Components.Arcade
|
||||
case PlayerAction.Heal:
|
||||
var pointAmount = _random.Next(1, 3);
|
||||
var healAmount = _random.Next(6, 8);
|
||||
_latestPlayerActionMessage = $"You use {pointAmount} magic to heal for {healAmount} damage!";
|
||||
_latestPlayerActionMessage = Loc.GetString("You use {0} magic to heal for {1} damage!", pointAmount, healAmount);
|
||||
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Effects/Arcade/player_heal.ogg", Owner.Owner, AudioParams.Default.WithVolume(-4f));
|
||||
if(!Owner._playerInvincibilityFlag) _playerMp -= pointAmount;
|
||||
_playerHp += healAmount;
|
||||
_turtleTracker++;
|
||||
break;
|
||||
case PlayerAction.Recharge:
|
||||
var charge_amount = _random.Next(4, 7);
|
||||
_latestPlayerActionMessage = $"You regain {charge_amount} points";
|
||||
var chargeAmount = _random.Next(4, 7);
|
||||
_latestPlayerActionMessage = Loc.GetString("You regain {0} points", chargeAmount);
|
||||
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Effects/Arcade/player_charge.ogg", Owner.Owner, AudioParams.Default.WithVolume(-4f));
|
||||
_playerMp += charge_amount;
|
||||
_playerMp += chargeAmount;
|
||||
_turtleTracker -= _turtleTracker > 0 ? 1 : 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!CheckGameConditions())
|
||||
{
|
||||
_running = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -291,7 +331,6 @@ namespace Content.Server.GameObjects.Components.Arcade
|
||||
|
||||
if (!CheckGameConditions())
|
||||
{
|
||||
_running = false;
|
||||
return;
|
||||
}
|
||||
ValidateVars();
|
||||
@@ -304,22 +343,28 @@ namespace Content.Server.GameObjects.Components.Arcade
|
||||
/// <returns>A bool indicating if the game should continue.</returns>
|
||||
private bool CheckGameConditions()
|
||||
{
|
||||
if ((_enemyHp <= 0 || _enemyMp <= 0) && (_playerHp > 0 && _playerMp > 0))
|
||||
if ((_playerHp > 0 && _playerMp > 0) && (_enemyHp <= 0 || _enemyMp <= 0))
|
||||
{
|
||||
UpdateUi("You won!", $"{_enemyName} dies.");
|
||||
_running = false;
|
||||
UpdateUi(Loc.GetString("You won!"), Loc.GetString("{0} dies.", _enemyName), true);
|
||||
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Effects/Arcade/win.ogg", Owner.Owner, AudioParams.Default.WithVolume(-4f));
|
||||
Owner.ProcessWin();
|
||||
return false;
|
||||
}
|
||||
if ((_playerHp <= 0 || _playerMp <= 0) && _enemyHp > 0 && _enemyMp > 0)
|
||||
|
||||
if (_playerHp > 0 && _playerMp > 0) return true;
|
||||
|
||||
if ((_enemyHp > 0 && _enemyMp > 0))
|
||||
{
|
||||
UpdateUi("You lost!", $"{_enemyName} cheers.");
|
||||
_running = false;
|
||||
UpdateUi(Loc.GetString("You lost!"), Loc.GetString("{0} cheers.", _enemyName), true);
|
||||
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Effects/Arcade/gameover.ogg", Owner.Owner, AudioParams.Default.WithVolume(-4f));
|
||||
return false;
|
||||
}
|
||||
if ((_playerHp <= 0 || _playerMp <= 0) && (_enemyHp <= 0 || _enemyMp <= 0))
|
||||
if (_enemyHp <= 0 || _enemyMp <= 0)
|
||||
{
|
||||
UpdateUi("You lost!", $"{_enemyName} dies, but takes you with him.");
|
||||
_running = false;
|
||||
UpdateUi(Loc.GetString("You lost!"), Loc.GetString("{0} dies, but takes you with him.", _enemyName), true);
|
||||
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Effects/Arcade/gameover.ogg", Owner.Owner, AudioParams.Default.WithVolume(-4f));
|
||||
return false;
|
||||
}
|
||||
@@ -330,16 +375,16 @@ namespace Content.Server.GameObjects.Components.Arcade
|
||||
/// <summary>
|
||||
/// Updates the UI.
|
||||
/// </summary>
|
||||
private void UpdateUi()
|
||||
private void UpdateUi(bool metadata = false)
|
||||
{
|
||||
Owner.UserInterface?.SendMessage(GenerateUpdateMessage(_latestPlayerActionMessage, _latestEnemyActionMessage));
|
||||
Owner.UserInterface?.SendMessage(metadata ? GenerateMetaDataMessage() : GenerateUpdateMessage());
|
||||
}
|
||||
|
||||
private void UpdateUi(string message1, string message2)
|
||||
private void UpdateUi(string message1, string message2, bool metadata = false)
|
||||
{
|
||||
_latestPlayerActionMessage = message1;
|
||||
_latestEnemyActionMessage = message2;
|
||||
UpdateUi();
|
||||
UpdateUi(metadata);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -351,14 +396,14 @@ namespace Content.Server.GameObjects.Components.Arcade
|
||||
if (_turtleTracker >= 4)
|
||||
{
|
||||
var boomAmount = _random.Next(5, 10);
|
||||
_latestEnemyActionMessage = $"{_enemyName} throws a bomb, exploding you for {boomAmount} damage!";
|
||||
_latestEnemyActionMessage = Loc.GetString("{0} throws a bomb, exploding you for {1} damage!", _enemyName, boomAmount);
|
||||
if (Owner._playerInvincibilityFlag) return;
|
||||
_playerHp -= boomAmount;
|
||||
_turtleTracker--;
|
||||
}else if (_enemyMp <= 5 && _random.Prob(0.7f))
|
||||
{
|
||||
var stealAmount = _random.Next(2, 3);
|
||||
_latestEnemyActionMessage = $"{_enemyName} steals {stealAmount} of your power!";
|
||||
_latestEnemyActionMessage = Loc.GetString("{0} steals {1} of your power!", _enemyName, stealAmount);
|
||||
if (Owner._playerInvincibilityFlag) return;
|
||||
_playerMp -= stealAmount;
|
||||
_enemyMp += stealAmount;
|
||||
@@ -366,12 +411,13 @@ namespace Content.Server.GameObjects.Components.Arcade
|
||||
{
|
||||
_enemyHp += 4;
|
||||
_enemyMp -= 4;
|
||||
_latestEnemyActionMessage = $"{_enemyName} heals for 4 health!";
|
||||
_latestEnemyActionMessage = Loc.GetString("{0} heals for 4 health!", _enemyName);
|
||||
}
|
||||
else
|
||||
{
|
||||
var attackAmount = _random.Next(3, 6);
|
||||
_latestEnemyActionMessage = $"{_enemyName} attacks you for {attackAmount} damage!";
|
||||
_latestEnemyActionMessage =
|
||||
Loc.GetString("{0} attacks you for {1} damage!", _enemyName, attackAmount);
|
||||
if (Owner._playerInvincibilityFlag) return;
|
||||
_playerHp -= attackAmount;
|
||||
}
|
||||
@@ -383,20 +429,18 @@ namespace Content.Server.GameObjects.Components.Arcade
|
||||
/// <returns>A Metadata-message.</returns>
|
||||
public SpaceVillainArcadeMetaDataUpdateMessage GenerateMetaDataMessage()
|
||||
{
|
||||
return new SpaceVillainArcadeMetaDataUpdateMessage(_playerHp, _playerMp, _enemyHp, _enemyMp, _latestPlayerActionMessage, _latestEnemyActionMessage, Name, _enemyName);
|
||||
return new SpaceVillainArcadeMetaDataUpdateMessage(_playerHp, _playerMp, _enemyHp, _enemyMp, _latestPlayerActionMessage, _latestEnemyActionMessage, Name, _enemyName, !_running);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an Update-message based on the objects values.
|
||||
/// </summary>
|
||||
/// <param name="playerAction">Content of the Playeraction-field.</param>
|
||||
/// <param name="enemyAction">Content of the Enemyaction-field.</param>
|
||||
/// <returns></returns>
|
||||
/// <returns>An Update-Message.</returns>
|
||||
public SpaceVillainArcadeDataUpdateMessage
|
||||
GenerateUpdateMessage(string playerAction = "", string enemyAction = "")
|
||||
GenerateUpdateMessage()
|
||||
{
|
||||
return new SpaceVillainArcadeDataUpdateMessage(_playerHp, _playerMp, _enemyHp, _enemyMp, playerAction,
|
||||
enemyAction);
|
||||
return new SpaceVillainArcadeDataUpdateMessage(_playerHp, _playerMp, _enemyHp, _enemyMp, _latestPlayerActionMessage,
|
||||
_latestEnemyActionMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
#nullable enable
|
||||
using Content.Server.GameObjects.Components.Body.Respiratory;
|
||||
using Content.Shared.GameObjects.Components.Inventory;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Npgsql.TypeHandlers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Atmos
|
||||
{
|
||||
/// <summary>
|
||||
/// Used in internals as breath tool.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class BreathToolComponent : Component, IEquipped, IUnequipped
|
||||
{
|
||||
/// <summary>
|
||||
/// Tool is functional only in allowed slots
|
||||
/// </summary>
|
||||
private EquipmentSlotDefines.SlotFlags _allowedSlots;
|
||||
|
||||
public override string Name => "BreathMask";
|
||||
public bool IsFunctional { get; private set; }
|
||||
public IEntity? ConnectedInternalsEntity { get; private set; }
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
serializer.DataField(ref _allowedSlots, "allowedSlots", EquipmentSlotDefines.SlotFlags.MASK);
|
||||
}
|
||||
|
||||
protected override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
DisconnectInternals();
|
||||
}
|
||||
|
||||
public void Equipped(EquippedEventArgs eventArgs)
|
||||
{
|
||||
if ((EquipmentSlotDefines.SlotMasks[eventArgs.Slot] & _allowedSlots) != _allowedSlots) return;
|
||||
IsFunctional = true;
|
||||
|
||||
if (eventArgs.User.TryGetComponent(out InternalsComponent? internals))
|
||||
{
|
||||
ConnectedInternalsEntity = eventArgs.User;
|
||||
internals.ConnectBreathTool(Owner);
|
||||
}
|
||||
}
|
||||
|
||||
public void Unequipped(UnequippedEventArgs eventArgs)
|
||||
{
|
||||
DisconnectInternals();
|
||||
|
||||
}
|
||||
|
||||
public void DisconnectInternals()
|
||||
{
|
||||
var old = ConnectedInternalsEntity;
|
||||
ConnectedInternalsEntity = null;
|
||||
|
||||
if (old != null && old.TryGetComponent<InternalsComponent>(out var internalsComponent))
|
||||
{
|
||||
internalsComponent.DisconnectBreathTool();
|
||||
}
|
||||
|
||||
IsFunctional = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components;
|
||||
using Robust.Shared.GameObjects.Components.Timers;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -205,7 +206,7 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
Owner.PopupMessage(Loc.GetString("You stop, drop, and roll!"));
|
||||
stunnable.Paralyze(2f);
|
||||
|
||||
Timer.Spawn(2000, () =>
|
||||
Owner.SpawnTimer(2000, () =>
|
||||
{
|
||||
_resisting = false;
|
||||
FireStacks -= 2f;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.Interfaces;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
@@ -6,23 +7,19 @@ using Robust.Shared.ViewVariables;
|
||||
namespace Content.Server.GameObjects.Components.Atmos
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class GasMixtureHolderComponent : Component
|
||||
public class GasMixtureHolderComponent : Component, IGasMixtureHolder
|
||||
{
|
||||
public override string Name => "GasMixtureHolder";
|
||||
|
||||
[ViewVariables] public GasMixture GasMixture { get; set; }
|
||||
[ViewVariables] public GasMixture Air { get; set; }
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
GasMixture = new GasMixture();
|
||||
Air = new GasMixture();
|
||||
|
||||
serializer.DataReadWriteFunction(
|
||||
"volume",
|
||||
0f,
|
||||
vol => GasMixture.Volume = vol,
|
||||
() => GasMixture.Volume);
|
||||
serializer.DataField(this, x => x.Air, "air", new GasMixture());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,356 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
#nullable enable
|
||||
using System;
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.Explosions;
|
||||
using Content.Server.GameObjects.Components.Body.Respiratory;
|
||||
using Content.Server.GameObjects.Components.GUI;
|
||||
using Content.Server.Interfaces;
|
||||
using Content.Server.Utility;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.GameObjects.Components.Atmos.GasTank;
|
||||
using Content.Shared.GameObjects.Components.Inventory;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.Verbs;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Content.Shared.Utility;
|
||||
using Robust.Server.GameObjects.Components.UserInterface;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
using Robust.Server.Interfaces.GameObjects;
|
||||
using Robust.Server.Interfaces.Player;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.EntitySystemMessages;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Atmos
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class GasTankComponent : Component
|
||||
[ComponentReference(typeof(IActivate))]
|
||||
public class GasTankComponent : SharedGasTankComponent, IExamine, IGasMixtureHolder, IUse, IDropped, IActivate
|
||||
{
|
||||
public override string Name => "GasTank";
|
||||
private const float MaxExplosionRange = 14f;
|
||||
private const float DefaultOutputPressure = Atmospherics.OneAtmosphere;
|
||||
|
||||
private float _pressureResistance;
|
||||
|
||||
private int _integrity = 3;
|
||||
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[ViewVariables] private BoundUserInterface? _userInterface;
|
||||
|
||||
[ViewVariables] public GasMixture? Air { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Distributed pressure.
|
||||
/// </summary>
|
||||
[ViewVariables] public float OutputPressure { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Tank is connected to internals.
|
||||
/// </summary>
|
||||
[ViewVariables] public bool IsConnected { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Represents that tank is functional and can be connected to internals.
|
||||
/// </summary>
|
||||
public bool IsFunctional => GetInternalsComponent() != null;
|
||||
|
||||
/// <summary>
|
||||
/// Pressure at which tanks start leaking.
|
||||
/// </summary>
|
||||
public float TankLeakPressure { get; set; } = 30 * Atmospherics.OneAtmosphere;
|
||||
|
||||
/// <summary>
|
||||
/// Pressure at which tank spills all contents into atmosphere.
|
||||
/// </summary>
|
||||
public float TankRupturePressure { get; set; } = 40 * Atmospherics.OneAtmosphere;
|
||||
|
||||
/// <summary>
|
||||
/// Base 3x3 explosion.
|
||||
/// </summary>
|
||||
public float TankFragmentPressure { get; set; } = 50 * Atmospherics.OneAtmosphere;
|
||||
|
||||
/// <summary>
|
||||
/// Increases explosion for each scale kPa above threshold.
|
||||
/// </summary>
|
||||
public float TankFragmentScale { get; set; } = 10 * Atmospherics.OneAtmosphere;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_userInterface = Owner.GetUIOrNull(SharedGasTankUiKey.Key);
|
||||
if (_userInterface != null)
|
||||
{
|
||||
_userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenInterface(IPlayerSession session)
|
||||
{
|
||||
_userInterface?.Open(session);
|
||||
UpdateUserInterface(true);
|
||||
}
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataField(this, x => x.Air, "air", new GasMixture());
|
||||
serializer.DataField(this, x => x.OutputPressure, "outputPressure", DefaultOutputPressure);
|
||||
serializer.DataField(this, x => x.TankLeakPressure, "tankLeakPressure", 30 * Atmospherics.OneAtmosphere);
|
||||
serializer.DataField(this, x => x.TankRupturePressure, "tankRupturePressure", 40 * Atmospherics.OneAtmosphere);
|
||||
serializer.DataField(this, x => x.TankFragmentPressure, "tankFragmentPressure", 50 * Atmospherics.OneAtmosphere);
|
||||
serializer.DataField(this, x => x.TankFragmentScale, "tankFragmentScale", 10 * Atmospherics.OneAtmosphere);
|
||||
serializer.DataField(ref _pressureResistance, "pressureResistance", Atmospherics.OneAtmosphere * 5f);
|
||||
}
|
||||
|
||||
public void Examine(FormattedMessage message, bool inDetailsRange)
|
||||
{
|
||||
message.AddMarkup(Loc.GetString("Pressure: [color=orange]{0}[/color] kPa.\n",
|
||||
Math.Round(Air?.Pressure ?? 0)));
|
||||
if (IsConnected)
|
||||
{
|
||||
message.AddMarkup(Loc.GetString("Connected to external component"));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
DisconnectFromInternals();
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
Air?.React(this);
|
||||
CheckStatus();
|
||||
UpdateUserInterface();
|
||||
}
|
||||
|
||||
public GasMixture? RemoveAir(float amount)
|
||||
{
|
||||
var gas = Air?.Remove(amount);
|
||||
CheckStatus();
|
||||
return gas;
|
||||
}
|
||||
|
||||
public GasMixture RemoveAirVolume(float volume)
|
||||
{
|
||||
if (Air == null)
|
||||
return new GasMixture(volume);
|
||||
|
||||
var tankPressure = Air.Pressure;
|
||||
if (tankPressure < OutputPressure)
|
||||
{
|
||||
OutputPressure = tankPressure;
|
||||
UpdateUserInterface();
|
||||
}
|
||||
|
||||
var molesNeeded = OutputPressure * volume / (Atmospherics.R * Air.Temperature);
|
||||
|
||||
var air = RemoveAir(molesNeeded);
|
||||
|
||||
if (air != null)
|
||||
air.Volume = volume;
|
||||
else
|
||||
return new GasMixture(volume);
|
||||
|
||||
return air;
|
||||
}
|
||||
|
||||
public bool UseEntity(UseEntityEventArgs eventArgs)
|
||||
{
|
||||
if (!eventArgs.User.TryGetComponent(out IActorComponent? actor)) return false;
|
||||
OpenInterface(actor.playerSession);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Activate(ActivateEventArgs eventArgs)
|
||||
{
|
||||
if (!eventArgs.User.TryGetComponent(out IActorComponent? actor)) return;
|
||||
OpenInterface(actor.playerSession);
|
||||
}
|
||||
|
||||
public void ConnectToInternals()
|
||||
{
|
||||
if (IsConnected || !IsFunctional) return;
|
||||
var internals = GetInternalsComponent();
|
||||
if (internals == null) return;
|
||||
IsConnected = internals.TryConnectTank(Owner);
|
||||
UpdateUserInterface();
|
||||
}
|
||||
|
||||
public void DisconnectFromInternals(IEntity? owner = null)
|
||||
{
|
||||
if (!IsConnected) return;
|
||||
IsConnected = false;
|
||||
GetInternalsComponent(owner)?.DisconnectTank();
|
||||
UpdateUserInterface();
|
||||
}
|
||||
|
||||
private void UpdateUserInterface(bool initialUpdate = false)
|
||||
{
|
||||
_userInterface?.SetState(
|
||||
new GasTankBoundUserInterfaceState
|
||||
{
|
||||
TankPressure = Air?.Pressure ?? 0,
|
||||
OutputPressure = initialUpdate ? OutputPressure : (float?) null,
|
||||
InternalsConnected = IsConnected,
|
||||
CanConnectInternals = IsFunctional && GetInternalsComponent() != null
|
||||
});
|
||||
}
|
||||
|
||||
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message)
|
||||
{
|
||||
switch (message.Message)
|
||||
{
|
||||
case GasTankSetPressureMessage msg:
|
||||
OutputPressure = msg.Pressure;
|
||||
break;
|
||||
case GasTankToggleInternalsMessage _:
|
||||
ToggleInternals();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void ToggleInternals()
|
||||
{
|
||||
if (IsConnected)
|
||||
{
|
||||
DisconnectFromInternals();
|
||||
return;
|
||||
}
|
||||
|
||||
ConnectToInternals();
|
||||
}
|
||||
|
||||
private InternalsComponent? GetInternalsComponent(IEntity? owner = null)
|
||||
{
|
||||
if (owner != null) return owner.GetComponentOrNull<InternalsComponent>();
|
||||
return ContainerHelpers.TryGetContainer(Owner, out var container)
|
||||
? container.Owner.GetComponentOrNull<InternalsComponent>()
|
||||
: null;
|
||||
}
|
||||
|
||||
public void AssumeAir(GasMixture giver)
|
||||
{
|
||||
Air?.Merge(giver);
|
||||
CheckStatus();
|
||||
}
|
||||
|
||||
private void CheckStatus()
|
||||
{
|
||||
if (Air == null)
|
||||
return;
|
||||
|
||||
var pressure = Air.Pressure;
|
||||
|
||||
if (pressure > TankFragmentPressure)
|
||||
{
|
||||
// Give the gas a chance to build up more pressure.
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
Air.React(this);
|
||||
}
|
||||
|
||||
pressure = Air.Pressure;
|
||||
var range = (pressure - TankFragmentPressure) / TankFragmentScale;
|
||||
|
||||
// Let's cap the explosion, yeah?
|
||||
if (range > MaxExplosionRange)
|
||||
{
|
||||
range = MaxExplosionRange;
|
||||
}
|
||||
|
||||
Owner.SpawnExplosion((int) (range * 0.25f), (int) (range * 0.5f), (int) (range * 1.5f), 1);
|
||||
|
||||
Owner.Delete();
|
||||
return;
|
||||
}
|
||||
|
||||
if (pressure > TankRupturePressure)
|
||||
{
|
||||
if (_integrity <= 0)
|
||||
{
|
||||
var tileAtmos = Owner.Transform.Coordinates.GetTileAtmosphere();
|
||||
tileAtmos?.AssumeAir(Air);
|
||||
|
||||
EntitySystem.Get<AudioSystem>().PlayAtCoords("Audio/Effects/spray.ogg", Owner.Transform.Coordinates,
|
||||
AudioHelpers.WithVariation(0.125f));
|
||||
|
||||
Owner.Delete();
|
||||
return;
|
||||
}
|
||||
|
||||
_integrity--;
|
||||
return;
|
||||
}
|
||||
|
||||
if (pressure > TankLeakPressure)
|
||||
{
|
||||
if (_integrity <= 0)
|
||||
{
|
||||
var tileAtmos = Owner.Transform.Coordinates.GetTileAtmosphere();
|
||||
if (tileAtmos == null)
|
||||
return;
|
||||
|
||||
var leakedGas = Air.RemoveRatio(0.25f);
|
||||
tileAtmos.AssumeAir(leakedGas);
|
||||
}
|
||||
else
|
||||
{
|
||||
_integrity--;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (_integrity < 3)
|
||||
_integrity++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Open interaction window
|
||||
/// </summary>
|
||||
[Verb]
|
||||
private sealed class ControlVerb : Verb<GasTankComponent>
|
||||
{
|
||||
public override bool RequireInteractionRange => true;
|
||||
|
||||
protected override void GetData(IEntity user, GasTankComponent component, VerbData data)
|
||||
{
|
||||
data.Visibility = VerbVisibility.Invisible;
|
||||
if (!user.HasComponent<IActorComponent>())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
data.Visibility = VerbVisibility.Visible;
|
||||
data.Text = "Open Control Panel";
|
||||
}
|
||||
|
||||
protected override void Activate(IEntity user, GasTankComponent component)
|
||||
{
|
||||
if (!user.TryGetComponent<IActorComponent>(out var actor))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
component.OpenInterface(actor.playerSession);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dropped(DroppedEventArgs eventArgs)
|
||||
{
|
||||
DisconnectFromInternals(eventArgs.User);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.GameObjects.Components.Atmos;
|
||||
using Content.Server.GameObjects.Components.Body.Circulatory;
|
||||
using Content.Server.GameObjects.Components.Body.Respiratory;
|
||||
using Content.Server.Utility;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.GameObjects.Components.Body.Behavior;
|
||||
@@ -10,6 +12,7 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.Timing;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
@@ -147,6 +150,16 @@ namespace Content.Server.GameObjects.Components.Body.Behavior
|
||||
|
||||
public override void Inhale(float frameTime)
|
||||
{
|
||||
if (Body != null && Body.Owner.TryGetComponent(out InternalsComponent? internals)
|
||||
&& internals.BreathToolEntity != null && internals.GasTankEntity != null
|
||||
&& internals.BreathToolEntity.TryGetComponent(out BreathToolComponent? breathTool)
|
||||
&& breathTool.IsFunctional && internals.GasTankEntity.TryGetComponent(out GasTankComponent? gasTank)
|
||||
&& gasTank.Air != null)
|
||||
{
|
||||
Inhale(frameTime, gasTank.RemoveAirVolume(Atmospherics.BreathVolume));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Owner.Transform.Coordinates.TryGetTileAir(out var tileAir))
|
||||
{
|
||||
return;
|
||||
@@ -157,8 +170,7 @@ namespace Content.Server.GameObjects.Components.Body.Behavior
|
||||
|
||||
public void Inhale(float frameTime, GasMixture from)
|
||||
{
|
||||
var ratio = Atmospherics.BreathPercentage * frameTime;
|
||||
|
||||
var ratio = (Atmospherics.BreathVolume / from.Volume) * frameTime;
|
||||
|
||||
Transfer(from, Air, ratio);
|
||||
ToBloodstream(Air);
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
#nullable enable
|
||||
using Content.Server.GameObjects.Components.Atmos;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Body.Respiratory
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class InternalsComponent : Component
|
||||
{
|
||||
public override string Name => "Internals";
|
||||
[ViewVariables] public IEntity? GasTankEntity { get; set; }
|
||||
[ViewVariables] public IEntity? BreathToolEntity { get; set; }
|
||||
|
||||
public void DisconnectBreathTool()
|
||||
{
|
||||
var old = BreathToolEntity;
|
||||
BreathToolEntity = null;
|
||||
|
||||
if (old != null && old.TryGetComponent(out BreathToolComponent? breathTool) )
|
||||
{
|
||||
breathTool.DisconnectInternals();
|
||||
DisconnectTank();
|
||||
}
|
||||
}
|
||||
|
||||
public void ConnectBreathTool(IEntity toolEntity)
|
||||
{
|
||||
if (BreathToolEntity != null && BreathToolEntity.TryGetComponent(out BreathToolComponent? tool))
|
||||
{
|
||||
tool.DisconnectInternals();
|
||||
}
|
||||
|
||||
BreathToolEntity = toolEntity;
|
||||
}
|
||||
|
||||
public void DisconnectTank()
|
||||
{
|
||||
if (GasTankEntity != null && GasTankEntity.TryGetComponent(out GasTankComponent? tank))
|
||||
{
|
||||
tank.DisconnectFromInternals(Owner);
|
||||
}
|
||||
|
||||
GasTankEntity = null;
|
||||
}
|
||||
|
||||
public bool TryConnectTank(IEntity tankEntity)
|
||||
{
|
||||
if (BreathToolEntity == null)
|
||||
return false;
|
||||
|
||||
if (GasTankEntity != null && GasTankEntity.TryGetComponent(out GasTankComponent? tank))
|
||||
{
|
||||
tank.DisconnectFromInternals(Owner);
|
||||
}
|
||||
|
||||
GasTankEntity = tankEntity;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -460,6 +460,7 @@ namespace Content.Server.GameObjects.Components.Buckle
|
||||
if (Moved)
|
||||
{
|
||||
TryUnbuckle(Owner, true);
|
||||
Moved = false;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
118
Content.Server/GameObjects/Components/ConfigurationComponent.cs
Normal file
118
Content.Server/GameObjects/Components/ConfigurationComponent.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
using Content.Server.GameObjects.Components.Interactable;
|
||||
using Content.Server.Utility;
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using Content.Shared.GameObjects.Components.Interactable;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Robust.Server.GameObjects.Components.UserInterface;
|
||||
using Robust.Server.Interfaces.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Content.Server.GameObjects.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(SharedConfigurationComponent))]
|
||||
public class ConfigurationComponent : SharedConfigurationComponent, IInteractUsing
|
||||
{
|
||||
[ViewVariables] private BoundUserInterface UserInterface => Owner.GetUIOrNull(ConfigurationUiKey.Key);
|
||||
|
||||
[ViewVariables]
|
||||
private readonly Dictionary<string, string> _config = new Dictionary<string, string>();
|
||||
|
||||
private Regex _validation;
|
||||
|
||||
public event Action<Dictionary<string, string>> OnConfigUpdate;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
if (UserInterface != null)
|
||||
{
|
||||
UserInterface.OnReceiveMessage += UserInterfaceOnReceiveMessage;
|
||||
}
|
||||
}
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataReadWriteFunction("keys", new List<string>(),
|
||||
(list) => FillConfiguration(list, _config, ""),
|
||||
() => _config.Keys.ToList());
|
||||
|
||||
serializer.DataReadFunction("vailidation", "^[a-zA-Z0-9 ]*$", value => _validation = new Regex("^[a-zA-Z0-9 ]*$", RegexOptions.Compiled));
|
||||
}
|
||||
|
||||
public string GetConfig(string name)
|
||||
{
|
||||
return _config.GetValueOrDefault(name);
|
||||
}
|
||||
|
||||
protected override void Startup()
|
||||
{
|
||||
base.Startup();
|
||||
UpdateUserInterface();
|
||||
}
|
||||
|
||||
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
{
|
||||
if (UserInterface == null || !eventArgs.User.TryGetComponent(out IActorComponent actor))
|
||||
return false;
|
||||
|
||||
if (!eventArgs.Using.TryGetComponent<ToolComponent>(out var tool))
|
||||
return false;
|
||||
|
||||
if (!await tool.UseTool(eventArgs.User, Owner, 0.2f, ToolQuality.Multitool))
|
||||
return false;
|
||||
|
||||
UpdateUserInterface();
|
||||
UserInterface.Open(actor.playerSession);
|
||||
UserInterface.SendMessage(new ValidationUpdateMessage(_validation.ToString()), actor.playerSession);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void UserInterfaceOnReceiveMessage(ServerBoundUserInterfaceMessage serverMsg)
|
||||
{
|
||||
var message = serverMsg.Message;
|
||||
var config = new Dictionary<string, string>(_config);
|
||||
|
||||
if (message is ConfigurationUpdatedMessage msg)
|
||||
{
|
||||
foreach (var key in config.Keys)
|
||||
{
|
||||
var value = msg.Config.GetValueOrDefault(key);
|
||||
|
||||
if (_validation != null && !_validation.IsMatch(value) && value != "")
|
||||
continue;
|
||||
|
||||
_config[key] = value;
|
||||
}
|
||||
|
||||
OnConfigUpdate(_config);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateUserInterface()
|
||||
{
|
||||
if (UserInterface == null)
|
||||
return;
|
||||
|
||||
UserInterface.SetState(new ConfigurationBoundUserInterfaceState(_config));
|
||||
}
|
||||
|
||||
private static void FillConfiguration<T>(List<string> list, Dictionary<string, T> configuration, T value){
|
||||
for (var index = 0; index < list.Count; index++)
|
||||
{
|
||||
configuration.Add(list[index], value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -155,7 +155,7 @@ namespace Content.Server.GameObjects.Components.Conveyor
|
||||
if (entity.TryGetComponent(out IPhysicsComponent? physics))
|
||||
{
|
||||
var controller = physics.EnsureController<ConveyedController>();
|
||||
controller.Move(direction, _speed * frameTime);
|
||||
controller.Move(direction, _speed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
@@ -7,6 +8,7 @@ using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Random;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Damage
|
||||
@@ -65,11 +67,6 @@ namespace Content.Server.GameObjects.Components.Damage
|
||||
protected override void DestructionBehavior()
|
||||
{
|
||||
_actSystem.HandleBreakage(Owner);
|
||||
if (!Owner.Deleted && DestroySound != string.Empty)
|
||||
{
|
||||
var pos = Owner.Transform.Coordinates;
|
||||
EntitySystem.Get<AudioSystem>().PlayAtCoords(DestroySound, pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.GameObjects.Components.Stack;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
using Content.Shared.Utility;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Random;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Damage
|
||||
@@ -17,6 +20,8 @@ namespace Content.Server.GameObjects.Components.Damage
|
||||
public class DestructibleComponent : RuinableComponent, IDestroyAct
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
protected ActSystem ActSystem;
|
||||
|
||||
@@ -24,22 +29,51 @@ namespace Content.Server.GameObjects.Components.Damage
|
||||
public override string Name => "Destructible";
|
||||
|
||||
/// <summary>
|
||||
/// Entity spawned upon destruction.
|
||||
/// Entities spawned on destruction plus the min and max amount spawned.
|
||||
/// </summary>
|
||||
public string SpawnOnDestroy { get; private set; }
|
||||
public Dictionary<string, MinMax> SpawnOnDestroy { get; private set; }
|
||||
|
||||
void IDestroyAct.OnDestroy(DestructionEventArgs eventArgs)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(SpawnOnDestroy) && eventArgs.IsSpawnWreck)
|
||||
if (SpawnOnDestroy == null || !eventArgs.IsSpawnWreck) return;
|
||||
foreach (var (key, value) in SpawnOnDestroy)
|
||||
{
|
||||
Owner.EntityManager.SpawnEntity(SpawnOnDestroy, Owner.Transform.Coordinates);
|
||||
int count;
|
||||
if (value.Min >= value.Max)
|
||||
{
|
||||
count = value.Min;
|
||||
}
|
||||
else
|
||||
{
|
||||
count = _random.Next(value.Min, value.Max + 1);
|
||||
}
|
||||
|
||||
if (count == 0) continue;
|
||||
|
||||
if (EntityPrototypeHelpers.HasComponent<StackComponent>(key))
|
||||
{
|
||||
var spawned = Owner.EntityManager.SpawnEntity(key, Owner.Transform.Coordinates);
|
||||
var stack = spawned.GetComponent<StackComponent>();
|
||||
stack.Count = count;
|
||||
spawned.RandomOffset(0.5f);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var spawned = Owner.EntityManager.SpawnEntity(key, Owner.Transform.Coordinates);
|
||||
spawned.RandomOffset(0.5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
serializer.DataField(this, d => d.SpawnOnDestroy, "spawnOnDestroy", string.Empty);
|
||||
|
||||
|
||||
serializer.DataField(this, d => d.SpawnOnDestroy, "spawnOnDestroy", null);
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
@@ -56,11 +90,13 @@ namespace Content.Server.GameObjects.Components.Damage
|
||||
var pos = Owner.Transform.Coordinates;
|
||||
ActSystem.HandleDestruction(Owner,
|
||||
true); //This will call IDestroyAct.OnDestroy on this component (and all other components on this entity)
|
||||
if (DestroySound != string.Empty)
|
||||
}
|
||||
}
|
||||
|
||||
public struct MinMax
|
||||
{
|
||||
EntitySystem.Get<AudioSystem>().PlayAtCoords(DestroySound, pos);
|
||||
}
|
||||
}
|
||||
public int Min;
|
||||
public int Max;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Random;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using Logger = Robust.Shared.Log.Logger;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Damage
|
||||
{
|
||||
@@ -16,12 +20,19 @@ namespace Content.Server.GameObjects.Components.Damage
|
||||
[ComponentReference(typeof(IDamageableComponent))]
|
||||
public abstract class RuinableComponent : DamageableComponent
|
||||
{
|
||||
[Dependency] private IRobustRandom _random = default!;
|
||||
/// <summary>
|
||||
/// Sound played upon destruction.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
protected string DestroySound { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Used instead of <see cref="DestroySound"/> if specified.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
protected string DestroySoundCollection { get; private set; }
|
||||
|
||||
public override List<DamageState> SupportedDamageStates =>
|
||||
new List<DamageState> {DamageState.Alive, DamageState.Dead};
|
||||
|
||||
@@ -44,6 +55,7 @@ namespace Content.Server.GameObjects.Components.Damage
|
||||
() => Thresholds.TryGetValue(DamageState.Dead, out var value) ? value : (int?) null);
|
||||
|
||||
serializer.DataField(this, ruinable => ruinable.DestroySound, "destroySound", string.Empty);
|
||||
serializer.DataField(this, ruinable => ruinable.DestroySoundCollection, "destroySoundCollection", string.Empty);
|
||||
}
|
||||
|
||||
protected override void EnterState(DamageState state)
|
||||
@@ -65,10 +77,24 @@ namespace Content.Server.GameObjects.Components.Damage
|
||||
{
|
||||
CurrentState = DamageState.Dead;
|
||||
|
||||
if (!Owner.Deleted && DestroySound != string.Empty)
|
||||
if (!Owner.Deleted)
|
||||
{
|
||||
var pos = Owner.Transform.Coordinates;
|
||||
EntitySystem.Get<AudioSystem>().PlayAtCoords(DestroySound, pos);
|
||||
string sound = string.Empty;
|
||||
if (DestroySoundCollection != string.Empty)
|
||||
{
|
||||
sound = AudioHelpers.GetRandomFileFromSoundCollection(DestroySoundCollection);
|
||||
|
||||
}
|
||||
else if (DestroySound != string.Empty)
|
||||
{
|
||||
sound = DestroySound;
|
||||
}
|
||||
if (sound != string.Empty)
|
||||
{
|
||||
Logger.Debug("Playing destruction sound");
|
||||
EntitySystem.Get<AudioSystem>().PlayAtCoords(sound, pos, AudioHelpers.WithVariation(0.125f));
|
||||
}
|
||||
}
|
||||
|
||||
DestructionBehavior();
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace Content.Server.GameObjects.Components.Disposal
|
||||
return TryInsert(holderComponent);
|
||||
}
|
||||
|
||||
private bool TryInsert(DisposalHolderComponent holder)
|
||||
public bool TryInsert(DisposalHolderComponent holder)
|
||||
{
|
||||
if (!Contents.Insert(holder.Owner))
|
||||
{
|
||||
|
||||
@@ -0,0 +1,840 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components.GUI;
|
||||
using Content.Server.GameObjects.Components.Items.Storage;
|
||||
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
|
||||
using Content.Server.GameObjects.EntitySystems.DeviceNetwork;
|
||||
using Content.Server.GameObjects.EntitySystems.DoAfter;
|
||||
using Content.Server.Interfaces;
|
||||
using Content.Server.Interfaces.GameObjects.Components.Items;
|
||||
using Content.Server.Utility;
|
||||
using Content.Shared.GameObjects.Components.Body;
|
||||
using Content.Shared.GameObjects.Components.Disposal;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.Verbs;
|
||||
using Content.Shared.Interfaces;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.GameObjects.Components.Container;
|
||||
using Robust.Server.GameObjects.Components.UserInterface;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
using Robust.Server.Interfaces.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components;
|
||||
using Robust.Shared.GameObjects.Components.Transform;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Timing;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using Timer = Robust.Shared.Timers.Timer;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Disposal
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(SharedDisposalMailingUnitComponent))]
|
||||
[ComponentReference(typeof(IActivate))]
|
||||
[ComponentReference(typeof(IInteractUsing))]
|
||||
public class DisposalMailingUnitComponent : SharedDisposalMailingUnitComponent, IInteractHand, IActivate, IInteractUsing, IDragDropOn
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
|
||||
private const string HolderPrototypeId = "DisposalHolder";
|
||||
|
||||
/// <summary>
|
||||
/// The delay for an entity trying to move out of this unit.
|
||||
/// </summary>
|
||||
private static readonly TimeSpan ExitAttemptDelay = TimeSpan.FromSeconds(0.5);
|
||||
|
||||
/// <summary>
|
||||
/// Last time that an entity tried to exit this disposal unit.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
private TimeSpan _lastExitAttempt;
|
||||
|
||||
public static readonly Regex TagRegex = new Regex("^[a-zA-Z0-9, ]*$", RegexOptions.Compiled);
|
||||
|
||||
/// <summary>
|
||||
/// The current pressure of this disposal unit.
|
||||
/// Prevents it from flushing if it is not equal to or bigger than 1.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
private float _pressure;
|
||||
|
||||
private bool _engaged;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
private TimeSpan _automaticEngageTime;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
private TimeSpan _flushDelay;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
private float _entryDelay;
|
||||
|
||||
/// <summary>
|
||||
/// Token used to cancel the automatic engage of a disposal unit
|
||||
/// after an entity enters it.
|
||||
/// </summary>
|
||||
private CancellationTokenSource? _automaticEngageToken;
|
||||
|
||||
/// <summary>
|
||||
/// Container of entities inside this disposal unit.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
private Container _container = default!;
|
||||
|
||||
[ViewVariables]
|
||||
private WiredNetworkConnection? _connection;
|
||||
|
||||
[ViewVariables] public IReadOnlyList<IEntity> ContainedEntities => _container.ContainedEntities;
|
||||
|
||||
[ViewVariables]
|
||||
private readonly List<string> _targetList = new List<string>();
|
||||
|
||||
[ViewVariables]
|
||||
private string _target = "";
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
private string _tag = "";
|
||||
|
||||
[ViewVariables]
|
||||
public bool Powered =>
|
||||
!Owner.TryGetComponent(out PowerReceiverComponent? receiver) ||
|
||||
receiver.Powered;
|
||||
|
||||
[ViewVariables]
|
||||
private PressureState State => _pressure >= 1 ? PressureState.Ready : PressureState.Pressurizing;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
private bool Engaged
|
||||
{
|
||||
get => _engaged;
|
||||
set
|
||||
{
|
||||
var oldEngaged = _engaged;
|
||||
_engaged = value;
|
||||
|
||||
if (oldEngaged == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateVisualState();
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(DisposalMailingUnitUiKey.Key);
|
||||
|
||||
private DisposalMailingUnitBoundUserInterfaceState? _lastUiState;
|
||||
|
||||
/// <summary>
|
||||
/// Store the translated state.
|
||||
/// </summary>
|
||||
private (PressureState State, string Localized) _locState;
|
||||
|
||||
public bool CanInsert(IEntity entity)
|
||||
{
|
||||
if (!Anchored)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!entity.TryGetComponent(out IPhysicsComponent? physics) ||
|
||||
!physics.CanCollide)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!entity.HasComponent<ItemComponent>() &&
|
||||
!entity.HasComponent<IBody>())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return _container.CanInsert(entity);
|
||||
}
|
||||
|
||||
private void TryQueueEngage()
|
||||
{
|
||||
if (!Powered && ContainedEntities.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_automaticEngageToken = new CancellationTokenSource();
|
||||
|
||||
Timer.Spawn(_automaticEngageTime, () =>
|
||||
{
|
||||
if (!TryFlush())
|
||||
{
|
||||
TryQueueEngage();
|
||||
}
|
||||
}, _automaticEngageToken.Token);
|
||||
}
|
||||
|
||||
private void AfterInsert(IEntity entity)
|
||||
{
|
||||
TryQueueEngage();
|
||||
|
||||
if (entity.TryGetComponent(out IActorComponent? actor))
|
||||
{
|
||||
UserInterface?.Close(actor.playerSession);
|
||||
}
|
||||
|
||||
UpdateVisualState();
|
||||
}
|
||||
|
||||
public async Task<bool> TryInsert(IEntity entity, IEntity? user = default)
|
||||
{
|
||||
if (!CanInsert(entity))
|
||||
return false;
|
||||
|
||||
if (user != null && _entryDelay > 0f)
|
||||
{
|
||||
var doAfterSystem = EntitySystem.Get<DoAfterSystem>();
|
||||
|
||||
var doAfterArgs = new DoAfterEventArgs(user, _entryDelay, default, Owner)
|
||||
{
|
||||
BreakOnDamage = true,
|
||||
BreakOnStun = true,
|
||||
BreakOnTargetMove = true,
|
||||
BreakOnUserMove = true,
|
||||
NeedHand = false,
|
||||
};
|
||||
|
||||
var result = await doAfterSystem.DoAfter(doAfterArgs);
|
||||
|
||||
if (result == DoAfterStatus.Cancelled)
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
if (!_container.Insert(entity))
|
||||
return false;
|
||||
|
||||
AfterInsert(entity);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryDrop(IEntity user, IEntity entity)
|
||||
{
|
||||
if (!user.TryGetComponent(out HandsComponent? hands))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CanInsert(entity) || !hands.Drop(entity, _container))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
AfterInsert(entity);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void Remove(IEntity entity)
|
||||
{
|
||||
_container.Remove(entity);
|
||||
|
||||
if (ContainedEntities.Count == 0)
|
||||
{
|
||||
_automaticEngageToken?.Cancel();
|
||||
_automaticEngageToken = null;
|
||||
}
|
||||
|
||||
UpdateVisualState();
|
||||
}
|
||||
|
||||
private bool CanFlush()
|
||||
{
|
||||
return _pressure >= 1 && Powered && Anchored;
|
||||
}
|
||||
|
||||
private void ToggleEngage()
|
||||
{
|
||||
Engaged ^= true;
|
||||
|
||||
if (Engaged && CanFlush())
|
||||
{
|
||||
Timer.Spawn(_flushDelay, () => TryFlush());
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryFlush()
|
||||
{
|
||||
if (!CanFlush())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var snapGrid = Owner.GetComponent<SnapGridComponent>();
|
||||
var entry = snapGrid
|
||||
.GetLocal()
|
||||
.FirstOrDefault(entity => entity.HasComponent<DisposalEntryComponent>());
|
||||
|
||||
if (entry == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var entryComponent = entry.GetComponent<DisposalEntryComponent>();
|
||||
var entities = _container.ContainedEntities.ToList();
|
||||
foreach (var entity in _container.ContainedEntities.ToList())
|
||||
{
|
||||
_container.Remove(entity);
|
||||
}
|
||||
|
||||
var holder = CreateTaggedHolder(entities, _target);
|
||||
|
||||
entryComponent.TryInsert(holder);
|
||||
|
||||
_automaticEngageToken?.Cancel();
|
||||
_automaticEngageToken = null;
|
||||
|
||||
_pressure = 0;
|
||||
|
||||
Engaged = false;
|
||||
|
||||
UpdateVisualState(true);
|
||||
UpdateInterface();
|
||||
|
||||
if (_connection != null)
|
||||
{
|
||||
var data = new Dictionary<string, string>
|
||||
{
|
||||
{ NetworkUtils.COMMAND, NET_CMD_SENT },
|
||||
{ NET_SRC, _tag },
|
||||
{ NET_TARGET, _target }
|
||||
};
|
||||
|
||||
_connection.Broadcast(_connection.Frequency, data);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private DisposalHolderComponent CreateTaggedHolder(IReadOnlyCollection<IEntity> entities, string tag)
|
||||
{
|
||||
var holder = Owner.EntityManager.SpawnEntity(HolderPrototypeId, Owner.Transform.MapPosition);
|
||||
var holderComponent = holder.GetComponent<DisposalHolderComponent>();
|
||||
|
||||
holderComponent.Tags.Add(tag);
|
||||
holderComponent.Tags.Add(TAGS_MAIL);
|
||||
|
||||
foreach (var entity in entities)
|
||||
{
|
||||
holderComponent.TryInsert(entity);
|
||||
}
|
||||
|
||||
return holderComponent;
|
||||
}
|
||||
|
||||
private void UpdateTargetList()
|
||||
{
|
||||
_targetList.Clear();
|
||||
var payload = new Dictionary<string, string>
|
||||
{
|
||||
{ NetworkUtils.COMMAND, NET_CMD_REQUEST }
|
||||
};
|
||||
|
||||
_connection?.Broadcast(_connection.Frequency, payload);
|
||||
}
|
||||
|
||||
private void TryEjectContents()
|
||||
{
|
||||
foreach (var entity in _container.ContainedEntities.ToArray())
|
||||
{
|
||||
Remove(entity);
|
||||
}
|
||||
}
|
||||
|
||||
private void TogglePower()
|
||||
{
|
||||
if (!Owner.TryGetComponent(out PowerReceiverComponent? receiver))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
receiver.PowerDisabled = !receiver.PowerDisabled;
|
||||
UpdateInterface();
|
||||
}
|
||||
|
||||
private DisposalMailingUnitBoundUserInterfaceState GetInterfaceState()
|
||||
{
|
||||
string stateString;
|
||||
|
||||
if (_locState.State != State)
|
||||
{
|
||||
stateString = Loc.GetString($"{State}");
|
||||
_locState = (State, stateString);
|
||||
}
|
||||
else
|
||||
{
|
||||
stateString = _locState.Localized;
|
||||
}
|
||||
|
||||
return new DisposalMailingUnitBoundUserInterfaceState(Owner.Name, stateString, _pressure, Powered, Engaged, _tag, _targetList, _target);
|
||||
}
|
||||
|
||||
private void UpdateInterface(bool checkEqual = true)
|
||||
{
|
||||
var state = GetInterfaceState();
|
||||
|
||||
if (checkEqual && _lastUiState != null && _lastUiState.Equals(state))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_lastUiState = state;
|
||||
UserInterface?.SetState((DisposalMailingUnitBoundUserInterfaceState) state.Clone());
|
||||
}
|
||||
|
||||
private bool PlayerCanUse(IEntity? player)
|
||||
{
|
||||
if (player == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ActionBlockerSystem.CanInteract(player) ||
|
||||
!ActionBlockerSystem.CanUse(player))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnUiReceiveMessage(ServerBoundUserInterfaceMessage obj)
|
||||
{
|
||||
if (obj.Session.AttachedEntity == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!PlayerCanUse(obj.Session.AttachedEntity))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (obj.Message is UiButtonPressedMessage buttonMessage)
|
||||
{
|
||||
switch (buttonMessage.Button)
|
||||
{
|
||||
case UiButton.Eject:
|
||||
TryEjectContents();
|
||||
break;
|
||||
case UiButton.Engage:
|
||||
ToggleEngage();
|
||||
break;
|
||||
case UiButton.Power:
|
||||
TogglePower();
|
||||
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Machines/machine_switch.ogg", Owner, AudioParams.Default.WithVolume(-2f));
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
if (obj.Message is UiTargetUpdateMessage tagMessage && TagRegex.IsMatch(tagMessage.Target))
|
||||
{
|
||||
_target = tagMessage.Target;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnConfigUpdate(Dictionary<string, string> config)
|
||||
{
|
||||
if (config.TryGetValue("Tag", out var tag))
|
||||
_tag = tag;
|
||||
}
|
||||
|
||||
private void UpdateVisualState()
|
||||
{
|
||||
UpdateVisualState(false);
|
||||
}
|
||||
|
||||
private void UpdateVisualState(bool flush)
|
||||
{
|
||||
if (!Owner.TryGetComponent(out AppearanceComponent? appearance))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!Anchored)
|
||||
{
|
||||
appearance.SetData(Visuals.VisualState, VisualState.UnAnchored);
|
||||
appearance.SetData(Visuals.Handle, HandleState.Normal);
|
||||
appearance.SetData(Visuals.Light, LightState.Off);
|
||||
return;
|
||||
}
|
||||
else if (_pressure < 1)
|
||||
{
|
||||
appearance.SetData(Visuals.VisualState, VisualState.Charging);
|
||||
}
|
||||
else
|
||||
{
|
||||
appearance.SetData(Visuals.VisualState, VisualState.Anchored);
|
||||
}
|
||||
|
||||
appearance.SetData(Visuals.Handle, Engaged
|
||||
? HandleState.Engaged
|
||||
: HandleState.Normal);
|
||||
|
||||
if (!Powered)
|
||||
{
|
||||
appearance.SetData(Visuals.Light, LightState.Off);
|
||||
return;
|
||||
}
|
||||
|
||||
if (flush)
|
||||
{
|
||||
appearance.SetData(Visuals.VisualState, VisualState.Flushing);
|
||||
appearance.SetData(Visuals.Light, LightState.Off);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ContainedEntities.Count > 0)
|
||||
{
|
||||
appearance.SetData(Visuals.Light, LightState.Full);
|
||||
return;
|
||||
}
|
||||
|
||||
appearance.SetData(Visuals.Light, _pressure < 1
|
||||
? LightState.Charging
|
||||
: LightState.Ready);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
if (!Powered)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var oldPressure = _pressure;
|
||||
|
||||
_pressure = _pressure + frameTime > 1
|
||||
? 1
|
||||
: _pressure + 0.05f * frameTime;
|
||||
|
||||
if (oldPressure < 1 && _pressure >= 1)
|
||||
{
|
||||
UpdateVisualState();
|
||||
|
||||
if (Engaged)
|
||||
{
|
||||
TryFlush();
|
||||
}
|
||||
}
|
||||
|
||||
UpdateInterface();
|
||||
}
|
||||
|
||||
private void PowerStateChanged(object? sender, PowerStateEventArgs args)
|
||||
{
|
||||
if (!args.Powered)
|
||||
{
|
||||
_automaticEngageToken?.Cancel();
|
||||
_automaticEngageToken = null;
|
||||
}
|
||||
|
||||
UpdateVisualState();
|
||||
|
||||
if (Engaged && !TryFlush())
|
||||
{
|
||||
TryQueueEngage();
|
||||
}
|
||||
}
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataReadWriteFunction(
|
||||
"pressure",
|
||||
1.0f,
|
||||
pressure => _pressure = pressure,
|
||||
() => _pressure);
|
||||
|
||||
serializer.DataReadWriteFunction(
|
||||
"automaticEngageTime",
|
||||
30,
|
||||
seconds => _automaticEngageTime = TimeSpan.FromSeconds(seconds),
|
||||
() => (int) _automaticEngageTime.TotalSeconds);
|
||||
|
||||
serializer.DataReadWriteFunction(
|
||||
"flushDelay",
|
||||
3,
|
||||
seconds => _flushDelay = TimeSpan.FromSeconds(seconds),
|
||||
() => (int) _flushDelay.TotalSeconds);
|
||||
|
||||
serializer.DataReadWriteFunction(
|
||||
"entryDelay",
|
||||
0.5f,
|
||||
seconds => _entryDelay = seconds,
|
||||
() => (int) _entryDelay);
|
||||
|
||||
serializer.DataField(ref _tag, "Tag", "");
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_container = ContainerManagerComponent.Ensure<Container>(Name, Owner);
|
||||
|
||||
if (UserInterface != null)
|
||||
{
|
||||
UserInterface.OnReceiveMessage += OnUiReceiveMessage;
|
||||
}
|
||||
|
||||
var network = IoCManager.Resolve<IDeviceNetwork>();
|
||||
_connection = new WiredNetworkConnection(OnReceiveNetMessage, false, Owner);
|
||||
|
||||
if (Owner.TryGetComponent<ConfigurationComponent>(out var configuration))
|
||||
configuration.OnConfigUpdate += OnConfigUpdate;
|
||||
|
||||
UpdateInterface();
|
||||
}
|
||||
|
||||
protected override void Startup()
|
||||
{
|
||||
base.Startup();
|
||||
|
||||
if(!Owner.HasComponent<AnchorableComponent>())
|
||||
{
|
||||
Logger.WarningS("VitalComponentMissing", $"Disposal unit {Owner.Uid} is missing an anchorable component");
|
||||
}
|
||||
|
||||
if (Owner.TryGetComponent(out IPhysicsComponent? physics))
|
||||
{
|
||||
physics.AnchoredChanged += UpdateVisualState;
|
||||
}
|
||||
|
||||
if (Owner.TryGetComponent(out PowerReceiverComponent? receiver))
|
||||
{
|
||||
receiver.OnPowerStateChanged += PowerStateChanged;
|
||||
}
|
||||
|
||||
UpdateTargetList();
|
||||
UpdateVisualState();
|
||||
}
|
||||
|
||||
public override void OnRemove()
|
||||
{
|
||||
if (Owner.TryGetComponent(out IPhysicsComponent? physics))
|
||||
{
|
||||
physics.AnchoredChanged -= UpdateVisualState;
|
||||
}
|
||||
|
||||
if (Owner.TryGetComponent(out PowerReceiverComponent? receiver))
|
||||
{
|
||||
receiver.OnPowerStateChanged -= PowerStateChanged;
|
||||
}
|
||||
|
||||
if (_container != null)
|
||||
{
|
||||
foreach (var entity in _container.ContainedEntities.ToArray())
|
||||
{
|
||||
_container.ForceRemove(entity);
|
||||
}
|
||||
}
|
||||
|
||||
UserInterface?.CloseAll();
|
||||
|
||||
_automaticEngageToken?.Cancel();
|
||||
_automaticEngageToken = null;
|
||||
|
||||
_container = null!;
|
||||
|
||||
_connection!.Close();
|
||||
|
||||
base.OnRemove();
|
||||
}
|
||||
|
||||
public override void HandleMessage(ComponentMessage message, IComponent? component)
|
||||
{
|
||||
base.HandleMessage(message, component);
|
||||
|
||||
switch (message)
|
||||
{
|
||||
case RelayMovementEntityMessage msg:
|
||||
if (!msg.Entity.TryGetComponent(out HandsComponent? hands) ||
|
||||
hands.Count == 0 ||
|
||||
_gameTiming.CurTime < _lastExitAttempt + ExitAttemptDelay)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
_lastExitAttempt = _gameTiming.CurTime;
|
||||
Remove(msg.Entity);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnReceiveNetMessage(int frequency, string sender, IReadOnlyDictionary<string, string> payload, object _, bool broadcast)
|
||||
{
|
||||
if (payload.TryGetValue(NetworkUtils.COMMAND, out var command) && Powered)
|
||||
{
|
||||
if (command == NET_CMD_RESPONSE && payload.TryGetValue(NET_TAG, out var tag))
|
||||
{
|
||||
_targetList.Add(tag);
|
||||
UpdateInterface(false);
|
||||
}
|
||||
|
||||
if (command == NET_CMD_REQUEST)
|
||||
{
|
||||
if (_tag == "" || !Powered)
|
||||
return;
|
||||
|
||||
var data = new Dictionary<string, string>
|
||||
{
|
||||
{NetworkUtils.COMMAND, NET_CMD_RESPONSE},
|
||||
{NET_TAG, _tag}
|
||||
};
|
||||
|
||||
_connection?.Send(frequency, sender, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsValidInteraction(ITargetedInteractEventArgs eventArgs)
|
||||
{
|
||||
if (!ActionBlockerSystem.CanInteract(eventArgs.User))
|
||||
{
|
||||
Owner.PopupMessage(eventArgs.User, Loc.GetString("You can't do that!"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ContainerHelpers.IsInContainer(eventArgs.User))
|
||||
{
|
||||
Owner.PopupMessage(eventArgs.User, Loc.GetString("You can't reach there!"));
|
||||
return false;
|
||||
}
|
||||
// This popup message doesn't appear on clicks, even when code was seperate. Unsure why.
|
||||
|
||||
if (!eventArgs.User.HasComponent<IHandsComponent>())
|
||||
{
|
||||
Owner.PopupMessage(eventArgs.User, Loc.GetString("You have no hands!"));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool IInteractHand.InteractHand(InteractHandEventArgs eventArgs)
|
||||
{
|
||||
if (!eventArgs.User.TryGetComponent(out IActorComponent? actor))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Duplicated code here, not sure how else to get actor inside to make UserInterface happy.
|
||||
|
||||
if (IsValidInteraction(eventArgs))
|
||||
{
|
||||
UpdateTargetList();
|
||||
UpdateInterface(false);
|
||||
UserInterface?.Open(actor.playerSession);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void IActivate.Activate(ActivateEventArgs eventArgs)
|
||||
{
|
||||
if (!eventArgs.User.TryGetComponent(out IActorComponent? actor))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsValidInteraction(eventArgs))
|
||||
{
|
||||
UserInterface?.Open(actor.playerSession);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
{
|
||||
return TryDrop(eventArgs.User, eventArgs.Using);
|
||||
}
|
||||
|
||||
bool IDragDropOn.CanDragDropOn(DragDropEventArgs eventArgs)
|
||||
{
|
||||
return CanInsert(eventArgs.Dragged);
|
||||
}
|
||||
|
||||
bool IDragDropOn.DragDropOn(DragDropEventArgs eventArgs)
|
||||
{
|
||||
_ = TryInsert(eventArgs.Dragged, eventArgs.User);
|
||||
return true;
|
||||
}
|
||||
|
||||
[Verb]
|
||||
private sealed class SelfInsertVerb : Verb<DisposalMailingUnitComponent>
|
||||
{
|
||||
protected override void GetData(IEntity user, DisposalMailingUnitComponent component, VerbData data)
|
||||
{
|
||||
data.Visibility = VerbVisibility.Invisible;
|
||||
|
||||
if (!ActionBlockerSystem.CanInteract(user) ||
|
||||
component.ContainedEntities.Contains(user))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
data.Visibility = VerbVisibility.Visible;
|
||||
data.Text = Loc.GetString("Jump inside");
|
||||
}
|
||||
|
||||
protected override void Activate(IEntity user, DisposalMailingUnitComponent component)
|
||||
{
|
||||
_ = component.TryInsert(user, user);
|
||||
}
|
||||
}
|
||||
|
||||
[Verb]
|
||||
private sealed class FlushVerb : Verb<DisposalMailingUnitComponent>
|
||||
{
|
||||
protected override void GetData(IEntity user, DisposalMailingUnitComponent component, VerbData data)
|
||||
{
|
||||
data.Visibility = VerbVisibility.Invisible;
|
||||
|
||||
if (!ActionBlockerSystem.CanInteract(user) ||
|
||||
component.ContainedEntities.Contains(user))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
data.Visibility = VerbVisibility.Visible;
|
||||
data.Text = Loc.GetString("Flush");
|
||||
}
|
||||
|
||||
protected override void Activate(IEntity user, DisposalMailingUnitComponent component)
|
||||
{
|
||||
component.Engaged = true;
|
||||
component.TryFlush();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,12 +6,14 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components.GUI;
|
||||
using Content.Server.GameObjects.Components.Items.Storage;
|
||||
using Content.Server.GameObjects.Components.Mobs.State;
|
||||
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
|
||||
using Content.Server.GameObjects.Components.Projectiles;
|
||||
using Content.Server.GameObjects.EntitySystems.DoAfter;
|
||||
using Content.Server.Interfaces.GameObjects.Components.Items;
|
||||
using Content.Server.Utility;
|
||||
using Content.Shared.GameObjects.Components.Body;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Disposal;
|
||||
using Content.Shared.GameObjects.Components.Items;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
@@ -27,6 +29,7 @@ using Robust.Shared.Audio;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components;
|
||||
using Robust.Shared.GameObjects.Components.Timers;
|
||||
using Robust.Shared.GameObjects.Components.Transform;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
@@ -136,11 +139,14 @@ namespace Content.Server.GameObjects.Components.Disposal
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (!entity.TryGetComponent(out IPhysicsComponent? physics) ||
|
||||
!physics.CanCollide)
|
||||
{
|
||||
if (!(entity.TryGetComponent(out IDamageableComponent? damageState) && damageState.CurrentState == DamageState.Dead)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!entity.HasComponent<ItemComponent>() &&
|
||||
!entity.HasComponent<IBody>())
|
||||
@@ -160,7 +166,7 @@ namespace Content.Server.GameObjects.Components.Disposal
|
||||
|
||||
_automaticEngageToken = new CancellationTokenSource();
|
||||
|
||||
Timer.Spawn(_automaticEngageTime, () =>
|
||||
Owner.SpawnTimer(_automaticEngageTime, () =>
|
||||
{
|
||||
if (!TryFlush())
|
||||
{
|
||||
@@ -255,7 +261,7 @@ namespace Content.Server.GameObjects.Components.Disposal
|
||||
|
||||
if (Engaged && CanFlush())
|
||||
{
|
||||
Timer.Spawn(_flushDelay, () => TryFlush());
|
||||
Owner.SpawnTimer(_flushDelay, () => TryFlush());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ using Robust.Server.GameObjects;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
using Robust.Server.Interfaces.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components.Timers;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -310,7 +311,7 @@ namespace Content.Server.GameObjects.Components.Doors
|
||||
PowerWiresPulsed = true;
|
||||
_powerWiresPulsedTimerCancel.Cancel();
|
||||
_powerWiresPulsedTimerCancel = new CancellationTokenSource();
|
||||
Timer.Spawn(PowerWiresTimeout,
|
||||
Owner.SpawnTimer(PowerWiresTimeout,
|
||||
() => PowerWiresPulsed = false,
|
||||
_powerWiresPulsedTimerCancel.Token);
|
||||
break;
|
||||
|
||||
@@ -23,6 +23,7 @@ using Robust.Server.GameObjects.EntitySystems;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components;
|
||||
using Robust.Shared.GameObjects.Components.Timers;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -249,7 +250,7 @@ namespace Content.Server.GameObjects.Components.Doors
|
||||
occluder.Enabled = false;
|
||||
}
|
||||
|
||||
Timer.Spawn(OpenTimeOne, async () =>
|
||||
Owner.SpawnTimer(OpenTimeOne, async () =>
|
||||
{
|
||||
if (Owner.TryGetComponent(out AirtightComponent? airtight))
|
||||
{
|
||||
@@ -319,7 +320,7 @@ namespace Content.Server.GameObjects.Components.Doors
|
||||
stun.Paralyze(DoorStunTime);
|
||||
|
||||
// If we hit someone, open up after stun (opens right when stun ends)
|
||||
Timer.Spawn(TimeSpan.FromSeconds(DoorStunTime) - OpenTimeOne - OpenTimeTwo, Open);
|
||||
Owner.SpawnTimer(TimeSpan.FromSeconds(DoorStunTime) - OpenTimeOne - OpenTimeTwo, Open);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -402,7 +403,7 @@ namespace Content.Server.GameObjects.Components.Doors
|
||||
occluder.Enabled = true;
|
||||
}
|
||||
|
||||
Timer.Spawn(CloseTimeOne, async () =>
|
||||
Owner.SpawnTimer(CloseTimeOne, async () =>
|
||||
{
|
||||
if (shouldCheckCrush && _canCrush)
|
||||
{
|
||||
@@ -435,7 +436,7 @@ namespace Content.Server.GameObjects.Components.Doors
|
||||
return;
|
||||
|
||||
SetAppearance(DoorVisualState.Deny);
|
||||
Timer.Spawn(DenyTime, () =>
|
||||
Owner.SpawnTimer(DenyTime, () =>
|
||||
{
|
||||
SetAppearance(DoorVisualState.Closed);
|
||||
}, _cancellationTokenSource.Token);
|
||||
|
||||
@@ -14,6 +14,7 @@ using Robust.Server.GameObjects.EntitySystems;
|
||||
using Robust.Server.Interfaces.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components;
|
||||
using Robust.Shared.GameObjects.Components.Timers;
|
||||
using Robust.Shared.GameObjects.Components.Transform;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
@@ -254,7 +255,7 @@ namespace Content.Server.GameObjects.Components.Fluids
|
||||
_evaporationToken = new CancellationTokenSource();
|
||||
|
||||
// KYS to evaporate
|
||||
Timer.Spawn(TimeSpan.FromSeconds(_evaporateTime), Evaporate, _evaporationToken.Token);
|
||||
Owner.SpawnTimer(TimeSpan.FromSeconds(_evaporateTime), Evaporate, _evaporationToken.Token);
|
||||
}
|
||||
|
||||
private void UpdateSlip()
|
||||
|
||||
@@ -4,12 +4,10 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Server.GameObjects.Components.Items.Storage;
|
||||
using Content.Server.GameObjects.Components.Mobs;
|
||||
using Content.Server.GameObjects.EntitySystems.Click;
|
||||
using Content.Server.Interfaces.GameObjects.Components.Items;
|
||||
using Content.Shared.GameObjects.Components.Body.Part;
|
||||
using Content.Shared.GameObjects.Components.Items;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Physics.Pull;
|
||||
using Robust.Server.GameObjects;
|
||||
@@ -18,7 +16,6 @@ using Robust.Server.GameObjects.EntitySystemMessages;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components;
|
||||
using Robust.Shared.GameObjects.Components.Transform;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Network;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -26,7 +23,6 @@ using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using Content.Server.GameObjects.Components.Pulling;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.GUI
|
||||
@@ -119,7 +115,8 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
: GetItem(ActiveHand);
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates over the hand keys, returning the active hand first.
|
||||
/// Enumerates over the enabled hand keys,
|
||||
/// returning the active hand first.
|
||||
/// </summary>
|
||||
public IEnumerable<string> ActivePriorityEnumerable()
|
||||
{
|
||||
@@ -135,6 +132,11 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!hand.Enabled)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
yield return hand.Name;
|
||||
}
|
||||
}
|
||||
@@ -205,7 +207,11 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
if (mobCheck && !ActionBlockerSystem.CanPickup(Owner))
|
||||
return false;
|
||||
|
||||
return GetHand(index)?.Container.CanInsert(item.Owner) == true;
|
||||
var hand = GetHand(index);
|
||||
|
||||
return hand != null &&
|
||||
hand.Enabled &&
|
||||
hand.Container.CanInsert(item.Owner) == true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -245,7 +251,7 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool Drop(string slot, EntityCoordinates coords, bool doMobChecks = true)
|
||||
public bool Drop(string slot, EntityCoordinates coords, bool doMobChecks = true, bool doDropInteraction = true)
|
||||
{
|
||||
var hand = GetHand(slot);
|
||||
if (!CanDrop(slot, doMobChecks) || hand?.Entity == null)
|
||||
@@ -260,7 +266,7 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!DroppedInteraction(item, false))
|
||||
if (doDropInteraction && !DroppedInteraction(item, false))
|
||||
return false;
|
||||
|
||||
item.RemovedFromSlot();
|
||||
@@ -282,7 +288,7 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Drop(IEntity entity, EntityCoordinates coords, bool doMobChecks = true)
|
||||
public bool Drop(IEntity entity, EntityCoordinates coords, bool doMobChecks = true, bool doDropInteraction = true)
|
||||
{
|
||||
if (entity == null)
|
||||
{
|
||||
@@ -294,15 +300,15 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
throw new ArgumentException("Entity must be held in one of our hands.", nameof(entity));
|
||||
}
|
||||
|
||||
return Drop(slot, coords, doMobChecks);
|
||||
return Drop(slot, coords, doMobChecks, doDropInteraction);
|
||||
}
|
||||
|
||||
public bool Drop(string slot, bool mobChecks = true)
|
||||
public bool Drop(string slot, bool mobChecks = true, bool doDropInteraction = true)
|
||||
{
|
||||
return Drop(slot, Owner.Transform.Coordinates, mobChecks);
|
||||
return Drop(slot, Owner.Transform.Coordinates, mobChecks, doDropInteraction);
|
||||
}
|
||||
|
||||
public bool Drop(IEntity entity, bool mobChecks = true)
|
||||
public bool Drop(IEntity entity, bool mobChecks = true, bool doDropInteraction = true)
|
||||
{
|
||||
if (entity == null)
|
||||
{
|
||||
@@ -314,10 +320,10 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
throw new ArgumentException("Entity must be held in one of our hands.", nameof(entity));
|
||||
}
|
||||
|
||||
return Drop(slot, Owner.Transform.Coordinates, mobChecks);
|
||||
return Drop(slot, Owner.Transform.Coordinates, mobChecks, doDropInteraction);
|
||||
}
|
||||
|
||||
public bool Drop(string slot, BaseContainer targetContainer, bool doMobChecks = true)
|
||||
public bool Drop(string slot, BaseContainer targetContainer, bool doMobChecks = true, bool doDropInteraction = true)
|
||||
{
|
||||
if (slot == null)
|
||||
{
|
||||
@@ -352,7 +358,7 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
if (!DroppedInteraction(item, doMobChecks))
|
||||
if (doDropInteraction && !DroppedInteraction(item, doMobChecks))
|
||||
return false;
|
||||
|
||||
item.RemovedFromSlot();
|
||||
@@ -368,7 +374,7 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Drop(IEntity entity, BaseContainer targetContainer, bool doMobChecks = true)
|
||||
public bool Drop(IEntity entity, BaseContainer targetContainer, bool doMobChecks = true, bool doDropInteraction = true)
|
||||
{
|
||||
if (entity == null)
|
||||
{
|
||||
@@ -380,7 +386,7 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
throw new ArgumentException("Entity must be held in one of our hands.", nameof(entity));
|
||||
}
|
||||
|
||||
return Drop(slot, targetContainer, doMobChecks);
|
||||
return Drop(slot, targetContainer, doMobChecks, doDropInteraction);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -411,7 +417,7 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
}
|
||||
|
||||
var container = ContainerManagerComponent.Create<ContainerSlot>($"hand {_nextHand++}", Owner);
|
||||
var hand = new Hand(name, container);
|
||||
var hand = new Hand(this, name, container);
|
||||
|
||||
_hands.Add(hand);
|
||||
|
||||
@@ -515,6 +521,53 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void HandleMessage(ComponentMessage message, IComponent? component)
|
||||
{
|
||||
base.HandleMessage(message, component);
|
||||
|
||||
if (message is PullMessage pullMessage &&
|
||||
pullMessage.Puller.Owner != Owner)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (message)
|
||||
{
|
||||
case PullAttemptMessage msg:
|
||||
if (!_hands.Any(hand => hand.Enabled))
|
||||
{
|
||||
msg.Cancelled = true;
|
||||
}
|
||||
|
||||
break;
|
||||
case PullStartedMessage _:
|
||||
var firstFreeHand = _hands.FirstOrDefault(hand => hand.Enabled);
|
||||
|
||||
if (firstFreeHand == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
firstFreeHand.Enabled = false;
|
||||
|
||||
break;
|
||||
case PullStoppedMessage _:
|
||||
var firstOccupiedHand = _hands.FirstOrDefault(hand => !hand.Enabled);
|
||||
|
||||
if (firstOccupiedHand == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
firstOccupiedHand.Enabled = true;
|
||||
|
||||
break;
|
||||
case HandDisabledMsg msg:
|
||||
Drop(msg.Name, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void HandleNetworkMessage(ComponentMessage message, INetChannel channel, ICommonSession? session = null)
|
||||
{
|
||||
base.HandleNetworkMessage(message, channel, session);
|
||||
@@ -625,34 +678,6 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
}
|
||||
}
|
||||
|
||||
private void AddPullingStatuses(IEntity pulled)
|
||||
{
|
||||
if (pulled.TryGetComponent(out ServerStatusEffectsComponent? pulledStatus))
|
||||
{
|
||||
pulledStatus.ChangeStatusEffectIcon(StatusEffect.Pulled,
|
||||
"/Textures/Interface/StatusEffects/Pull/pulled.png");
|
||||
}
|
||||
|
||||
if (Owner.TryGetComponent(out ServerStatusEffectsComponent? ownerStatus))
|
||||
{
|
||||
ownerStatus.ChangeStatusEffectIcon(StatusEffect.Pulling,
|
||||
"/Textures/Interface/StatusEffects/Pull/pulling.png");
|
||||
}
|
||||
}
|
||||
|
||||
private void RemovePullingStatuses(IEntity pulled)
|
||||
{
|
||||
if (pulled.TryGetComponent(out ServerStatusEffectsComponent? pulledStatus))
|
||||
{
|
||||
pulledStatus.RemoveStatusEffect(StatusEffect.Pulled);
|
||||
}
|
||||
|
||||
if (Owner.TryGetComponent(out ServerStatusEffectsComponent? ownerStatus))
|
||||
{
|
||||
ownerStatus.RemoveStatusEffect(StatusEffect.Pulling);
|
||||
}
|
||||
}
|
||||
|
||||
void IBodyPartAdded.BodyPartAdded(BodyPartAddedEventArgs args)
|
||||
{
|
||||
if (args.Part.PartType != BodyPartType.Hand)
|
||||
@@ -676,16 +701,42 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
|
||||
public class Hand : IDisposable
|
||||
{
|
||||
public Hand(string name, ContainerSlot container)
|
||||
private bool _enabled = true;
|
||||
|
||||
public Hand(HandsComponent parent, string name, ContainerSlot container)
|
||||
{
|
||||
Parent = parent;
|
||||
Name = name;
|
||||
Container = container;
|
||||
}
|
||||
|
||||
private HandsComponent Parent { get; }
|
||||
public string Name { get; }
|
||||
public IEntity? Entity => Container.ContainedEntity;
|
||||
public ContainerSlot Container { get; }
|
||||
|
||||
public bool Enabled
|
||||
{
|
||||
get => _enabled;
|
||||
set
|
||||
{
|
||||
if (_enabled == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_enabled = value;
|
||||
Parent.Dirty();
|
||||
|
||||
var message = value
|
||||
? (ComponentMessage) new HandEnabledMsg(Name)
|
||||
: new HandDisabledMsg(Name);
|
||||
|
||||
Parent.HandleMessage(message, Parent);
|
||||
Parent.Owner.SendMessage(Parent, message);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Container.Shutdown(); // TODO verify this
|
||||
@@ -693,7 +744,7 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
|
||||
public SharedHand ToShared(int index, HandLocation location)
|
||||
{
|
||||
return new SharedHand(index, Name, Entity?.Uid, location);
|
||||
return new SharedHand(index, Name, Entity?.Uid, location, Enabled);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Server.GameObjects.Components.Items.Storage;
|
||||
using Robust.Server.GameObjects.Components.Container;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components.Timers;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Timers;
|
||||
@@ -67,7 +68,7 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
switch (message)
|
||||
{
|
||||
case ContainerContentsModifiedMessage contentsModified:
|
||||
Timer.Spawn(0, DropIdAndPocketsIfWeNoLongerHaveAUniform);
|
||||
Owner.SpawnTimer(0, DropIdAndPocketsIfWeNoLongerHaveAUniform);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,6 +156,13 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
{
|
||||
return GetSlotItem<ItemComponent>(slot);
|
||||
}
|
||||
|
||||
public IEnumerable<T> LookupItems<T>() where T: Component
|
||||
{
|
||||
return _slotContainers.Values.SelectMany(x => x.ContainedEntities.Select(e => e.GetComponentOrNull<T>()))
|
||||
.Where(x => x != null);
|
||||
}
|
||||
|
||||
public T GetSlotItem<T>(Slots slot) where T : ItemComponent
|
||||
{
|
||||
if (!_slotContainers.ContainsKey(slot))
|
||||
@@ -435,7 +442,7 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
var activeHand = hands.GetActiveHand;
|
||||
if (activeHand != null && activeHand.Owner.TryGetComponent(out ItemComponent clothing))
|
||||
{
|
||||
hands.Drop(hands.ActiveHand);
|
||||
hands.Drop(hands.ActiveHand, doDropInteraction:false);
|
||||
if (!Equip(msg.Inventoryslot, clothing, true, out var reason))
|
||||
{
|
||||
hands.PutInHand(clothing);
|
||||
@@ -534,7 +541,7 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
var list = new List<KeyValuePair<Slots, EntityUid>>();
|
||||
foreach (var (slot, container) in _slotContainers)
|
||||
{
|
||||
if (container.ContainedEntity != null)
|
||||
if (container != null && container.ContainedEntity != null)
|
||||
{
|
||||
list.Add(new KeyValuePair<Slots, EntityUid>(slot, container.ContainedEntity.Uid));
|
||||
}
|
||||
|
||||
@@ -70,6 +70,47 @@ namespace Content.Server.GameObjects.Components.Instruments
|
||||
[ViewVariables]
|
||||
private int _midiEventCount = 0;
|
||||
|
||||
private byte _instrumentProgram;
|
||||
private byte _instrumentBank;
|
||||
private bool _allowPercussion;
|
||||
private bool _allowProgramChange;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public override byte InstrumentProgram { get => _instrumentProgram;
|
||||
set
|
||||
{
|
||||
_instrumentProgram = value;
|
||||
Dirty();
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public override byte InstrumentBank { get => _instrumentBank;
|
||||
set
|
||||
{
|
||||
_instrumentBank = value;
|
||||
Dirty();
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public override bool AllowPercussion { get => _allowPercussion;
|
||||
set
|
||||
{
|
||||
_allowPercussion = value;
|
||||
Dirty();
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public override bool AllowProgramChange { get => _allowProgramChange;
|
||||
set
|
||||
{
|
||||
_allowProgramChange = value;
|
||||
Dirty();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the instrument is an item which can be held or not.
|
||||
/// </summary>
|
||||
@@ -130,11 +171,15 @@ namespace Content.Server.GameObjects.Components.Instruments
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
serializer.DataField(ref _handheld, "handheld", false);
|
||||
serializer.DataField(ref _instrumentProgram, "program", (byte) 1);
|
||||
serializer.DataField(ref _instrumentBank, "bank", (byte) 0);
|
||||
serializer.DataField(ref _allowPercussion, "allowPercussion", false);
|
||||
serializer.DataField(ref _allowProgramChange, "allowProgramChange", false);
|
||||
}
|
||||
|
||||
public override ComponentState GetComponentState()
|
||||
{
|
||||
return new InstrumentState(Playing, _lastSequencerTick);
|
||||
return new InstrumentState(Playing, InstrumentProgram, InstrumentBank, AllowPercussion, AllowProgramChange, _lastSequencerTick);
|
||||
}
|
||||
|
||||
public override void HandleNetworkMessage(ComponentMessage message, INetChannel channel, ICommonSession? session = null)
|
||||
|
||||
@@ -1,52 +1,33 @@
|
||||
#nullable enable
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components.GUI;
|
||||
using Content.Server.GameObjects.Components.Items.Clothing;
|
||||
using Content.Server.GameObjects.Components.Items.Storage;
|
||||
using Content.Server.GameObjects.Components.Power;
|
||||
using Content.Server.Interfaces.GameObjects.Components.Items;
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.Verbs;
|
||||
using Content.Shared.Interfaces;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.GameObjects.Components.Container;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
using Robust.Server.Interfaces.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Interactable
|
||||
{
|
||||
/// <summary>
|
||||
/// Component that represents a handheld lightsource which can be toggled on and off.
|
||||
/// Component that represents a powered handheld light source which can be toggled on and off.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
internal sealed class HandheldLightComponent : SharedHandheldLightComponent, IUse, IExamine, IInteractUsing,
|
||||
IMapInit
|
||||
internal sealed class HandheldLightComponent : SharedHandheldLightComponent, IUse, IExamine, IInteractUsing
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite)] public float Wattage { get; set; } = 10;
|
||||
[ViewVariables] private ContainerSlot _cellContainer = default!;
|
||||
|
||||
[ViewVariables]
|
||||
private BatteryComponent? Cell
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_cellContainer.ContainedEntity == null) return null;
|
||||
if (_cellContainer.ContainedEntity.TryGetComponent(out BatteryComponent? cell))
|
||||
{
|
||||
return cell;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
[ViewVariables(VVAccess.ReadWrite)] public float Wattage { get; set; } = 10f;
|
||||
[ViewVariables] private PowerCellSlotComponent _cellSlot = default!;
|
||||
private PowerCellComponent? Cell => _cellSlot.Cell;
|
||||
|
||||
/// <summary>
|
||||
/// Status of light, whether or not it is emitting light.
|
||||
@@ -54,26 +35,36 @@ namespace Content.Server.GameObjects.Components.Interactable
|
||||
[ViewVariables]
|
||||
public bool Activated { get; private set; }
|
||||
|
||||
[ViewVariables] protected override bool HasCell => Cell != null;
|
||||
[ViewVariables] protected override bool HasCell => _cellSlot.HasCell;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)] public string? TurnOnSound;
|
||||
[ViewVariables(VVAccess.ReadWrite)] public string? TurnOnFailSound;
|
||||
[ViewVariables(VVAccess.ReadWrite)] public string? TurnOffSound;
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
serializer.DataField(this, x => x.Wattage, "wattage", 10f);
|
||||
serializer.DataField(ref TurnOnSound, "turnOnSound", "/Audio/Items/flashlight_toggle.ogg");
|
||||
serializer.DataField(ref TurnOnFailSound, "turnOnFailSound", "/Audio/Machines/button.ogg");
|
||||
serializer.DataField(ref TurnOffSound, "turnOffSound", "/Audio/Items/flashlight_toggle.ogg");
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
Owner.EnsureComponent<PointLightComponent>();
|
||||
_cellSlot = Owner.EnsureComponent<PowerCellSlotComponent>();
|
||||
|
||||
Dirty();
|
||||
}
|
||||
|
||||
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
{
|
||||
if (!eventArgs.Using.HasComponent<BatteryComponent>()) return false;
|
||||
|
||||
if (Cell != null) return false;
|
||||
|
||||
var handsComponent = eventArgs.User.GetComponent<IHandsComponent>();
|
||||
|
||||
if (!handsComponent.Drop(eventArgs.Using, _cellContainer))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Items/pistol_magin.ogg", Owner);
|
||||
|
||||
|
||||
if (!ActionBlockerSystem.CanInteract(eventArgs.User)) return false;
|
||||
if (!_cellSlot.InsertCell(eventArgs.Using)) return false;
|
||||
Dirty();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -83,6 +74,10 @@ namespace Content.Server.GameObjects.Components.Interactable
|
||||
{
|
||||
message.AddMarkup(Loc.GetString("The light is currently [color=darkgreen]on[/color]."));
|
||||
}
|
||||
else
|
||||
{
|
||||
message.AddMarkup(Loc.GetString("The light is currently [color=darkred]off[/color]."));
|
||||
}
|
||||
}
|
||||
|
||||
bool IUse.UseEntity(UseEntityEventArgs eventArgs)
|
||||
@@ -90,45 +85,20 @@ namespace Content.Server.GameObjects.Components.Interactable
|
||||
return ToggleStatus(eventArgs.User);
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
Owner.EnsureComponent<PointLightComponent>();
|
||||
_cellContainer =
|
||||
ContainerManagerComponent.Ensure<ContainerSlot>("flashlight_cell_container", Owner, out _);
|
||||
|
||||
Dirty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Illuminates the light if it is not active, extinguishes it if it is active.
|
||||
/// </summary>
|
||||
/// <returns>True if the light's status was toggled, false otherwise.</returns>
|
||||
private bool ToggleStatus(IEntity user)
|
||||
{
|
||||
var item = Owner.GetComponent<ItemComponent>();
|
||||
// Update sprite and light states to match the activation.
|
||||
if (Activated)
|
||||
{
|
||||
TurnOff();
|
||||
item.EquippedPrefix = "off";
|
||||
}
|
||||
else
|
||||
{
|
||||
TurnOn(user);
|
||||
item.EquippedPrefix = "on";
|
||||
return Activated ? TurnOff() : TurnOn(user);
|
||||
}
|
||||
|
||||
// Toggle always succeeds.
|
||||
return true;
|
||||
}
|
||||
|
||||
private void TurnOff(bool makeNoise = true)
|
||||
private bool TurnOff(bool makeNoise = true)
|
||||
{
|
||||
if (!Activated)
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
SetState(false);
|
||||
@@ -136,40 +106,41 @@ namespace Content.Server.GameObjects.Components.Interactable
|
||||
|
||||
if (makeNoise)
|
||||
{
|
||||
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Items/flashlight_toggle.ogg", Owner);
|
||||
}
|
||||
if (TurnOffSound != null) EntitySystem.Get<AudioSystem>().PlayFromEntity(TurnOffSound, Owner);
|
||||
}
|
||||
|
||||
private void TurnOn(IEntity user)
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TurnOn(IEntity user)
|
||||
{
|
||||
if (Activated)
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
var cell = Cell;
|
||||
if (cell == null)
|
||||
if (Cell == null)
|
||||
{
|
||||
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Machines/button.ogg", Owner);
|
||||
|
||||
if (TurnOnFailSound != null) EntitySystem.Get<AudioSystem>().PlayFromEntity(TurnOnFailSound, Owner);
|
||||
Owner.PopupMessage(user, Loc.GetString("Cell missing..."));
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// To prevent having to worry about frame time in here.
|
||||
// Let's just say you need a whole second of charge before you can turn it on.
|
||||
// Simple enough.
|
||||
if (Wattage > cell.CurrentCharge)
|
||||
if (Wattage > Cell.CurrentCharge)
|
||||
{
|
||||
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Machines/button.ogg", Owner);
|
||||
if (TurnOnFailSound != null) EntitySystem.Get<AudioSystem>().PlayFromEntity(TurnOnFailSound, Owner);
|
||||
Owner.PopupMessage(user, Loc.GetString("Dead cell..."));
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
Activated = true;
|
||||
SetState(true);
|
||||
|
||||
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Items/flashlight_toggle.ogg", Owner);
|
||||
if (TurnOnSound != null) EntitySystem.Get<AudioSystem>().PlayFromEntity(TurnOnSound, Owner);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void SetState(bool on)
|
||||
@@ -188,11 +159,20 @@ namespace Content.Server.GameObjects.Components.Interactable
|
||||
{
|
||||
clothing.ClothingEquippedPrefix = on ? "On" : "Off";
|
||||
}
|
||||
|
||||
if (Owner.TryGetComponent(out ItemComponent? item))
|
||||
{
|
||||
item.EquippedPrefix = on ? "on" : "off";
|
||||
}
|
||||
}
|
||||
|
||||
public void OnUpdate(float frameTime)
|
||||
{
|
||||
if (!Activated || Cell == null) return;
|
||||
if (Cell == null)
|
||||
{
|
||||
TurnOff(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var appearanceComponent = Owner.GetComponent<AppearanceComponent>();
|
||||
|
||||
@@ -209,43 +189,10 @@ namespace Content.Server.GameObjects.Components.Interactable
|
||||
appearanceComponent.SetData(HandheldLightVisuals.Power, HandheldLightPowerStates.Dying);
|
||||
}
|
||||
|
||||
if (Cell == null || !Cell.TryUseCharge(Wattage * frameTime)) TurnOff();
|
||||
|
||||
if (Activated && !Cell.TryUseCharge(Wattage * frameTime)) TurnOff(false);
|
||||
Dirty();
|
||||
}
|
||||
|
||||
private void EjectCell(IEntity user)
|
||||
{
|
||||
if (Cell == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var cell = Cell;
|
||||
|
||||
if (!_cellContainer.Remove(cell.Owner))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Dirty();
|
||||
|
||||
if (!user.TryGetComponent(out HandsComponent? hands))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hands.PutInHand(cell.Owner.GetComponent<ItemComponent>()))
|
||||
{
|
||||
cell.Owner.Transform.Coordinates = user.Transform.Coordinates;
|
||||
}
|
||||
|
||||
// Assuming the battery has just been taken out of the flashlight, make sure it's getting disabled
|
||||
TurnOff(false);
|
||||
|
||||
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Items/pistol_magout.ogg", Owner);
|
||||
}
|
||||
|
||||
public override ComponentState GetComponentState()
|
||||
{
|
||||
if (Cell == null)
|
||||
@@ -262,44 +209,5 @@ namespace Content.Server.GameObjects.Components.Interactable
|
||||
|
||||
return new HandheldLightComponentState(Cell.CurrentCharge / Cell.MaxCharge, HasCell);
|
||||
}
|
||||
|
||||
[Verb]
|
||||
public sealed class EjectCellVerb : Verb<HandheldLightComponent>
|
||||
{
|
||||
protected override void GetData(IEntity user, HandheldLightComponent component, VerbData data)
|
||||
{
|
||||
if (!ActionBlockerSystem.CanInteract(user))
|
||||
{
|
||||
data.Visibility = VerbVisibility.Invisible;
|
||||
return;
|
||||
}
|
||||
|
||||
if (component.Cell == null)
|
||||
{
|
||||
data.Text = Loc.GetString("Eject cell (cell missing)");
|
||||
data.Visibility = VerbVisibility.Disabled;
|
||||
}
|
||||
else
|
||||
{
|
||||
data.Text = Loc.GetString("Eject cell");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Activate(IEntity user, HandheldLightComponent component)
|
||||
{
|
||||
component.EjectCell(user);
|
||||
}
|
||||
}
|
||||
|
||||
void IMapInit.MapInit()
|
||||
{
|
||||
if (_cellContainer.ContainedEntity != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var cell = Owner.EntityManager.SpawnEntity("PowerCellSmallStandard", Owner.Transform.Coordinates);
|
||||
_cellContainer.Insert(cell);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage
|
||||
|
||||
public override string Name => "CursedEntityStorage";
|
||||
|
||||
public override void CloseStorage()
|
||||
protected override void CloseStorage()
|
||||
{
|
||||
base.CloseStorage();
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage
|
||||
var locker = lockerEnt.GetComponent<EntityStorageComponent>();
|
||||
|
||||
if(locker.Open)
|
||||
locker.CloseStorage();
|
||||
locker.TryCloseStorage(Owner);
|
||||
|
||||
foreach (var entity in Contents.ContainedEntities.ToArray())
|
||||
{
|
||||
|
||||
@@ -47,11 +47,15 @@ namespace Content.Server.GameObjects.Components.Items.Storage
|
||||
[ViewVariables]
|
||||
private bool _isCollidableWhenOpen;
|
||||
[ViewVariables]
|
||||
private IEntityQuery _entityQuery;
|
||||
protected IEntityQuery EntityQuery;
|
||||
|
||||
private bool _showContents;
|
||||
private bool _occludesLight;
|
||||
private bool _open;
|
||||
private bool _canWeldShut;
|
||||
private bool _isWeldedShut;
|
||||
private string _closeSound = "/Audio/Machines/closetclose.ogg";
|
||||
private string _openSound = "/Audio/Machines/closetopen.ogg";
|
||||
|
||||
[ViewVariables]
|
||||
protected Container Contents;
|
||||
@@ -104,14 +108,24 @@ namespace Content.Server.GameObjects.Components.Items.Storage
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool CanWeldShut { get; set; }
|
||||
public bool CanWeldShut {
|
||||
get => _canWeldShut;
|
||||
set
|
||||
{
|
||||
_canWeldShut = value;
|
||||
if (Owner.TryGetComponent(out AppearanceComponent appearance))
|
||||
{
|
||||
appearance.SetData(StorageVisuals.CanWeld, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
Contents = ContainerManagerComponent.Ensure<Container>(nameof(EntityStorageComponent), Owner);
|
||||
_entityQuery = new IntersectingEntityQuery(Owner);
|
||||
EntityQuery = new IntersectingEntityQuery(Owner);
|
||||
|
||||
Contents.ShowContents = _showContents;
|
||||
Contents.OccludesLight = _occludesLight;
|
||||
@@ -134,6 +148,8 @@ namespace Content.Server.GameObjects.Components.Items.Storage
|
||||
serializer.DataField(ref _open, "open", false);
|
||||
serializer.DataField(this, a => a.IsWeldedShut, "IsWeldedShut", false);
|
||||
serializer.DataField(this, a => a.CanWeldShut, "CanWeldShut", true);
|
||||
serializer.DataField(this, x => _closeSound, "closeSound", "/Audio/Machines/closetclose.ogg");
|
||||
serializer.DataField(this, x => _openSound, "openSound", "/Audio/Machines/closetopen.ogg");
|
||||
}
|
||||
|
||||
public virtual void Activate(ActivateEventArgs eventArgs)
|
||||
@@ -141,17 +157,26 @@ namespace Content.Server.GameObjects.Components.Items.Storage
|
||||
ToggleOpen(eventArgs.User);
|
||||
}
|
||||
|
||||
private void ToggleOpen(IEntity user)
|
||||
public virtual bool CanOpen(IEntity user, bool silent = false)
|
||||
{
|
||||
if (IsWeldedShut)
|
||||
{
|
||||
Owner.PopupMessage(user, Loc.GetString("It's welded completely shut!"));
|
||||
return;
|
||||
if(!silent) Owner.PopupMessage(user, Loc.GetString("It's welded completely shut!"));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual bool CanClose(IEntity user, bool silent = false)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ToggleOpen(IEntity user)
|
||||
{
|
||||
if (Open)
|
||||
{
|
||||
CloseStorage();
|
||||
TryCloseStorage(user);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -159,10 +184,10 @@ namespace Content.Server.GameObjects.Components.Items.Storage
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void CloseStorage()
|
||||
protected virtual void CloseStorage()
|
||||
{
|
||||
Open = false;
|
||||
var entities = Owner.EntityManager.GetEntities(_entityQuery);
|
||||
var entities = Owner.EntityManager.GetEntities(EntityQuery);
|
||||
var count = 0;
|
||||
foreach (var entity in entities)
|
||||
{
|
||||
@@ -187,16 +212,16 @@ namespace Content.Server.GameObjects.Components.Items.Storage
|
||||
}
|
||||
|
||||
ModifyComponents();
|
||||
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Machines/closetclose.ogg", Owner);
|
||||
EntitySystem.Get<AudioSystem>().PlayFromEntity(_closeSound, Owner);
|
||||
_lastInternalOpenAttempt = default;
|
||||
}
|
||||
|
||||
private void OpenStorage()
|
||||
protected virtual void OpenStorage()
|
||||
{
|
||||
Open = true;
|
||||
EmptyContents();
|
||||
ModifyComponents();
|
||||
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Machines/closetopen.ogg", Owner);
|
||||
EntitySystem.Get<AudioSystem>().PlayFromEntity(_openSound, Owner);
|
||||
}
|
||||
|
||||
private void ModifyComponents()
|
||||
@@ -224,8 +249,9 @@ namespace Content.Server.GameObjects.Components.Items.Storage
|
||||
}
|
||||
}
|
||||
|
||||
private bool AddToContents(IEntity entity)
|
||||
protected virtual bool AddToContents(IEntity entity)
|
||||
{
|
||||
if (entity == Owner) return false;
|
||||
if (entity.TryGetComponent(out IPhysicsComponent entityPhysicsComponent))
|
||||
{
|
||||
if(MaxSize < entityPhysicsComponent.WorldAABB.Size.X
|
||||
@@ -247,12 +273,18 @@ namespace Content.Server.GameObjects.Components.Items.Storage
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual Vector2 ContentsDumpPosition()
|
||||
{
|
||||
return Owner.Transform.WorldPosition;
|
||||
}
|
||||
|
||||
private void EmptyContents()
|
||||
{
|
||||
foreach (var contained in Contents.ContainedEntities.ToArray())
|
||||
{
|
||||
if(Contents.Remove(contained))
|
||||
{
|
||||
contained.Transform.WorldPosition = ContentsDumpPosition();
|
||||
if (contained.TryGetComponent<IPhysicsComponent>(out var physics))
|
||||
{
|
||||
physics.CanCollide = true;
|
||||
@@ -284,14 +316,18 @@ namespace Content.Server.GameObjects.Components.Items.Storage
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void TryOpenStorage(IEntity user)
|
||||
public virtual bool TryOpenStorage(IEntity user)
|
||||
{
|
||||
if (IsWeldedShut)
|
||||
{
|
||||
Owner.PopupMessage(user, Loc.GetString("It's welded completely shut!"));
|
||||
return;
|
||||
}
|
||||
if (!CanOpen(user)) return false;
|
||||
OpenStorage();
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual bool TryCloseStorage(IEntity user)
|
||||
{
|
||||
if (!CanClose(user)) return false;
|
||||
CloseStorage();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -58,12 +58,12 @@ namespace Content.Server.GameObjects.Components.Items.Storage
|
||||
}
|
||||
}
|
||||
|
||||
public void Equipped(EquippedEventArgs eventArgs)
|
||||
public virtual void Equipped(EquippedEventArgs eventArgs)
|
||||
{
|
||||
EquippedToSlot();
|
||||
}
|
||||
|
||||
public void Unequipped(UnequippedEventArgs eventArgs)
|
||||
public virtual void Unequipped(UnequippedEventArgs eventArgs)
|
||||
{
|
||||
RemovedFromSlot();
|
||||
}
|
||||
|
||||
@@ -69,15 +69,14 @@ namespace Content.Server.GameObjects.Components.Items.Storage
|
||||
base.Activate(eventArgs);
|
||||
}
|
||||
|
||||
protected override void TryOpenStorage(IEntity user)
|
||||
public override bool CanOpen(IEntity user, bool silent = false)
|
||||
{
|
||||
if (Locked)
|
||||
{
|
||||
Owner.PopupMessage(user, "It's locked!");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
base.TryOpenStorage(user);
|
||||
return base.CanOpen(user, silent);
|
||||
}
|
||||
|
||||
protected override void OpenVerbGetData(IEntity user, EntityStorageComponent component, VerbData data)
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace Content.Server.GameObjects.Components.Kitchen
|
||||
private string? _meatPrototype;
|
||||
private string _meatSource1p = "?";
|
||||
private string _meatSource0 = "?";
|
||||
private string _meatName = "?";
|
||||
|
||||
void IActivate.Activate(ActivateEventArgs eventArgs)
|
||||
{
|
||||
@@ -38,7 +39,11 @@ namespace Content.Server.GameObjects.Components.Kitchen
|
||||
|
||||
if (!string.IsNullOrEmpty(_meatPrototype))
|
||||
{
|
||||
Owner.EntityManager.SpawnEntity(_meatPrototype, Owner.Transform.Coordinates);
|
||||
var meat = Owner.EntityManager.SpawnEntity(_meatPrototype, Owner.Transform.Coordinates);
|
||||
if (meat != null)
|
||||
{
|
||||
meat.Name = _meatName;
|
||||
}
|
||||
}
|
||||
|
||||
if (_meatParts != 0)
|
||||
@@ -74,6 +79,9 @@ namespace Content.Server.GameObjects.Components.Kitchen
|
||||
_meatParts = 5;
|
||||
_meatSource1p = Loc.GetString("You remove some meat from {0:theName}.", victim);
|
||||
_meatSource0 = Loc.GetString("You remove the last piece of meat from {0:theName}!", victim);
|
||||
// TODO: This could stand to be improved somehow, but it'd require Name to be much 'richer' in detail than it presently is.
|
||||
// But Name is RobustToolbox-level, so presumably it'd have to be done in some other way (interface???)
|
||||
_meatName = Loc.GetString("{0:name} meat", victim);
|
||||
|
||||
if (Owner.TryGetComponent(out sprite))
|
||||
{
|
||||
|
||||
@@ -26,6 +26,7 @@ using Robust.Server.GameObjects.EntitySystems;
|
||||
using Robust.Server.Interfaces.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components.Timers;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -312,7 +313,7 @@ namespace Content.Server.GameObjects.Components.Kitchen
|
||||
(_currentCookTimerTime == (uint)recipeToCook.CookTime);
|
||||
SetAppearance(MicrowaveVisualState.Cooking);
|
||||
_audioSystem.PlayFromEntity(_startCookingSound, Owner, AudioParams.Default);
|
||||
Timer.Spawn((int)(_currentCookTimerTime * _cookTimeMultiplier), (Action)(() =>
|
||||
Owner.SpawnTimer((int)(_currentCookTimerTime * _cookTimeMultiplier), (Action)(() =>
|
||||
{
|
||||
if (_lostPower)
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components.Timers;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Random;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -67,7 +68,7 @@ namespace Content.Server.GameObjects.Components.Markers
|
||||
{
|
||||
TokenSource?.Cancel();
|
||||
TokenSource = new CancellationTokenSource();
|
||||
Timer.SpawnRepeating(TimeSpan.FromSeconds(IntervalSeconds), OnTimerFired, TokenSource.Token);
|
||||
Owner.SpawnRepeatingTimer(TimeSpan.FromSeconds(IntervalSeconds), OnTimerFired, TokenSource.Token);
|
||||
}
|
||||
|
||||
private void OnTimerFired()
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components.Mobs;
|
||||
using Content.Server.GameObjects.Components.Observer;
|
||||
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
|
||||
@@ -19,6 +17,7 @@ using Robust.Server.GameObjects.Components.UserInterface;
|
||||
using Robust.Server.Interfaces.GameObjects;
|
||||
using Robust.Server.Interfaces.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -76,12 +75,12 @@ namespace Content.Server.GameObjects.Components.Medical
|
||||
HandleGhostReturn);
|
||||
}
|
||||
|
||||
public void Update(float frametime)
|
||||
public void Update(float frameTime)
|
||||
{
|
||||
if (_bodyContainer.ContainedEntity != null &&
|
||||
Powered)
|
||||
{
|
||||
_cloningProgress += frametime;
|
||||
_cloningProgress += frameTime;
|
||||
_cloningProgress = MathHelper.Clamp(_cloningProgress, 0f, _cloningTime);
|
||||
}
|
||||
|
||||
@@ -122,7 +121,9 @@ namespace Content.Server.GameObjects.Components.Medical
|
||||
|
||||
private CloningPodBoundUserInterfaceState GetUserInterfaceState()
|
||||
{
|
||||
return new CloningPodBoundUserInterfaceState(CloningSystem.getIdToUser(), _cloningProgress,
|
||||
var idToUser = EntitySystem.Get<CloningSystem>().GetIdToUser();
|
||||
|
||||
return new CloningPodBoundUserInterfaceState(idToUser, _cloningProgress,
|
||||
(_status == CloningPodStatus.Cloning));
|
||||
}
|
||||
|
||||
@@ -152,11 +153,12 @@ namespace Content.Server.GameObjects.Components.Medical
|
||||
switch (message.Button)
|
||||
{
|
||||
case UiButton.Clone:
|
||||
|
||||
if (message.ScanId == null) return;
|
||||
|
||||
var cloningSystem = EntitySystem.Get<CloningSystem>();
|
||||
|
||||
if (_bodyContainer.ContainedEntity != null ||
|
||||
!CloningSystem.Minds.TryGetValue(message.ScanId.Value, out var mind))
|
||||
!cloningSystem.Minds.TryGetValue(message.ScanId.Value, out var mind))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ using Robust.Server.GameObjects.Components.UserInterface;
|
||||
using Robust.Server.Interfaces.GameObjects;
|
||||
using Robust.Server.Interfaces.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Timing;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -126,9 +127,12 @@ namespace Content.Server.GameObjects.Components.Medical
|
||||
return new MedicalScannerBoundUserInterfaceState(body.Uid, classes, types, true);
|
||||
}
|
||||
|
||||
var cloningSystem = EntitySystem.Get<CloningSystem>();
|
||||
var scanned = _bodyContainer.ContainedEntity.TryGetComponent(out MindComponent? mindComponent) &&
|
||||
mindComponent.Mind != null &&
|
||||
cloningSystem.HasDnaScan(mindComponent.Mind);
|
||||
|
||||
return new MedicalScannerBoundUserInterfaceState(body.Uid, classes, types,
|
||||
CloningSystem.HasDnaScan(_bodyContainer.ContainedEntity.GetComponent<MindComponent>().Mind));
|
||||
return new MedicalScannerBoundUserInterfaceState(body.Uid, classes, types, scanned);
|
||||
}
|
||||
|
||||
private void UpdateUserInterface()
|
||||
@@ -261,7 +265,8 @@ namespace Content.Server.GameObjects.Components.Medical
|
||||
if (_bodyContainer.ContainedEntity != null)
|
||||
{
|
||||
//TODO: Show a 'ERROR: Body is completely devoid of soul' if no Mind owns the entity.
|
||||
CloningSystem.AddToDnaScans(_playerManager
|
||||
var cloningSystem = EntitySystem.Get<CloningSystem>();
|
||||
cloningSystem.AddToDnaScans(_playerManager
|
||||
.GetPlayersBy(playerSession =>
|
||||
{
|
||||
var mindOwnedMob = playerSession.ContentData()?.Mind?.OwnedEntity;
|
||||
|
||||
@@ -9,6 +9,7 @@ using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Robust.Server.GameObjects.Components.UserInterface;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components.Timers;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Map;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -135,7 +136,7 @@ namespace Content.Server.GameObjects.Components.Mobs
|
||||
else
|
||||
{
|
||||
var spawnPosition = Owner.Transform.Coordinates;
|
||||
Timer.Spawn(0, () =>
|
||||
Owner.SpawnTimer(0, () =>
|
||||
{
|
||||
// Async this so that we don't throw if the grid we're on is being deleted.
|
||||
var mapMan = IoCManager.Resolve<IMapManager>();
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.GameObjects.Components.Atmos;
|
||||
using Content.Server.GameObjects.Components.Buckle;
|
||||
using Content.Server.GameObjects.Components.GUI;
|
||||
using Content.Server.GameObjects.Components.Movement;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Content.Shared.GameObjects.Components.Pulling;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
@@ -23,6 +23,22 @@ namespace Content.Server.GameObjects.Components.Mobs
|
||||
[ViewVariables]
|
||||
private readonly Dictionary<StatusEffect, StatusEffectStatus> _statusEffects = new Dictionary<StatusEffect, StatusEffectStatus>();
|
||||
|
||||
public override IReadOnlyDictionary<StatusEffect, StatusEffectStatus> Statuses => _statusEffects;
|
||||
|
||||
protected override void Startup()
|
||||
{
|
||||
base.Startup();
|
||||
|
||||
EntitySystem.Get<WeightlessSystem>().AddStatus(this);
|
||||
}
|
||||
|
||||
public override void OnRemove()
|
||||
{
|
||||
EntitySystem.Get<WeightlessSystem>().RemoveStatus(this);
|
||||
|
||||
base.OnRemove();
|
||||
}
|
||||
|
||||
public override ComponentState GetComponentState()
|
||||
{
|
||||
return new StatusEffectComponentState(_statusEffects);
|
||||
|
||||
@@ -38,8 +38,6 @@ namespace Content.Server.GameObjects.Components.Mobs.State
|
||||
|
||||
public override void ExitState(IEntity entity)
|
||||
{
|
||||
EntitySystem.Get<StandingStateSystem>().Standing(entity);
|
||||
|
||||
if (entity.TryGetComponent(out ServerOverlayEffectsComponent overlay))
|
||||
{
|
||||
overlay.ClearOverlays();
|
||||
|
||||
@@ -44,8 +44,6 @@ namespace Content.Server.GameObjects.Components.Mobs.State
|
||||
|
||||
public override void ExitState(IEntity entity)
|
||||
{
|
||||
EntitySystem.Get<StandingStateSystem>().Standing(entity);
|
||||
|
||||
if (entity.TryGetComponent(out IPhysicsComponent physics))
|
||||
{
|
||||
physics.CanCollide = true;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using Content.Server.GameObjects.Components.Body;
|
||||
using Content.Server.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Body;
|
||||
using Content.Server.GameObjects.Components.Damage;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Mobs.State
|
||||
@@ -13,6 +13,8 @@ namespace Content.Server.GameObjects.Components.Mobs.State
|
||||
{
|
||||
public override void EnterState(IEntity entity)
|
||||
{
|
||||
EntitySystem.Get<StandingStateSystem>().Standing(entity);
|
||||
|
||||
if (entity.TryGetComponent(out AppearanceComponent appearance))
|
||||
{
|
||||
appearance.SetData(DamageStateVisuals.State, DamageState.Alive);
|
||||
|
||||
@@ -5,6 +5,7 @@ using Content.Shared.GameObjects.Components.Movement;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using NFluidsynth;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components.Timers;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.Timing;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -101,7 +102,7 @@ namespace Content.Server.GameObjects.Components.Mobs
|
||||
|
||||
if (progress >= length)
|
||||
{
|
||||
Timer.Spawn(250, () => status.RemoveStatusEffect(StatusEffect.Stun), StatusRemoveCancellation.Token);
|
||||
Owner.SpawnTimer(250, () => status.RemoveStatusEffect(StatusEffect.Stun), StatusRemoveCancellation.Token);
|
||||
LastStun = null;
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user