Merge branch 'master' into prediction

This commit is contained in:
Pieter-Jan Briers
2020-04-18 14:07:02 +02:00
237 changed files with 4278 additions and 1948 deletions

View File

@@ -40,8 +40,8 @@ csharp_space_between_method_declaration_parameter_list_parentheses=false
csharp_space_between_parentheses=false
csharp_space_between_square_brackets=false
csharp_style_expression_bodied_accessors=true:suggestion
csharp_style_expression_bodied_constructors=true:suggestion
csharp_style_expression_bodied_methods=true:suggestion
csharp_style_expression_bodied_constructors=false:suggestion
csharp_style_expression_bodied_methods=false:suggestion
csharp_style_expression_bodied_properties=true:suggestion
csharp_style_var_elsewhere=true:suggestion
csharp_style_var_for_built_in_types=true:suggestion

View File

@@ -184,6 +184,9 @@ namespace Content.Client.Chat
case ChatChannel.OOC:
color = Color.LightSkyBlue;
break;
case ChatChannel.Dead:
color = Color.MediumPurple;
break;
}
_currentChatBox?.AddLine(messageText, message.Channel, color);
@@ -288,7 +291,7 @@ namespace Content.Client.Chat
WriteChatMessage(storedMessage);
// Local messages that have an entity attached get a speech bubble.
if (msg.Channel == ChatChannel.Local && msg.SenderEntity != default)
if ((msg.Channel == ChatChannel.Local || msg.Channel == ChatChannel.Dead) && msg.SenderEntity != default)
{
AddSpeechBubble(msg);
}

View File

@@ -6,8 +6,11 @@ using Content.Client.Interfaces.Parallax;
using Content.Client.Parallax;
using Content.Client.Sandbox;
using Content.Client.UserInterface;
using Content.Client.UserInterface.Stylesheets;
using Content.Client.Utility;
using Content.Shared.Chemistry;
using Content.Shared.Interfaces;
using Content.Shared.Interfaces.Chemistry;
using Robust.Shared.IoC;
namespace Content.Client
@@ -27,6 +30,7 @@ namespace Content.Client
IoCManager.Register<IModuleManager, ClientModuleManager>();
IoCManager.Register<IClientPreferencesManager, ClientPreferencesManager>();
IoCManager.Register<IItemSlotManager, ItemSlotManager>();
IoCManager.Register<IStylesheetManager, StylesheetManager>();
}
}
}

View File

@@ -0,0 +1,80 @@
using System.Threading;
using Content.Client.GameObjects.Components.Command;
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.Log;
using Robust.Shared.Maths;
using Timer = Robust.Shared.Timers.Timer;
namespace Content.Client.Command
{
public class CommunicationsConsoleMenu : SS14Window
{
#pragma warning disable 649
[Dependency] private readonly ILocalizationManager _localizationManager;
#pragma warning restore 649
protected override Vector2? CustomSize => new Vector2(600, 400);
private CommunicationsConsoleBoundUserInterface Owner { get; set; }
private readonly CancellationTokenSource _timerCancelTokenSource = new CancellationTokenSource();
private readonly Button _emergencyShuttleButton;
private readonly RichTextLabel _countdownLabel;
public CommunicationsConsoleMenu(CommunicationsConsoleBoundUserInterface owner)
{
IoCManager.InjectDependencies(this);
Title = _localizationManager.GetString("Communications Console");
Owner = owner;
_countdownLabel = new RichTextLabel(){CustomMinimumSize = new Vector2(0, 200)};
_emergencyShuttleButton = new Button();
_emergencyShuttleButton.OnPressed += (e) => Owner.EmergencyShuttleButtonPressed();
var vbox = new VBoxContainer() {SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsVertical = SizeFlags.FillExpand};
vbox.AddChild(_countdownLabel);
vbox.AddChild(_emergencyShuttleButton);
var hbox = new HBoxContainer() {SizeFlagsHorizontal = SizeFlags.FillExpand, SizeFlagsVertical = SizeFlags.FillExpand};
hbox.AddChild(new Control(){CustomMinimumSize = new Vector2(100,0), SizeFlagsHorizontal = SizeFlags.FillExpand});
hbox.AddChild(vbox);
hbox.AddChild(new Control(){CustomMinimumSize = new Vector2(100,0), SizeFlagsHorizontal = SizeFlags.FillExpand});
Contents.AddChild(hbox);
UpdateCountdown();
Timer.SpawnRepeating(1000, UpdateCountdown, _timerCancelTokenSource.Token);
}
public void UpdateCountdown()
{
if (!Owner.CountdownStarted)
{
_countdownLabel.SetMessage("");
_emergencyShuttleButton.Text = _localizationManager.GetString("Call emergency shuttle");
return;
}
_emergencyShuttleButton.Text = _localizationManager.GetString("Recall emergency shuttle");
_countdownLabel.SetMessage($"Time remaining\n{Owner.Countdown.ToString()}s");
}
public override void Close()
{
base.Close();
_timerCancelTokenSource.Cancel();
}
protected override void Dispose(bool disposing)
{
if(disposing)
_timerCancelTokenSource.Cancel();
}
}
}

View File

@@ -8,16 +8,18 @@ using Content.Client.Parallax;
using Content.Client.Sandbox;
using Content.Client.State;
using Content.Client.UserInterface;
using Content.Client.UserInterface.Stylesheets;
using Content.Shared.GameObjects.Components;
using Content.Shared.GameObjects.Components.Cargo;
using Content.Shared.GameObjects.Components.Chemistry;
using Content.Shared.GameObjects.Components.Markers;
using Content.Shared.GameObjects.Components.Research;
using Content.Shared.GameObjects.Components.VendingMachines;
using Robust.Client;
using Robust.Client.Interfaces;
using Robust.Client.Interfaces.Graphics.Overlays;
using Robust.Client.Interfaces.Input;
using Robust.Client.Interfaces.UserInterface;
using Robust.Client.Interfaces.State;
using Robust.Client.Player;
using Robust.Shared.ContentPack;
using Robust.Shared.Interfaces.GameObjects;
@@ -35,6 +37,8 @@ namespace Content.Client
[Dependency] private readonly IPlayerManager _playerManager;
[Dependency] private readonly IBaseClient _baseClient;
[Dependency] private readonly IEscapeMenuOwner _escapeMenuOwner;
[Dependency] private readonly IGameController _gameController;
[Dependency] private readonly IStateManager _stateManager;
#pragma warning restore 649
public override void Init()
@@ -131,6 +135,7 @@ namespace Content.Client
"Paper",
"Write",
"Bloodstream",
"TransformableContainer",
"Mind",
"MovementSpeedModifier",
"StorageFill"
@@ -145,7 +150,7 @@ namespace Content.Client
factory.Register<SharedLatheComponent>();
factory.Register<SharedSpawnPointComponent>();
factory.Register<SolutionComponent>();
factory.Register<SharedSolutionComponent>();
factory.Register<SharedVendingMachineComponent>();
factory.Register<SharedWiresComponent>();
@@ -167,10 +172,7 @@ namespace Content.Client
IoCManager.Resolve<IParallaxManager>().LoadParallax();
IoCManager.Resolve<IBaseClient>().PlayerJoinedServer += SubscribePlayerAttachmentEvents;
var stylesheet = new NanoStyle();
IoCManager.Resolve<IUserInterfaceManager>().Stylesheet = stylesheet.Stylesheet;
IoCManager.Resolve<IStylesheetManager>().Initialize();
IoCManager.InjectDependencies(this);
@@ -225,6 +227,37 @@ namespace Content.Client
IoCManager.Resolve<ISandboxManager>().Initialize();
IoCManager.Resolve<IClientPreferencesManager>().Initialize();
IoCManager.Resolve<IItemSlotManager>().Initialize();
_baseClient.RunLevelChanged += (sender, args) =>
{
if (args.NewLevel == ClientRunLevel.Initialize)
{
SwitchToDefaultState(args.OldLevel == ClientRunLevel.Connected ||
args.OldLevel == ClientRunLevel.InGame);
}
};
SwitchToDefaultState();
}
private void SwitchToDefaultState(bool disconnected = false)
{
// Fire off into state dependent on launcher or not.
if (_gameController.LaunchState.FromLauncher)
{
_stateManager.RequestStateChange<LauncherConnecting>();
var state = (LauncherConnecting) _stateManager.CurrentState;
if (disconnected)
{
state.SetDisconnected();
}
}
else
{
_stateManager.RequestStateChange<MainScreen>();
}
}
public override void Update(ModUpdateLevel level, FrameEventArgs frameEventArgs)

View File

@@ -5,7 +5,6 @@ using Robust.Client.Interfaces.Input;
using Robust.Client.Interfaces.Placement;
using Robust.Client.Interfaces.ResourceManagement;
using Robust.Client.Interfaces.State;
using Robust.Client.State.States;
using Robust.Shared.Input;
using Robust.Shared.Interfaces.Configuration;
using Robust.Shared.Interfaces.Map;

View File

@@ -1,5 +1,6 @@
using Content.Client.GameObjects.Components.Mobs;
using Content.Client.UserInterface;
using Content.Client.UserInterface.Stylesheets;
using Robust.Client.Interfaces.GameObjects.Components;
using Robust.Client.Interfaces.ResourceManagement;
using Robust.Client.UserInterface;
@@ -68,7 +69,7 @@ namespace Content.Client.GameObjects.Components.Actor
(SubText = new Label
{
SizeFlagsVertical = SizeFlags.None,
StyleClasses = {NanoStyle.StyleClassLabelSubText}
StyleClasses = {StyleNano.StyleClassLabelSubText}
})
}
}

View File

@@ -1,4 +1,5 @@
using Content.Client.UserInterface;
using Content.Client.UserInterface.Stylesheets;
using Content.Client.Utility;
using Robust.Shared.Timing;
using Content.Shared.GameObjects.Components.Chemistry;
@@ -7,6 +8,7 @@ using Robust.Client.UserInterface.Controls;
using Robust.Shared.GameObjects;
using Robust.Shared.Localization;
using Robust.Shared.ViewVariables;
using Content.Shared.Chemistry;
namespace Content.Client.GameObjects.Components.Chemistry
{
@@ -16,8 +18,8 @@ namespace Content.Client.GameObjects.Components.Chemistry
[RegisterComponent]
public class InjectorComponent : SharedInjectorComponent, IItemStatus
{
[ViewVariables] private int CurrentVolume { get; set; }
[ViewVariables] private int TotalVolume { get; set; }
[ViewVariables] private ReagentUnit CurrentVolume { get; set; }
[ViewVariables] private ReagentUnit TotalVolume { get; set; }
[ViewVariables] private InjectorToggleMode CurrentMode { get; set; }
[ViewVariables(VVAccess.ReadWrite)] private bool _uiUpdateNeeded;
@@ -28,7 +30,7 @@ namespace Content.Client.GameObjects.Components.Chemistry
//Handle net updates
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
{
var cast = (InjectorComponentState)curState;
var cast = (InjectorComponentState) curState;
if (cast != null)
{
CurrentVolume = cast.CurrentVolume;
@@ -49,7 +51,7 @@ namespace Content.Client.GameObjects.Components.Chemistry
public StatusControl(InjectorComponent parent)
{
_parent = parent;
_label = new RichTextLabel { StyleClasses = { NanoStyle.StyleClassItemStatus } };
_label = new RichTextLabel { StyleClasses = { StyleNano.StyleClassItemStatus } };
AddChild(_label);
parent._uiUpdateNeeded = true;

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic;
using Content.Client.UserInterface;
using Content.Client.UserInterface.Stylesheets;
using Content.Shared.Chemistry;
using Content.Shared.GameObjects.Components.Chemistry;
using Robust.Client.Graphics.Drawing;
@@ -170,7 +171,7 @@ namespace Content.Client.GameObjects.Components.Chemistry
Title = castState.DispenserName;
UpdateContainerInfo(castState);
switch (castState.SelectedDispenseAmount)
switch (castState.SelectedDispenseAmount.Int())
{
case 1:
DispenseButton1.Pressed = true;
@@ -218,7 +219,7 @@ namespace Content.Client.GameObjects.Components.Chemistry
new Label
{
Text = $"{state.BeakerCurrentVolume}/{state.BeakerMaxVolume}",
StyleClasses = {NanoStyle.StyleClassLabelSecondaryColor}
StyleClasses = {StyleNano.StyleClassLabelSecondaryColor}
}
}
});
@@ -247,12 +248,12 @@ namespace Content.Client.GameObjects.Components.Chemistry
new Label
{
Text = $"{name}: ",
StyleClasses = {NanoStyle.StyleClassPowerStateGood}
StyleClasses = {StyleNano.StyleClassPowerStateGood}
},
new Label
{
Text = $"{reagent.Quantity}u",
StyleClasses = {NanoStyle.StyleClassPowerStateGood}
StyleClasses = {StyleNano.StyleClassPowerStateGood}
}
}
});
@@ -267,7 +268,7 @@ namespace Content.Client.GameObjects.Components.Chemistry
new Label
{
Text = $"{reagent.Quantity}u",
StyleClasses = {NanoStyle.StyleClassLabelSecondaryColor}
StyleClasses = {StyleNano.StyleClassLabelSecondaryColor}
}
}
});

View File

@@ -0,0 +1,83 @@
using System;
using Content.Client.Command;
using Content.Shared.GameObjects.Components.Command;
using Robust.Client.GameObjects.Components.UserInterface;
using Robust.Shared.GameObjects.Components.UserInterface;
using Robust.Shared.Interfaces.Timing;
using Robust.Shared.IoC;
using Robust.Shared.ViewVariables;
namespace Content.Client.GameObjects.Components.Command
{
public class CommunicationsConsoleBoundUserInterface : BoundUserInterface
{
[ViewVariables]
private CommunicationsConsoleMenu _menu;
[Dependency] private IGameTiming _gameTiming;
public bool CountdownStarted { get; private set; }
public int Countdown => _expectedCountdownTime == null
? 0 : Math.Max((int)_expectedCountdownTime.Value.Subtract(_gameTiming.CurTime).TotalSeconds, 0);
private TimeSpan? _expectedCountdownTime;
public CommunicationsConsoleBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
{
}
protected override void Open()
{
base.Open();
_menu = new CommunicationsConsoleMenu(this);
_menu.OnClose += Close;
_menu.OpenCentered();
}
public void EmergencyShuttleButtonPressed()
{
if(CountdownStarted)
RecallShuttle();
else
CallShuttle();
}
public void CallShuttle()
{
SendMessage(new CommunicationsConsoleCallEmergencyShuttleMessage());
}
public void RecallShuttle()
{
SendMessage(new CommunicationsConsoleRecallEmergencyShuttleMessage());
}
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
{
switch (message)
{
}
}
protected override void UpdateState(BoundUserInterfaceState state)
{
if (!(state is CommunicationsConsoleInterfaceState commsState))
return;
_expectedCountdownTime = commsState.ExpectedCountdownEnd;
CountdownStarted = commsState.CountdownStarted;
_menu?.UpdateCountdown();
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposing) return;
_menu?.Dispose();
}
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using Content.Shared.GameObjects.Components.Instruments;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Client.Audio.Midi;
using Robust.Shared.Audio.Midi;
@@ -28,6 +29,7 @@ namespace Content.Client.GameObjects.Components.Instruments
[Dependency] private readonly IGameTiming _timing;
#pragma warning restore 649
[CanBeNull]
private IMidiRenderer _renderer;
private int _instrumentProgram = 1;
@@ -42,8 +44,14 @@ namespace Content.Client.GameObjects.Components.Instruments
[ViewVariables(VVAccess.ReadWrite)]
public bool LoopMidi
{
get => _renderer.LoopMidi;
set => _renderer.LoopMidi = value;
get => _renderer?.LoopMidi ?? false;
set
{
if (_renderer != null)
{
_renderer.LoopMidi = value;
}
}
}
/// <summary>
@@ -53,9 +61,13 @@ namespace Content.Client.GameObjects.Components.Instruments
public int InstrumentProgram
{
get => _instrumentProgram;
set {
set
{
_instrumentProgram = value;
_renderer.MidiProgram = _instrumentProgram;
if (_renderer != null)
{
_renderer.MidiProgram = _instrumentProgram;
}
}
}
@@ -63,22 +75,26 @@ namespace Content.Client.GameObjects.Components.Instruments
/// Whether there's a midi song being played or not.
/// </summary>
[ViewVariables]
public bool IsMidiOpen => _renderer.Status == MidiRendererStatus.File;
public bool IsMidiOpen => _renderer?.Status == MidiRendererStatus.File;
/// <summary>
/// Whether the midi renderer is listening for midi input or not.
/// </summary>
[ViewVariables]
public bool IsInputOpen => _renderer.Status == MidiRendererStatus.Input;
public bool IsInputOpen => _renderer?.Status == MidiRendererStatus.Input;
public override void Initialize()
{
base.Initialize();
IoCManager.InjectDependencies(this);
_renderer = _midiManager.GetNewRenderer();
_renderer.MidiProgram = _instrumentProgram;
_renderer.TrackingEntity = Owner;
_renderer.OnMidiPlayerFinished += () => { OnMidiPlaybackEnded?.Invoke(); };
if (_renderer != null)
{
_renderer.MidiProgram = _instrumentProgram;
_renderer.TrackingEntity = Owner;
_renderer.OnMidiPlayerFinished += () => { OnMidiPlaybackEnded?.Invoke(); };
}
}
protected override void Shutdown()
@@ -93,9 +109,16 @@ namespace Content.Client.GameObjects.Components.Instruments
serializer.DataField(ref _instrumentProgram, "program", 1);
}
public override void HandleMessage(ComponentMessage message, INetChannel netChannel = null, IComponent component = null)
public override void HandleMessage(ComponentMessage message, INetChannel netChannel = null,
IComponent component = null)
{
base.HandleMessage(message, netChannel, component);
if (_renderer == null)
{
return;
}
switch (message)
{
case InstrumentMidiEventMessage midiEventMessage:
@@ -107,8 +130,8 @@ namespace Content.Client.GameObjects.Components.Instruments
case InstrumentStopMidiMessage _:
_renderer.StopAllNotes();
if(IsInputOpen) CloseInput();
if(IsMidiOpen) CloseMidi();
if (IsInputOpen) CloseInput();
if (IsMidiOpen) CloseMidi();
break;
}
}
@@ -116,7 +139,7 @@ namespace Content.Client.GameObjects.Components.Instruments
/// <inheritdoc cref="MidiRenderer.OpenInput"/>
public bool OpenInput()
{
if (_renderer.OpenInput())
if (_renderer != null && _renderer.OpenInput())
{
_renderer.OnMidiEvent += RendererOnMidiEvent;
return true;
@@ -128,28 +151,37 @@ namespace Content.Client.GameObjects.Components.Instruments
/// <inheritdoc cref="MidiRenderer.CloseInput"/>
public bool CloseInput()
{
if (!_renderer.CloseInput()) return false;
if (_renderer == null || !_renderer.CloseInput())
{
return false;
}
_renderer.OnMidiEvent -= RendererOnMidiEvent;
return true;
}
/// <inheritdoc cref="MidiRenderer.OpenMidi(string)"/>
public bool OpenMidi(string filename)
{
if (!_renderer.OpenMidi(filename)) return false;
if (_renderer == null || !_renderer.OpenMidi(filename))
{
return false;
}
_renderer.OnMidiEvent += RendererOnMidiEvent;
return true;
}
/// <inheritdoc cref="MidiRenderer.CloseMidi"/>
public bool CloseMidi()
{
if (!_renderer.CloseMidi()) return false;
if (_renderer == null || !_renderer.CloseMidi())
{
return false;
}
_renderer.OnMidiEvent -= RendererOnMidiEvent;
return true;
}
/// <summary>

View File

@@ -1,6 +1,7 @@
using System;
using System.Linq;
using Content.Client.UserInterface;
using Content.Client.UserInterface.Stylesheets;
using Content.Shared.Preferences.Appearance;
using JetBrains.Annotations;
using Robust.Client.GameObjects.Components.UserInterface;
@@ -126,9 +127,9 @@ namespace Content.Client.GameObjects.Components
var vBox = new VBoxContainer();
AddChild(vBox);
vBox.AddChild(_colorSliderR = new ColorSlider(NanoStyle.StyleClassSliderRed));
vBox.AddChild(_colorSliderG = new ColorSlider(NanoStyle.StyleClassSliderGreen));
vBox.AddChild(_colorSliderB = new ColorSlider(NanoStyle.StyleClassSliderBlue));
vBox.AddChild(_colorSliderR = new ColorSlider(StyleNano.StyleClassSliderRed));
vBox.AddChild(_colorSliderG = new ColorSlider(StyleNano.StyleClassSliderGreen));
vBox.AddChild(_colorSliderB = new ColorSlider(StyleNano.StyleClassSliderBlue));
Action colorValueChanged = ColorValueChanged;
_colorSliderR.OnValueChanged += colorValueChanged;

View File

@@ -76,10 +76,6 @@ namespace Content.Client.GameObjects.Components.Mobs
private void PlayerDetached()
{
if (!CurrentlyControlled)
{
return;
}
_ui?.Dispose();
_ui = null;
}

View File

@@ -0,0 +1,108 @@
using Content.Client.UserInterface;
using Content.Shared.GameObjects.Components.Observer;
using Robust.Client.GameObjects;
using Robust.Client.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Network;
using Robust.Shared.IoC;
using Robust.Shared.ViewVariables;
namespace Content.Client.GameObjects.Components.Observer
{
[RegisterComponent]
public class GhostComponent : SharedGhostComponent
{
private GhostGui _gui;
[ViewVariables(VVAccess.ReadOnly)]
public bool CanReturnToBody { get; private set; } = true;
private bool _isAttached;
#pragma warning disable 649
[Dependency] private readonly IGameHud _gameHud;
[Dependency] private readonly IPlayerManager _playerManager;
[Dependency] private IComponentManager _componentManager;
#pragma warning restore 649
public override void OnRemove()
{
base.OnRemove();
_gui?.Dispose();
// PlayerDetachedMsg might not fire due to deletion order so...
if (_isAttached)
{
SetGhostVisibility(false);
}
}
private void SetGhostVisibility(bool visibility)
{
foreach (var ghost in _componentManager.GetAllComponents(typeof(GhostComponent)))
{
if (ghost.Owner.TryGetComponent(out SpriteComponent component))
component.Visible = visibility;
}
}
public override void Initialize()
{
base.Initialize();
if (Owner.TryGetComponent(out SpriteComponent component))
component.Visible = _playerManager.LocalPlayer.ControlledEntity?.HasComponent<GhostComponent>() ?? false;
}
public override void HandleMessage(ComponentMessage message, INetChannel netChannel = null,
IComponent component = null)
{
base.HandleMessage(message, netChannel, component);
switch (message)
{
case PlayerAttachedMsg _:
if (_gui == null)
{
_gui = new GhostGui(this);
}
else
{
_gui.Orphan();
}
_gameHud.HandsContainer.AddChild(_gui);
SetGhostVisibility(true);
_isAttached = true;
break;
case PlayerDetachedMsg _:
_gui.Parent?.RemoveChild(_gui);
SetGhostVisibility(false);
_isAttached = false;
break;
}
}
public void SendReturnToBodyMessage() => SendNetworkMessage(new ReturnToBodyComponentMessage());
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
{
base.HandleComponentState(curState, nextState);
if (!(curState is GhostComponentState state)) return;
CanReturnToBody = state.CanReturnToBody;
if (Owner == _playerManager.LocalPlayer.ControlledEntity)
{
_gui?.Update();
}
}
}
}

View File

@@ -1,5 +1,6 @@
using System;
using Content.Client.UserInterface;
using Content.Client.UserInterface.Stylesheets;
using Content.Shared.GameObjects.Components.Power;
using Robust.Client.GameObjects.Components.UserInterface;
using Robust.Client.Graphics.Drawing;
@@ -47,15 +48,15 @@ namespace Content.Client.GameObjects.Components.Power
{
case ApcExternalPowerState.None:
_externalPowerStateLabel.Text = "None";
_externalPowerStateLabel.SetOnlyStyleClass(NanoStyle.StyleClassPowerStateNone);
_externalPowerStateLabel.SetOnlyStyleClass(StyleNano.StyleClassPowerStateNone);
break;
case ApcExternalPowerState.Low:
_externalPowerStateLabel.Text = "Low";
_externalPowerStateLabel.SetOnlyStyleClass(NanoStyle.StyleClassPowerStateLow);
_externalPowerStateLabel.SetOnlyStyleClass(StyleNano.StyleClassPowerStateLow);
break;
case ApcExternalPowerState.Good:
_externalPowerStateLabel.Text = "Good";
_externalPowerStateLabel.SetOnlyStyleClass(NanoStyle.StyleClassPowerStateGood);
_externalPowerStateLabel.SetOnlyStyleClass(StyleNano.StyleClassPowerStateGood);
break;
default:
throw new ArgumentOutOfRangeException();
@@ -140,7 +141,7 @@ namespace Content.Client.GameObjects.Components.Power
var externalStatus = new HBoxContainer();
var externalStatusLabel = new Label {Text = "External Power: "};
ExternalPowerStateLabel = new Label {Text = "Good"};
ExternalPowerStateLabel.SetOnlyStyleClass(NanoStyle.StyleClassPowerStateGood);
ExternalPowerStateLabel.SetOnlyStyleClass(StyleNano.StyleClassPowerStateGood);
externalStatus.AddChild(externalStatusLabel);
externalStatus.AddChild(ExternalPowerStateLabel);
rows.AddChild(externalStatus);

View File

@@ -17,9 +17,9 @@ namespace Content.Client.GameObjects.Components.Research
private IPrototypeManager _prototypeManager;
#pragma warning restore
[ViewVariables]
private LatheMenu menu;
private LatheMenu _menu;
[ViewVariables]
private LatheQueueMenu queueMenu;
private LatheQueueMenu _queueMenu;
public MaterialStorageComponent Storage { get; private set; }
public SharedLatheComponent Lathe { get; private set; }
@@ -48,30 +48,30 @@ namespace Content.Client.GameObjects.Components.Research
Lathe = lathe;
Database = database;
menu = new LatheMenu(this);
queueMenu = new LatheQueueMenu { Owner = this };
_menu = new LatheMenu(this);
_queueMenu = new LatheQueueMenu { Owner = this };
menu.OnClose += Close;
_menu.OnClose += Close;
menu.Populate();
menu.PopulateMaterials();
_menu.Populate();
_menu.PopulateMaterials();
menu.QueueButton.OnPressed += (args) => { queueMenu.OpenCentered(); };
_menu.QueueButton.OnPressed += (args) => { _queueMenu.OpenCentered(); };
menu.ServerConnectButton.OnPressed += (args) =>
_menu.ServerConnectButton.OnPressed += (args) =>
{
SendMessage(new SharedLatheComponent.LatheServerSelectionMessage());
};
menu.ServerSyncButton.OnPressed += (args) =>
_menu.ServerSyncButton.OnPressed += (args) =>
{
SendMessage(new SharedLatheComponent.LatheServerSyncMessage());
};
storage.OnMaterialStorageChanged += menu.PopulateDisabled;
storage.OnMaterialStorageChanged += menu.PopulateMaterials;
storage.OnMaterialStorageChanged += _menu.PopulateDisabled;
storage.OnMaterialStorageChanged += _menu.PopulateMaterials;
menu.OpenCentered();
_menu.OpenCentered();
}
public void Queue(LatheRecipePrototype recipe, int quantity = 1)
@@ -85,10 +85,10 @@ namespace Content.Client.GameObjects.Components.Research
{
case SharedLatheComponent.LatheProducingRecipeMessage msg:
if (!_prototypeManager.TryIndex(msg.ID, out LatheRecipePrototype recipe)) break;
queueMenu?.SetInfo(recipe);
_queueMenu?.SetInfo(recipe);
break;
case SharedLatheComponent.LatheStoppedProducingRecipeMessage _:
queueMenu?.ClearInfo();
_queueMenu?.ClearInfo();
break;
case SharedLatheComponent.LatheFullQueueMessage msg:
_queuedRecipes.Clear();
@@ -97,7 +97,7 @@ namespace Content.Client.GameObjects.Components.Research
if (!_prototypeManager.TryIndex(id, out LatheRecipePrototype recipePrototype)) break;
_queuedRecipes.Enqueue(recipePrototype);
}
queueMenu?.PopulateList();
_queueMenu?.PopulateList();
break;
}
}
@@ -106,8 +106,8 @@ namespace Content.Client.GameObjects.Components.Research
{
base.Dispose(disposing);
if (!disposing) return;
menu?.Dispose();
queueMenu?.Dispose();
_menu?.Dispose();
_queueMenu?.Dispose();
}
}
}

View File

@@ -1,4 +1,4 @@
using Content.Client.UserInterface;
using Content.Client.UserInterface.Stylesheets;
using Content.Client.Utility;
using Content.Shared.GameObjects.Components;
using Robust.Client.UserInterface;
@@ -13,21 +13,19 @@ namespace Content.Client.GameObjects.Components
[RegisterComponent]
public class StackComponent : SharedStackComponent, IItemStatus
{
[ViewVariables] public int Count { get; private set; }
[ViewVariables] public int MaxCount { get; private set; }
[ViewVariables(VVAccess.ReadWrite)] private bool _uiUpdateNeeded;
public Control MakeControl() => new StatusControl(this);
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
public override int Count
{
if (!(curState is StackComponentState cast))
return;
get => base.Count;
set
{
base.Count = value;
Count = cast.Count;
MaxCount = cast.MaxCount;
_uiUpdateNeeded = true;
_uiUpdateNeeded = true;
}
}
private sealed class StatusControl : Control
@@ -38,7 +36,7 @@ namespace Content.Client.GameObjects.Components
public StatusControl(StackComponent parent)
{
_parent = parent;
_label = new RichTextLabel {StyleClasses = {NanoStyle.StyleClassItemStatus}};
_label = new RichTextLabel {StyleClasses = {StyleNano.StyleClassItemStatus}};
AddChild(_label);
parent._uiUpdateNeeded = true;

View File

@@ -1,6 +1,7 @@
using System;
using Content.Client.Animations;
using Content.Client.UserInterface;
using Content.Client.UserInterface.Stylesheets;
using Content.Client.Utility;
using Content.Shared.GameObjects;
using Content.Shared.GameObjects.Components.Weapons.Ranged;
@@ -174,7 +175,7 @@ namespace Content.Client.GameObjects.Components.Weapons.Ranged
(_noMagazineLabel = new Label
{
Text = "No Magazine!",
StyleClasses = {NanoStyle.StyleClassItemStatus}
StyleClasses = {StyleNano.StyleClassItemStatus}
})
}
},

View File

@@ -1,5 +1,6 @@
using System;
using Content.Client.UserInterface;
using Content.Client.UserInterface.Stylesheets;
using Content.Client.Utility;
using Content.Shared.GameObjects;
using Content.Shared.GameObjects.Components;
@@ -46,7 +47,7 @@ namespace Content.Client.GameObjects.Components
public StatusControl(WelderComponent parent)
{
_parent = parent;
_label = new RichTextLabel {StyleClasses = {NanoStyle.StyleClassItemStatus}};
_label = new RichTextLabel {StyleClasses = {StyleNano.StyleClassItemStatus}};
AddChild(_label);
parent._uiUpdateNeeded = true;

View File

@@ -1,6 +1,7 @@
using System;
using Content.Client.Interfaces;
using Content.Client.State;
using Content.Client.UserInterface;
using Content.Shared;
using Robust.Client.Interfaces.State;
using Robust.Shared.Interfaces.Network;
@@ -35,10 +36,13 @@ namespace Content.Client.GameTicking
_netManager.RegisterNetMessage<MsgTickerJoinGame>(nameof(MsgTickerJoinGame), JoinGame);
_netManager.RegisterNetMessage<MsgTickerLobbyStatus>(nameof(MsgTickerLobbyStatus), LobbyStatus);
_netManager.RegisterNetMessage<MsgTickerLobbyInfo>(nameof(MsgTickerLobbyInfo), LobbyInfo);
_netManager.RegisterNetMessage<MsgRoundEndMessage>(nameof(MsgRoundEndMessage), RoundEnd);
_initialized = true;
}
private void JoinLobby(MsgTickerJoinLobby message)
{
_stateManager.RequestStateChange<LobbyState>();
@@ -64,5 +68,13 @@ namespace Content.Client.GameTicking
{
_stateManager.RequestStateChange<GameScreen>();
}
private void RoundEnd(MsgRoundEndMessage message)
{
//This is not ideal at all, but I don't see an immediately better fit anywhere else.
var roundEnd = new RoundEndSummaryWindow(message.GamemodeTitle, message.RoundDuration, message.AllPlayersEndInfo);
}
}
}

View File

@@ -1,10 +1,13 @@
using Content.Client.GameObjects.Components.Instruments;
using Content.Client.UserInterface.Stylesheets;
using Robust.Client.Audio.Midi;
using Robust.Client.Graphics.Drawing;
using Robust.Client.Interfaces.UserInterface;
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.Log;
using Robust.Shared.Maths;
@@ -27,7 +30,7 @@ namespace Content.Client.Instruments
public InstrumentMenu(InstrumentBoundUserInterface owner)
{
IoCManager.InjectDependencies(this);
Title = "Instrument";
Title = Loc.GetString("Instrument");
_owner = owner;
@@ -55,7 +58,7 @@ namespace Content.Client.Instruments
midiInputButton = new Button()
{
Text = "MIDI Input",
Text = Loc.GetString("MIDI Input"),
TextAlign = Label.AlignMode.Center,
SizeFlagsHorizontal = SizeFlags.FillExpand,
SizeFlagsStretchRatio = 1,
@@ -73,7 +76,7 @@ namespace Content.Client.Instruments
var midiFileButton = new Button()
{
Text = "Open File",
Text = Loc.GetString("Play MIDI File"),
TextAlign = Label.AlignMode.Center,
SizeFlagsHorizontal = SizeFlags.FillExpand,
SizeFlagsStretchRatio = 1,
@@ -91,7 +94,7 @@ namespace Content.Client.Instruments
midiLoopButton = new Button()
{
Text = "Loop",
Text = Loc.GetString("Loop"),
TextAlign = Label.AlignMode.Center,
SizeFlagsHorizontal = SizeFlags.FillExpand,
SizeFlagsStretchRatio = 1,
@@ -110,7 +113,7 @@ namespace Content.Client.Instruments
midiStopButton = new Button()
{
Text = "Stop",
Text = Loc.GetString("Stop"),
TextAlign = Label.AlignMode.Center,
SizeFlagsHorizontal = SizeFlags.FillExpand,
SizeFlagsStretchRatio = 1,
@@ -132,6 +135,26 @@ namespace Content.Client.Instruments
margin.AddChild(vBox);
if (!_midiManager.IsAvailable)
{
margin.AddChild(new PanelContainer
{
MouseFilter = MouseFilterMode.Stop,
PanelOverride = new StyleBoxFlat {BackgroundColor = Color.Black.WithAlpha(0.90f)},
Children =
{
new Label
{
Align = Label.AlignMode.Center,
SizeFlagsVertical = SizeFlags.ShrinkCenter,
SizeFlagsHorizontal = SizeFlags.ShrinkCenter,
StyleClasses = {StyleNano.StyleClassLabelBig},
Text = Loc.GetString("MIDI support is currently\nnot available on your platform.")
}
}
});
}
Contents.AddChild(margin);
}
@@ -148,7 +171,8 @@ namespace Content.Client.Instruments
private async void MidiFileButtonOnOnPressed(BaseButton.ButtonEventArgs obj)
{
var filename = await _fileDialogManager.OpenFile();
var filters = new FileDialogFilters(new FileDialogFilters.Group("mid", "midi"));
var filename = await _fileDialogManager.OpenFile(filters);
if (filename == null) return;
@@ -160,7 +184,7 @@ namespace Content.Client.Instruments
if (!_owner.Instrument.OpenMidi(filename)) return;
MidiPlaybackSetButtonsDisabled(false);
if(midiInputButton.Pressed)
if (midiInputButton.Pressed)
midiInputButton.Pressed = false;
}

View File

@@ -43,8 +43,6 @@ namespace Content.Client.State
public override void Shutdown()
{
_playerManager.LocalPlayer.DetachEntity();
_inputManager.KeyBindStateChanged -= OnKeyBindStateChanged;
}

View File

@@ -0,0 +1,308 @@
using Content.Client.UserInterface.Controls;
using Content.Client.UserInterface.Stylesheets;
using Content.Client.Utility;
using Robust.Client.Graphics.Drawing;
using Robust.Client.Interfaces;
using Robust.Client.Interfaces.UserInterface;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Interfaces.Network;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using static Content.Client.StaticIoC;
namespace Content.Client.State
{
public class LauncherConnecting : Robust.Client.State.State
{
#pragma warning disable 649
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager;
[Dependency] private readonly IStylesheetManager _stylesheetManager;
[Dependency] private readonly IClientNetManager _clientNetManager;
[Dependency] private readonly IGameController _gameController;
[Dependency] private readonly IBaseClient _baseClient;
#pragma warning restore 649
private Control _control;
private Label _connectStatus;
private Control _connectingStatus;
private Control _connectFail;
private Label _connectFailReason;
private Control _disconnected;
public override void Startup()
{
var panelTex = ResC.GetTexture("/Nano/button.svg.96dpi.png");
var back = new StyleBoxTexture
{
Texture = panelTex,
Modulate = new Color(32, 32, 48),
};
back.SetPatchMargin(StyleBox.Margin.All, 10);
Button exitButton;
Button reconnectButton;
Button retryButton;
var address = _gameController.LaunchState.Ss14Address ?? _gameController.LaunchState.ConnectAddress;
VBoxContainer disconnected;
_control = new Control
{
Stylesheet = _stylesheetManager.SheetSpace,
Children =
{
new PanelContainer
{
PanelOverride = back
},
new VBoxContainer
{
SeparationOverride = 0,
CustomMinimumSize = (300, 200),
Children =
{
new HBoxContainer
{
Children =
{
new MarginContainer
{
MarginLeftOverride = 8,
Children =
{
new Label
{
Text = Loc.GetString("Space Station 14"),
StyleClasses = {StyleBase.StyleClassLabelHeading},
VAlign = Label.VAlignMode.Center
},
}
},
(exitButton = new Button
{
Text = Loc.GetString("Exit"),
SizeFlagsHorizontal = Control.SizeFlags.ShrinkEnd | Control.SizeFlags.Expand
}),
}
},
// Line
new HighDivider(),
new MarginContainer
{
SizeFlagsVertical = Control.SizeFlags.FillExpand,
MarginLeftOverride = 4,
MarginRightOverride = 4,
MarginTopOverride = 4,
Children =
{
new VBoxContainer
{
SeparationOverride = 0,
Children =
{
new Control
{
SizeFlagsVertical = Control.SizeFlags.FillExpand,
Children =
{
(_connectingStatus = new VBoxContainer
{
SeparationOverride = 0,
Children =
{
new Label
{
Text = Loc.GetString("Connecting to server..."),
Align = Label.AlignMode.Center,
},
(_connectStatus = new Label
{
StyleClasses = {StyleBase.StyleClassLabelSubText},
Align = Label.AlignMode.Center,
}),
}
}),
(_connectFail = new VBoxContainer
{
Visible = false,
SeparationOverride = 0,
Children =
{
(_connectFailReason = new Label
{
Align = Label.AlignMode.Center
}),
(retryButton = new Button
{
Text = "Retry",
SizeFlagsHorizontal = Control.SizeFlags.ShrinkCenter,
SizeFlagsVertical =
Control.SizeFlags.Expand |
Control.SizeFlags.ShrinkEnd
})
}
}),
(_disconnected = new VBoxContainer
{
SeparationOverride = 0,
Children =
{
new Label
{
Text = "Disconnected from server:",
Align = Label.AlignMode.Center
},
new Label
{
Text = _baseClient.LastDisconnectReason,
Align = Label.AlignMode.Center
},
(reconnectButton = new Button
{
Text = "Reconnect",
SizeFlagsHorizontal = Control.SizeFlags.ShrinkCenter,
SizeFlagsVertical =
Control.SizeFlags.Expand |
Control.SizeFlags.ShrinkEnd
})
}
})
}
},
// Padding.
new Control {CustomMinimumSize = (0, 8)},
new Label
{
Text = address,
StyleClasses = {StyleBase.StyleClassLabelSubText},
SizeFlagsHorizontal = Control.SizeFlags.ShrinkCenter,
}
}
},
}
},
// Line
new PanelContainer
{
PanelOverride = new StyleBoxFlat
{
BackgroundColor = Color.FromHex("#444"),
ContentMarginTopOverride = 2
},
},
new MarginContainer
{
MarginLeftOverride = 12,
MarginRightOverride = 4,
Children =
{
new HBoxContainer
{
SizeFlagsVertical = Control.SizeFlags.ShrinkEnd,
Children =
{
new Label
{
Text = Loc.GetString("Don't die!"),
StyleClasses = {StyleBase.StyleClassLabelSubText}
},
new Label
{
Text = "ver 0.1",
SizeFlagsHorizontal =
Control.SizeFlags.Expand | Control.SizeFlags.ShrinkEnd,
StyleClasses = {StyleBase.StyleClassLabelSubText}
}
}
}
}
},
}
},
}
};
_userInterfaceManager.StateRoot.AddChild(_control);
LayoutContainer.SetAnchorPreset(_control, LayoutContainer.LayoutPreset.Center);
LayoutContainer.SetGrowHorizontal(_control, LayoutContainer.GrowDirection.Both);
LayoutContainer.SetGrowVertical(_control, LayoutContainer.GrowDirection.Both);
exitButton.OnPressed += args =>
{
_gameController.Shutdown("Exit button pressed");
};
void Retry(BaseButton.ButtonEventArgs args)
{
_baseClient.ConnectToServer(_gameController.LaunchState.ConnectEndpoint);
SetActivePage(Page.Connecting);
}
reconnectButton.OnPressed += Retry;
retryButton.OnPressed += Retry;
_clientNetManager.ConnectFailed += (sender, args) =>
{
_connectFailReason.Text = Loc.GetString("Failed to connect to server:\n{0}", args.Reason);
SetActivePage(Page.ConnectFailed);
};
_clientNetManager.ClientConnectStateChanged += ConnectStateChanged;
SetActivePage(Page.Connecting);
ConnectStateChanged(_clientNetManager.ClientConnectState);
}
private void ConnectStateChanged(ClientConnectionState state)
{
_connectStatus.Text = Loc.GetString(state switch
{
ClientConnectionState.NotConnecting => "You should not be seeing this",
ClientConnectionState.ResolvingHost => "Resolving server address...",
ClientConnectionState.EstablishingConnection => "Establishing initial connection...",
ClientConnectionState.Handshake => "Doing handshake...",
ClientConnectionState.Connected => "Synchronizing game state...",
_ => state.ToString()
});
}
public override void Shutdown()
{
_control.Dispose();
}
public void SetDisconnected()
{
SetActivePage(Page.Disconnected);
}
private void SetActivePage(Page page)
{
_connectingStatus.Visible = page == Page.Connecting;
_connectFail.Visible = page == Page.ConnectFailed;
_disconnected.Visible = page == Page.Disconnected;
}
private enum Page
{
Connecting,
ConnectFailed,
Disconnected,
}
}
}

View File

@@ -0,0 +1,329 @@
using System;
using System.Text.RegularExpressions;
using Robust.Client;
using Robust.Client.Interfaces;
using Robust.Client.Interfaces.ResourceManagement;
using Robust.Client.Interfaces.UserInterface;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Interfaces.Configuration;
using Robust.Shared.Interfaces.Network;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Utility;
namespace Content.Client.State
{
/// <summary>
/// Main menu screen that is the first screen to be displayed when the game starts.
/// </summary>
// Instantiated dynamically through the StateManager, Dependencies will be resolved.
public class MainScreen : Robust.Client.State.State
{
private const string PublicServerAddress = "server.spacestation14.io";
#pragma warning disable 649
[Dependency] private readonly IBaseClient _client;
[Dependency] private readonly IClientNetManager _netManager;
[Dependency] private readonly IConfigurationManager _configurationManager;
[Dependency] private readonly IGameController _controllerProxy;
[Dependency] private readonly ILocalizationManager _loc;
[Dependency] private readonly IResourceCache _resourceCache;
[Dependency] private readonly IUserInterfaceManager userInterfaceManager;
#pragma warning restore 649
private MainMenuControl _mainMenuControl;
private OptionsMenu OptionsMenu;
private bool _isConnecting;
// ReSharper disable once InconsistentNaming
private static readonly Regex IPv6Regex = new Regex(@"\[(.*:.*:.*)](?::(\d+))?");
/// <inheritdoc />
public override void Startup()
{
_mainMenuControl = new MainMenuControl(_resourceCache, _configurationManager);
userInterfaceManager.StateRoot.AddChild(_mainMenuControl);
_mainMenuControl.QuitButton.OnPressed += QuitButtonPressed;
_mainMenuControl.OptionsButton.OnPressed += OptionsButtonPressed;
_mainMenuControl.DirectConnectButton.OnPressed += DirectConnectButtonPressed;
_mainMenuControl.JoinPublicServerButton.OnPressed += JoinPublicServerButtonPressed;
_mainMenuControl.AddressBox.OnTextEntered += AddressBoxEntered;
_client.RunLevelChanged += RunLevelChanged;
OptionsMenu = new OptionsMenu(_configurationManager);
}
/// <inheritdoc />
public override void Shutdown()
{
_client.RunLevelChanged -= RunLevelChanged;
_netManager.ConnectFailed -= _onConnectFailed;
_mainMenuControl.Dispose();
OptionsMenu.Dispose();
}
private void QuitButtonPressed(BaseButton.ButtonEventArgs args)
{
_controllerProxy.Shutdown();
}
private void OptionsButtonPressed(BaseButton.ButtonEventArgs args)
{
OptionsMenu.OpenCentered();
}
private void DirectConnectButtonPressed(BaseButton.ButtonEventArgs args)
{
var input = _mainMenuControl.AddressBox;
TryConnect(input.Text);
}
private void JoinPublicServerButtonPressed(BaseButton.ButtonEventArgs args)
{
TryConnect(PublicServerAddress);
}
private void AddressBoxEntered(LineEdit.LineEditEventArgs args)
{
if (_isConnecting)
{
return;
}
TryConnect(args.Text);
}
private void TryConnect(string address)
{
var inputName = _mainMenuControl.UserNameBox.Text.Trim();
var (nameValid, invalidReason) = UsernameHelpers.IsNameValid(inputName);
if (!nameValid)
{
invalidReason = _loc.GetString(invalidReason);
userInterfaceManager.Popup(
_loc.GetString("Invalid username:\n{0}", invalidReason),
_loc.GetString("Invalid Username"));
return;
}
var configName = _configurationManager.GetCVar<string>("player.name");
if (_mainMenuControl.UserNameBox.Text != configName)
{
_configurationManager.SetCVar("player.name", inputName);
_configurationManager.SaveToFile();
}
_setConnectingState(true);
_netManager.ConnectFailed += _onConnectFailed;
try
{
ParseAddress(address, out var ip, out var port);
_client.ConnectToServer(ip, port);
}
catch (ArgumentException e)
{
userInterfaceManager.Popup($"Unable to connect: {e.Message}", "Connection error.");
Logger.Warning(e.ToString());
_netManager.ConnectFailed -= _onConnectFailed;
}
}
private void RunLevelChanged(object obj, RunLevelChangedEventArgs args)
{
if (args.NewLevel == ClientRunLevel.Initialize)
{
_setConnectingState(false);
_netManager.ConnectFailed -= _onConnectFailed;
}
}
private void ParseAddress(string address, out string ip, out ushort port)
{
var match6 = IPv6Regex.Match(address);
if (match6 != Match.Empty)
{
ip = match6.Groups[1].Value;
if (!match6.Groups[2].Success)
{
port = _client.DefaultPort;
}
else if (!ushort.TryParse(match6.Groups[2].Value, out port))
{
throw new ArgumentException("Not a valid port.");
}
return;
}
// See if the IP includes a port.
var split = address.Split(':');
ip = address;
port = _client.DefaultPort;
if (split.Length > 2)
{
throw new ArgumentException("Not a valid Address.");
}
// IP:port format.
if (split.Length == 2)
{
ip = split[0];
if (!ushort.TryParse(split[1], out port))
{
throw new ArgumentException("Not a valid port.");
}
}
}
private void _onConnectFailed(object _, NetConnectFailArgs args)
{
userInterfaceManager.Popup($"Failed to connect:\n{args.Reason}");
_netManager.ConnectFailed -= _onConnectFailed;
_setConnectingState(false);
}
private void _setConnectingState(bool state)
{
_isConnecting = state;
_mainMenuControl.DirectConnectButton.Disabled = state;
#if FULL_RELEASE
_mainMenuControl.JoinPublicServerButton.Disabled = state;
#endif
}
private sealed class MainMenuControl : Control
{
private readonly IResourceCache _resourceCache;
private readonly IConfigurationManager _configurationManager;
public LineEdit UserNameBox { get; private set; }
public Button JoinPublicServerButton { get; private set; }
public LineEdit AddressBox { get; private set; }
public Button DirectConnectButton { get; private set; }
public Button OptionsButton { get; private set; }
public Button QuitButton { get; private set; }
public Label VersionLabel { get; private set; }
public MainMenuControl(IResourceCache resCache, IConfigurationManager configMan)
{
_resourceCache = resCache;
_configurationManager = configMan;
PerformLayout();
}
private void PerformLayout()
{
LayoutContainer.SetAnchorPreset(this, LayoutContainer.LayoutPreset.Wide);
var layout = new LayoutContainer();
AddChild(layout);
var vBox = new VBoxContainer
{
StyleIdentifier = "mainMenuVBox"
};
layout.AddChild(vBox);
LayoutContainer.SetAnchorPreset(vBox, LayoutContainer.LayoutPreset.TopRight);
LayoutContainer.SetMarginRight(vBox, -25);
LayoutContainer.SetMarginTop(vBox, 30);
LayoutContainer.SetGrowHorizontal(vBox, LayoutContainer.GrowDirection.Begin);
var logoTexture = _resourceCache.GetResource<TextureResource>("/Textures/Logo/logo.png");
var logo = new TextureRect
{
Texture = logoTexture,
Stretch = TextureRect.StretchMode.KeepCentered,
};
vBox.AddChild(logo);
var userNameHBox = new HBoxContainer {SeparationOverride = 4};
vBox.AddChild(userNameHBox);
userNameHBox.AddChild(new Label {Text = "Username:"});
var currentUserName = _configurationManager.GetCVar<string>("player.name");
UserNameBox = new LineEdit
{
Text = currentUserName, PlaceHolder = "Username",
SizeFlagsHorizontal = SizeFlags.FillExpand
};
userNameHBox.AddChild(UserNameBox);
JoinPublicServerButton = new Button
{
Text = "Join Public Server",
StyleIdentifier = "mainMenu",
TextAlign = Label.AlignMode.Center,
#if !FULL_RELEASE
Disabled = true,
ToolTip = "Cannot connect to public server with a debug build."
#endif
};
vBox.AddChild(JoinPublicServerButton);
// Separator.
vBox.AddChild(new Control {CustomMinimumSize = (0, 2)});
AddressBox = new LineEdit
{
Text = "localhost",
PlaceHolder = "server address:port",
SizeFlagsHorizontal = SizeFlags.FillExpand
};
vBox.AddChild(AddressBox);
DirectConnectButton = new Button
{
Text = "Direct Connect",
TextAlign = Label.AlignMode.Center,
StyleIdentifier = "mainMenu",
};
vBox.AddChild(DirectConnectButton);
// Separator.
vBox.AddChild(new Control {CustomMinimumSize = (0, 2)});
OptionsButton = new Button
{
Text = "Options",
TextAlign = Label.AlignMode.Center,
StyleIdentifier = "mainMenu",
};
vBox.AddChild(OptionsButton);
QuitButton = new Button
{
Text = "Quit",
TextAlign = Label.AlignMode.Center,
StyleIdentifier = "mainMenu",
};
vBox.AddChild(QuitButton);
VersionLabel = new Label
{
Text = $"v0.1"
};
LayoutContainer.SetAnchorPreset(VersionLabel, LayoutContainer.LayoutPreset.BottomRight);
LayoutContainer.SetGrowHorizontal(VersionLabel, LayoutContainer.GrowDirection.Begin);
LayoutContainer.SetGrowVertical(VersionLabel, LayoutContainer.GrowDirection.Begin);
layout.AddChild(VersionLabel);
}
}
}
}

View File

@@ -9,6 +9,7 @@ using Robust.Shared.Localization;
using Robust.Shared.Maths;
using System;
using System.Collections.Generic;
using Content.Client.UserInterface.Stylesheets;
namespace Content.Client.UserInterface.Cargo
{
@@ -57,7 +58,7 @@ namespace Content.Client.UserInterface.Cargo
var accountName = new HBoxContainer();
var accountNameLabel = new Label {
Text = _loc.GetString("Account Name: "),
StyleClasses = { NanoStyle.StyleClassLabelKeyText }
StyleClasses = { StyleNano.StyleClassLabelKeyText }
};
_accountNameLabel = new Label {
Text = "None" //Owner.Bank.Account.Name
@@ -70,7 +71,7 @@ namespace Content.Client.UserInterface.Cargo
var pointsLabel = new Label
{
Text = _loc.GetString("Points: "),
StyleClasses = { NanoStyle.StyleClassLabelKeyText }
StyleClasses = { StyleNano.StyleClassLabelKeyText }
};
_pointsLabel = new Label
{
@@ -84,7 +85,7 @@ namespace Content.Client.UserInterface.Cargo
var shuttleStatusLabel = new Label
{
Text = _loc.GetString("Shuttle Status: "),
StyleClasses = { NanoStyle.StyleClassLabelKeyText }
StyleClasses = { StyleNano.StyleClassLabelKeyText }
};
_shuttleStatusLabel = new Label
{
@@ -410,13 +411,13 @@ namespace Content.Client.UserInterface.Cargo
ProductName = new Label
{
SizeFlagsHorizontal = SizeFlags.FillExpand,
StyleClasses = { NanoStyle.StyleClassLabelSubText },
StyleClasses = { StyleNano.StyleClassLabelSubText },
ClipText = true
};
Description = new Label
{
SizeFlagsHorizontal = SizeFlags.FillExpand,
StyleClasses = { NanoStyle.StyleClassLabelSubText },
StyleClasses = { StyleNano.StyleClassLabelSubText },
ClipText = true
};
vBox.AddChild(ProductName);
@@ -426,14 +427,14 @@ namespace Content.Client.UserInterface.Cargo
Approve = new Button
{
Text = "Approve",
StyleClasses = { NanoStyle.StyleClassLabelSubText }
StyleClasses = { StyleNano.StyleClassLabelSubText }
};
hBox.AddChild(Approve);
Cancel = new Button
{
Text = "Cancel",
StyleClasses = { NanoStyle.StyleClassLabelSubText }
StyleClasses = { StyleNano.StyleClassLabelSubText }
};
hBox.AddChild(Cancel);

View File

@@ -1,6 +1,7 @@
using System.Linq;
using Content.Client.GameObjects.Components.Mobs;
using Content.Client.Interfaces;
using Content.Client.UserInterface.Stylesheets;
using Content.Client.Utility;
using Content.Shared.Jobs;
using Content.Shared.Preferences;
@@ -67,7 +68,7 @@ namespace Content.Client.UserInterface
{
SizeFlagsHorizontal = SizeFlags.Expand | SizeFlags.ShrinkEnd,
Text = Loc.GetString("Save and close"),
StyleClasses = {NanoStyle.StyleClassButtonBig}
StyleClasses = {StyleNano.StyleClassButtonBig}
};
var topHBox = new HBoxContainer
@@ -83,7 +84,7 @@ namespace Content.Client.UserInterface
new Label
{
Text = Loc.GetString("Character Setup"),
StyleClasses = {NanoStyle.StyleClassLabelHeadingBigger},
StyleClasses = {StyleNano.StyleClassLabelHeadingBigger},
VAlign = Label.VAlignMode.Center,
SizeFlagsHorizontal = SizeFlags.Expand | SizeFlags.ShrinkCenter
}
@@ -99,7 +100,7 @@ namespace Content.Client.UserInterface
{
PanelOverride = new StyleBoxFlat
{
BackgroundColor = NanoStyle.NanoGold,
BackgroundColor = StyleNano.NanoGold,
ContentMarginTopOverride = 2
}
});
@@ -146,7 +147,7 @@ namespace Content.Client.UserInterface
hBox.AddChild(new PanelContainer
{
PanelOverride = new StyleBoxFlat {BackgroundColor = NanoStyle.NanoGold},
PanelOverride = new StyleBoxFlat {BackgroundColor = StyleNano.NanoGold},
CustomMinimumSize = (2, 0)
});
_humanoidProfileEditor = new HumanoidProfileEditor(preferencesManager, prototypeManager);

View File

@@ -0,0 +1,14 @@
using Content.Client.UserInterface.Stylesheets;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
namespace Content.Client.UserInterface.Controls
{
public sealed class HighDivider : Control
{
public HighDivider()
{
Children.Add(new PanelContainer {StyleClasses = {StyleBase.ClassHighDivider}});
}
}
}

View File

@@ -0,0 +1,34 @@
using System.Data;
using Content.Client.GameObjects.Components.Observer;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.IoC;
namespace Content.Client.UserInterface
{
public class GhostGui : Control
{
public Button ReturnToBody = new Button(){Text = "Return to body"};
private GhostComponent _owner;
public GhostGui(GhostComponent owner)
{
IoCManager.InjectDependencies(this);
_owner = owner;
MouseFilter = MouseFilterMode.Ignore;
ReturnToBody.OnPressed += (args) => { owner.SendReturnToBodyMessage(); };
AddChild(ReturnToBody);
Update();
}
public void Update()
{
ReturnToBody.Disabled = !_owner.CanReturnToBody;
}
}
}

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic;
using Content.Client.GameObjects.Components;
using Content.Client.UserInterface.Stylesheets;
using Content.Client.Utility;
using Robust.Client.Graphics.Drawing;
using Robust.Client.UserInterface;
@@ -57,7 +58,7 @@ namespace Content.Client.UserInterface
(_itemNameLabel = new Label
{
ClipText = true,
StyleClasses = {NanoStyle.StyleClassItemStatus}
StyleClasses = {StyleNano.StyleClassItemStatus}
})
}
}

View File

@@ -1,5 +1,6 @@
using Content.Client.Chat;
using Content.Client.Interfaces;
using Content.Client.UserInterface.Stylesheets;
using Content.Client.Utility;
using Robust.Client.Graphics.Drawing;
using Robust.Client.Interfaces.ResourceManagement;
@@ -69,7 +70,7 @@ namespace Content.Client.UserInterface
new Label
{
Text = Loc.GetString("Lobby"),
StyleClasses = {NanoStyle.StyleClassLabelHeadingBigger},
StyleClasses = {StyleNano.StyleClassLabelHeadingBigger},
/*MarginBottom = 40,
MarginLeft = 8,*/
VAlign = Label.VAlignMode.Center
@@ -78,7 +79,7 @@ namespace Content.Client.UserInterface
},
(ServerName = new Label
{
StyleClasses = {NanoStyle.StyleClassLabelHeadingBigger},
StyleClasses = {StyleNano.StyleClassLabelHeadingBigger},
/*MarginBottom = 40,
GrowHorizontal = GrowDirection.Both,*/
VAlign = Label.VAlignMode.Center,
@@ -88,7 +89,7 @@ namespace Content.Client.UserInterface
{
SizeFlagsHorizontal = SizeFlags.ShrinkEnd,
Text = Loc.GetString("Leave"),
StyleClasses = {NanoStyle.StyleClassButtonBig},
StyleClasses = {StyleNano.StyleClassButtonBig},
//GrowHorizontal = GrowDirection.Begin
})
}
@@ -100,7 +101,7 @@ namespace Content.Client.UserInterface
{
PanelOverride = new StyleBoxFlat
{
BackgroundColor = NanoStyle.NanoGold,
BackgroundColor = StyleNano.NanoGold,
ContentMarginTopOverride = 2
},
});
@@ -146,20 +147,20 @@ namespace Content.Client.UserInterface
(ObserveButton = new Button
{
Text = Loc.GetString("Observe"),
StyleClasses = {NanoStyle.StyleClassButtonBig}
StyleClasses = {StyleNano.StyleClassButtonBig}
}),
(StartTime = new Label
{
SizeFlagsHorizontal = SizeFlags.FillExpand,
Align = Label.AlignMode.Right,
FontColorOverride = Color.DarkGray,
StyleClasses = {NanoStyle.StyleClassLabelBig}
StyleClasses = {StyleNano.StyleClassLabelBig}
}),
(ReadyButton = new Button
{
ToggleMode = true,
Text = Loc.GetString("Ready Up"),
StyleClasses = {NanoStyle.StyleClassButtonBig}
StyleClasses = {StyleNano.StyleClassButtonBig}
}),
}
}
@@ -188,7 +189,7 @@ namespace Content.Client.UserInterface
hBox.AddChild(new PanelContainer
{
PanelOverride = new StyleBoxFlat {BackgroundColor = NanoStyle.NanoGold}, CustomMinimumSize = (2, 0)
PanelOverride = new StyleBoxFlat {BackgroundColor = StyleNano.NanoGold}, CustomMinimumSize = (2, 0)
});
{

View File

@@ -1,3 +1,4 @@
using Content.Client.UserInterface.Stylesheets;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Maths;
@@ -14,7 +15,7 @@ namespace Content.Client.UserInterface
{
Children = {(_label = new Label
{
StyleClasses = {NanoStyle.StyleClassLabelHeading}
StyleClasses = {StyleNano.StyleClassLabelHeading}
})}
};
AddChild(_panel);

View File

@@ -0,0 +1,101 @@
using Robust.Client.Graphics;
using Robust.Client.Interfaces.Input;
using Robust.Client.Interfaces.ResourceManagement;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Content.Client.Utility;
using Robust.Client.Player;
using System.Linq;
using System.Collections.Generic;
using static Robust.Client.UserInterface.Controls.ItemList;
using static Content.Shared.SharedGameTicker;
using System;
namespace Content.Client.UserInterface
{
public sealed class RoundEndSummaryWindow : SS14Window
{
private VBoxContainer RoundEndSummaryTab { get; }
private VBoxContainer PlayerManifestoTab { get; }
private TabContainer RoundEndWindowTabs { get; }
protected override Vector2? CustomSize => (520, 580);
public RoundEndSummaryWindow(string gm, TimeSpan roundTimeSpan, List<RoundEndPlayerInfo> info )
{
Title = Loc.GetString("Round End Summary");
//Round End Window is split into two tabs, one about the round stats
//and the other is a list of RoundEndPlayerInfo for each player.
//This tab would be a good place for things like: "x many people died.",
//"clown slipped the crew x times.", "x shots were fired this round.", etc.
//Also good for serious info.
RoundEndSummaryTab = new VBoxContainer()
{
Name = Loc.GetString("Round Information")
};
//Tab for listing unique info per player.
PlayerManifestoTab = new VBoxContainer()
{
Name = Loc.GetString("Player Manifesto")
};
RoundEndWindowTabs = new TabContainer();
RoundEndWindowTabs.AddChild(RoundEndSummaryTab);
RoundEndWindowTabs.AddChild(PlayerManifestoTab);
Contents.AddChild(RoundEndWindowTabs);
//Gamemode Name
var gamemodeLabel = new RichTextLabel();
gamemodeLabel.SetMarkup(Loc.GetString("Round of [color=white]{0}[/color] has ended.", gm));
RoundEndSummaryTab.AddChild(gamemodeLabel);
//Duration
var roundTimeLabel = new RichTextLabel();
roundTimeLabel.SetMarkup(Loc.GetString("It lasted for [color=yellow]{0} hours, {1} minutes, and {2} seconds.",
roundTimeSpan.Hours,roundTimeSpan.Minutes,roundTimeSpan.Seconds));
RoundEndSummaryTab.AddChild(roundTimeLabel);
//Initialize what will be the list of players display.
var scrollContainer = new ScrollContainer();
scrollContainer.SizeFlagsVertical = SizeFlags.FillExpand;
var innerScrollContainer = new VBoxContainer();
//Put antags on top of the list.
var manifestSortedList = info.OrderBy(p => !p.Antag);
//Create labels for each player info.
foreach (var plyinfo in manifestSortedList)
{
var playerInfoText = new RichTextLabel()
{
SizeFlagsVertical = SizeFlags.Fill
};
//TODO: On Hover display a popup detailing more play info.
//For example: their antag goals and if they completed them sucessfully.
var icNameColor = plyinfo.Antag ? "red" : "white";
playerInfoText.SetMarkup(
Loc.GetString($"[color=gray]{plyinfo.PlayerOOCName}[/color] was [color={icNameColor}]{plyinfo.PlayerICName}[/color] playing role of [color=orange]{plyinfo.Role}[/color]."));
innerScrollContainer.AddChild(playerInfoText);
}
scrollContainer.AddChild(innerScrollContainer);
//Attach the entire ScrollContainer that holds all the playerinfo.
PlayerManifestoTab.AddChild(scrollContainer);
//Finally, display the window.
OpenCentered();
MoveToFront();
}
}
}

View File

@@ -0,0 +1,12 @@
using Robust.Client.UserInterface;
namespace Content.Client.UserInterface.Stylesheets
{
public interface IStylesheetManager
{
Stylesheet SheetNano { get; }
Stylesheet SheetSpace { get; }
void Initialize();
}
}

View File

@@ -0,0 +1,47 @@
using Content.Client.Utility;
using Robust.Client.Graphics.Drawing;
using Robust.Client.Interfaces.ResourceManagement;
using Robust.Client.UserInterface;
namespace Content.Client.UserInterface.Stylesheets
{
public abstract class StyleBase
{
public const string ClassHighDivider = "HighDivider";
public const string StyleClassLabelHeading = "LabelHeading";
public const string StyleClassLabelSubText = "LabelSubText";
public abstract Stylesheet Stylesheet { get; }
protected StyleRule[] BaseRules { get; }
protected StyleBoxTexture BaseButton { get; }
protected StyleBase(IResourceCache resCache)
{
var notoSans12 = resCache.GetFont("/Nano/NotoSans/NotoSans-Regular.ttf", 12);
// Button styles.
var buttonTex = resCache.GetTexture("/Nano/button.svg.96dpi.png");
BaseButton = new StyleBoxTexture
{
Texture = buttonTex,
};
BaseButton.SetPatchMargin(StyleBox.Margin.All, 10);
BaseButton.SetPadding(StyleBox.Margin.All, 1);
BaseButton.SetContentMarginOverride(StyleBox.Margin.Vertical, 2);
BaseButton.SetContentMarginOverride(StyleBox.Margin.Horizontal, 14);
BaseRules = new[]
{
// Default font.
new StyleRule(
new SelectorElement(null, null, null, null),
new[]
{
new StyleProperty("font", notoSans12),
}),
};
}
}
}

View File

@@ -1,29 +1,29 @@
using Content.Client.GameObjects.EntitySystems;
using System.Linq;
using Content.Client.GameObjects.EntitySystems;
using Content.Client.Utility;
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.Maths;
namespace Content.Client.UserInterface
namespace Content.Client.UserInterface.Stylesheets
{
public sealed class NanoStyle
public sealed class StyleNano : StyleBase
{
public const string StyleClassSliderRed = "Red";
public const string StyleClassSliderGreen = "Green";
public const string StyleClassSliderBlue = "Blue";
public const string StyleClassLabelHeading = "LabelHeading";
public const string StyleClassLabelHeadingBigger = "LabelHeadingBigger";
public const string StyleClassLabelSubText = "LabelSubText";
public const string StyleClassLabelKeyText = "LabelKeyText";
public const string StyleClassLabelSecondaryColor = "LabelSecondaryColor";
public const string StyleClassLabelBig = "LabelBig";
public const string StyleClassButtonBig = "ButtonBig";
public static readonly Color NanoGold = Color.FromHex("#A88B5E");
public static readonly Color ButtonColorDefault = Color.FromHex("#464966");
public static readonly Color ButtonColorHovered = Color.FromHex("#575b7f");
public static readonly Color ButtonColorPressed = Color.FromHex("#3e6c45");
@@ -36,12 +36,10 @@ namespace Content.Client.UserInterface
public const string StyleClassItemStatus = "ItemStatus";
public Stylesheet Stylesheet { get; }
public override Stylesheet Stylesheet { get; }
public NanoStyle()
public StyleNano(IResourceCache resCache) : base(resCache)
{
var resCache = IoCManager.Resolve<IResourceCache>();
var notoSans8 = resCache.GetFont("/Nano/NotoSans/NotoSans-Regular.ttf", 8);
var notoSans10 = resCache.GetFont("/Nano/NotoSans/NotoSans-Regular.ttf", 10);
var notoSans12 = resCache.GetFont("/Nano/NotoSans/NotoSans-Regular.ttf", 12);
var notoSansBold12 = resCache.GetFont("/Nano/NotoSans/NotoSans-Bold.ttf", 12);
@@ -69,16 +67,10 @@ namespace Content.Client.UserInterface
var textureInvertedTriangle = resCache.GetTexture("/Nano/inverted_triangle.svg.png");
// Button styles.
var buttonTex = resCache.GetTexture("/Nano/button.svg.96dpi.png");
var buttonNormal = new StyleBoxTexture
var buttonNormal = new StyleBoxTexture(BaseButton)
{
Texture = buttonTex,
Modulate = ButtonColorDefault
};
buttonNormal.SetPatchMargin(StyleBox.Margin.All, 10);
buttonNormal.SetPadding(StyleBox.Margin.All, 1);
buttonNormal.SetContentMarginOverride(StyleBox.Margin.Vertical, 2);
buttonNormal.SetContentMarginOverride(StyleBox.Margin.Horizontal, 14);
var buttonHover = new StyleBoxTexture(buttonNormal)
{
@@ -248,16 +240,8 @@ namespace Content.Client.UserInterface
var sliderFillRed = new StyleBoxTexture(sliderFillBox) {Modulate = Color.Red};
var sliderFillBlue = new StyleBoxTexture(sliderFillBox) {Modulate = Color.Blue};
Stylesheet = new Stylesheet(new[]
Stylesheet = new Stylesheet(BaseRules.Concat(new[]
{
// Default font.
new StyleRule(
new SelectorElement(null, null, null, null),
new[]
{
new StyleProperty("font", notoSans12),
}),
// Window title.
new StyleRule(
new SelectorElement(typeof(Label), new[] {SS14Window.StyleClassWindowTitle}, null, null),
@@ -721,7 +705,12 @@ namespace Content.Client.UserInterface
{
new StyleProperty(Label.StylePropertyAlignMode, Label.AlignMode.Center),
}),
});
new StyleRule(new SelectorElement(typeof(PanelContainer), new []{ ClassHighDivider}, null, null), new []
{
new StyleProperty(PanelContainer.StylePropertyPanel, new StyleBoxFlat { BackgroundColor = NanoGold, ContentMarginBottomOverride = 2, ContentMarginLeftOverride = 2}),
})
}).ToList());
}
}
}

View File

@@ -0,0 +1,107 @@
using System.Linq;
using Content.Client.Utility;
using Robust.Client.Graphics.Drawing;
using Robust.Client.Interfaces.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Maths;
namespace Content.Client.UserInterface.Stylesheets
{
public class StyleSpace : StyleBase
{
public static readonly Color SpaceRed = Color.FromHex("#9b2236");
public static readonly Color ButtonColorDefault = Color.FromHex("#464966");
public static readonly Color ButtonColorHovered = Color.FromHex("#575b7f");
public static readonly Color ButtonColorPressed = Color.FromHex("#3e6c45");
public static readonly Color ButtonColorDisabled = Color.FromHex("#30313c");
public override Stylesheet Stylesheet { get; }
public StyleSpace(IResourceCache resCache) : base(resCache)
{
var notoSans10 = resCache.GetFont("/Nano/NotoSans/NotoSans-Regular.ttf", 10);
var notoSansBold16 = resCache.GetFont("/Nano/NotoSans/NotoSans-Bold.ttf", 16);
// Button styles.
var buttonNormal = new StyleBoxTexture(BaseButton)
{
Modulate = ButtonColorDefault
};
var buttonHover = new StyleBoxTexture(buttonNormal)
{
Modulate = ButtonColorHovered
};
var buttonPressed = new StyleBoxTexture(buttonNormal)
{
Modulate = ButtonColorPressed
};
var buttonDisabled = new StyleBoxTexture(buttonNormal)
{
Modulate = ButtonColorDisabled
};
Stylesheet = new Stylesheet(BaseRules.Concat(new StyleRule[]
{
// Big Label
new StyleRule(new SelectorElement(typeof(Label), new[] {StyleClassLabelHeading}, null, null), new[]
{
new StyleProperty(Label.StylePropertyFont, notoSansBold16),
new StyleProperty(Label.StylePropertyFontColor, SpaceRed),
}),
// Small Label
new StyleRule(new SelectorElement(typeof(Label), new[] {StyleClassLabelSubText}, null, null), new[]
{
new StyleProperty(Label.StylePropertyFont, notoSans10),
new StyleProperty(Label.StylePropertyFontColor, Color.DarkGray),
}),
new StyleRule(new SelectorElement(typeof(PanelContainer), new[] {ClassHighDivider}, null, null), new[]
{
new StyleProperty(PanelContainer.StylePropertyPanel,
new StyleBoxFlat
{
BackgroundColor = SpaceRed, ContentMarginBottomOverride = 2, ContentMarginLeftOverride = 2
}),
}),
// Regular buttons!
new StyleRule(new SelectorElement(typeof(ContainerButton), new[] { ContainerButton.StyleClassButton }, null, new[] {ContainerButton.StylePseudoClassNormal}), new[]
{
new StyleProperty(ContainerButton.StylePropertyStyleBox, buttonNormal),
}),
new StyleRule(new SelectorElement(typeof(ContainerButton), new[] { ContainerButton.StyleClassButton }, null, new[] {ContainerButton.StylePseudoClassHover}), new[]
{
new StyleProperty(ContainerButton.StylePropertyStyleBox, buttonHover),
}),
new StyleRule(new SelectorElement(typeof(ContainerButton), new[] { ContainerButton.StyleClassButton }, null, new[] {ContainerButton.StylePseudoClassPressed}), new[]
{
new StyleProperty(ContainerButton.StylePropertyStyleBox, buttonPressed),
}),
new StyleRule(new SelectorElement(typeof(ContainerButton), new[] { ContainerButton.StyleClassButton }, null, new[] {ContainerButton.StylePseudoClassDisabled}), new[]
{
new StyleProperty(ContainerButton.StylePropertyStyleBox, buttonDisabled),
}),
new StyleRule(new SelectorElement(typeof(Label), new[] { Button.StyleClassButton }, null, null), new[]
{
new StyleProperty(Label.StylePropertyAlignMode, Label.AlignMode.Center),
}),
new StyleRule(new SelectorChild(
new SelectorElement(typeof(Button), null, null, new[] {ContainerButton.StylePseudoClassDisabled}),
new SelectorElement(typeof(Label), null, null, null)),
new[]
{
new StyleProperty("font-color", Color.FromHex("#E5E5E581")),
}),
}).ToList());
}
}
}

View File

@@ -0,0 +1,26 @@
using Robust.Client.Interfaces.ResourceManagement;
using Robust.Client.Interfaces.UserInterface;
using Robust.Client.UserInterface;
using Robust.Shared.IoC;
namespace Content.Client.UserInterface.Stylesheets
{
public sealed class StylesheetManager : IStylesheetManager
{
#pragma warning disable 649
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager;
[Dependency] private readonly IResourceCache _resourceCache;
#pragma warning restore 649
public Stylesheet SheetNano { get; private set; }
public Stylesheet SheetSpace { get; private set; }
public void Initialize()
{
SheetNano = new StyleNano(_resourceCache).Stylesheet;
SheetSpace = new StyleSpace(_resourceCache).Stylesheet;
_userInterfaceManager.Stylesheet = SheetNano;
}
}
}

View File

@@ -4,6 +4,7 @@ using Content.Server.GameTicking;
using Content.Server.Interfaces.GameTicking;
using Content.Shared;
using Robust.Server.Interfaces.Player;
using Robust.Shared.Map;
using Robust.Shared.Timing;
namespace Content.IntegrationTests
@@ -54,6 +55,10 @@ namespace Content.IntegrationTests
{
}
public GridCoordinates GetLateJoinSpawnPoint() => GridCoordinates.InvalidGrid;
public GridCoordinates GetJobSpawnPoint(string jobId) => GridCoordinates.InvalidGrid;
public GridCoordinates GetObserverSpawnPoint() => GridCoordinates.InvalidGrid;
public T AddGameRule<T>() where T : GameRule, new()
{
return new T();

View File

@@ -0,0 +1,81 @@
using NUnit.Framework;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Robust.Shared.Log;
using Robust.Server.Interfaces.Maps;
using Robust.Server.Interfaces.Timing;
namespace Content.IntegrationTests.Tests
{
[TestFixture]
[TestOf(typeof(Robust.Shared.GameObjects.Entity))]
public class EntityTest : ContentIntegrationTest
{
[Test]
public async Task Test()
{
var server = StartServerDummyTicker();
await server.WaitIdleAsync();
var mapMan = server.ResolveDependency<IMapManager>();
var entityMan = server.ResolveDependency<IEntityManager>();
var prototypeMan = server.ResolveDependency<IPrototypeManager>();
var mapLoader = server.ResolveDependency<IMapLoader>();
var pauseMan = server.ResolveDependency<IPauseManager>();
var prototypes = new List<EntityPrototype>();
IMapGrid grid = default;
IEntity testEntity = null;
//Build up test environment
server.Post(() =>
{
var mapId = mapMan.CreateMap();
pauseMan.AddUninitializedMap(mapId);
grid = mapLoader.LoadBlueprint(mapId, "Maps/stationstation.yml");
});
server.Assert(() =>
{
var testLocation = new GridCoordinates(new Robust.Shared.Maths.Vector2(0, 0), grid);
//Generate list of non-abstract prototypes to test
foreach (var prototype in prototypeMan.EnumeratePrototypes<EntityPrototype>())
{
if (prototype.Abstract)
{
continue;
}
prototypes.Add(prototype);
}
//Iterate list of prototypes to spawn
foreach (var prototype in prototypes)
{
try
{
Logger.LogS(LogLevel.Debug, "EntityTest", "Testing: " + prototype.Name);
testEntity = entityMan.SpawnEntity(prototype.ID, testLocation);
server.RunTicks(2);
Assert.That(testEntity.Initialized);
entityMan.DeleteEntity(testEntity.Uid);
}
//Fail any exceptions thrown on spawn
catch (Exception e)
{
Logger.LogS(LogLevel.Error, "EntityTest", "Entity '" + testEntity.Name + "' threw: " + e.Message);
Assert.Fail();
}
}
});
await server.WaitIdleAsync();
}
}
}

View File

@@ -1,9 +1,12 @@
using Content.Server.Players;
using System.Timers;
using Content.Server.GameObjects.Components.Observer;
using Content.Server.Players;
using Robust.Server.Interfaces.Console;
using Robust.Server.Interfaces.Player;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Timer = Robust.Shared.Timers.Timer;
namespace Content.Server.Administration
{
@@ -30,10 +33,14 @@ namespace Content.Server.Administration
}
else
{
var canReturn = mind.CurrentEntity != null && !mind.CurrentEntity.HasComponent<GhostComponent>();
var entityManager = IoCManager.Resolve<IEntityManager>();
var ghost = entityManager.SpawnEntity("AdminObserver", player.AttachedEntity.Transform.GridPosition);
mind.Visit(ghost);
if(canReturn)
mind.Visit(ghost);
else
mind.TransferTo(ghost);
ghost.GetComponent<GhostComponent>().CanReturnToBody = canReturn;
}
}
}

View File

@@ -1,4 +1,5 @@
using Content.Server.GameObjects.Components.Mobs;
using Content.Server.GameObjects.Components.Observer;
using Content.Server.Mobs;
using Content.Server.Players;
using Robust.Server.Interfaces.Console;
@@ -55,9 +56,14 @@ namespace Content.Server.Administration
return;
}
var oldEntity = mind.CurrentEntity;
mindComponent.Mind?.TransferTo(null);
mind.TransferTo(target);
if(oldEntity.HasComponent<GhostComponent>())
oldEntity.Delete();
}
}
}

View File

@@ -1,4 +1,6 @@
using Content.Server.Interfaces.Chat;
using Content.Server.GameObjects.Components.Observer;
using Content.Server.Interfaces.Chat;
using Content.Server.Observer;
using Robust.Server.Interfaces.Console;
using Robust.Server.Interfaces.Player;
using Robust.Shared.Enums;
@@ -24,7 +26,10 @@ namespace Content.Server.Chat
var message = string.Join(" ", args);
chat.EntitySay(player.AttachedEntity, message);
if (player.AttachedEntity.HasComponent<GhostComponent>())
chat.SendDeadChat(player, message);
else
chat.EntitySay(player.AttachedEntity, message);
}
}

View File

@@ -1,12 +1,17 @@
using System.Linq;
using Content.Server.GameObjects.Components.Observer;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces;
using Content.Server.Interfaces.Chat;
using Content.Server.Observer;
using Content.Server.Players;
using Content.Shared.Chat;
using Robust.Server.Interfaces.Player;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Network;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
namespace Content.Server.Chat
{
@@ -20,6 +25,7 @@ namespace Content.Server.Chat
#pragma warning disable 649
[Dependency] private readonly IServerNetManager _netManager;
[Dependency] private readonly IPlayerManager _playerManager;
[Dependency] private readonly ILocalizationManager _localizationManager;
[Dependency] private readonly IMoMMILink _mommiLink;
#pragma warning restore 649
@@ -93,6 +99,18 @@ namespace Content.Server.Chat
_mommiLink.SendOOCMessage(player.SessionId.ToString(), message);
}
public void SendDeadChat(IPlayerSession player, string message)
{
var clients = _playerManager.GetPlayersBy(x => x.AttachedEntity != null && x.AttachedEntity.HasComponent<GhostComponent>()).Select(p => p.ConnectedClient);;
var msg = _netManager.CreateNetMessage<MsgChatMessage>();
msg.Channel = ChatChannel.Dead;
msg.Message = message;
msg.MessageWrap = $"{_localizationManager.GetString("DEAD")}: {player.AttachedEntity.Name}: {{0}}";
msg.SenderEntity = player.AttachedEntityUid.GetValueOrDefault();
_netManager.ServerSendToMany(msg, clients.ToList());
}
public void SendHookOOC(string sender, string message)
{
var msg = _netManager.CreateNetMessage<MsgChatMessage>();

View File

@@ -1,5 +1,6 @@
using System;
using Content.Server.GameObjects.Components.Nutrition;
using Content.Shared.Chemistry;
using Content.Shared.Interfaces.Chemistry;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Serialization;
@@ -14,8 +15,8 @@ namespace Content.Server.Chemistry.Metabolism
class DefaultDrink : IMetabolizable
{
//Rate of metabolism in units / second
private int _metabolismRate;
public int MetabolismRate => _metabolismRate;
private ReagentUnit _metabolismRate;
public ReagentUnit MetabolismRate => _metabolismRate;
//How much thirst is satiated when 1u of the reagent is metabolized
private float _hydrationFactor;
@@ -23,16 +24,16 @@ namespace Content.Server.Chemistry.Metabolism
void IExposeData.ExposeData(ObjectSerializer serializer)
{
serializer.DataField(ref _metabolismRate, "rate", 1);
serializer.DataField(ref _metabolismRate, "rate", ReagentUnit.New(1));
serializer.DataField(ref _hydrationFactor, "nutrimentFactor", 30.0f);
}
//Remove reagent at set rate, satiate thirst if a ThirstComponent can be found
int IMetabolizable.Metabolize(IEntity solutionEntity, string reagentId, float tickTime)
ReagentUnit IMetabolizable.Metabolize(IEntity solutionEntity, string reagentId, float tickTime)
{
int metabolismAmount = (int)Math.Round(MetabolismRate * tickTime);
var metabolismAmount = MetabolismRate * tickTime;
if (solutionEntity.TryGetComponent(out ThirstComponent thirst))
thirst.UpdateThirst(metabolismAmount * HydrationFactor);
thirst.UpdateThirst(metabolismAmount.Float() * HydrationFactor);
//Return amount of reagent to be removed, remove reagent regardless of ThirstComponent presence
return metabolismAmount;

View File

@@ -1,8 +1,9 @@
using System;
using Content.Server.GameObjects.Components.Nutrition;
using Content.Server.GameObjects.Components.Nutrition;
using Content.Shared.Chemistry;
using Content.Shared.Interfaces.Chemistry;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.IoC;
using Robust.Shared.Serialization;
namespace Content.Server.Chemistry.Metabolism
@@ -14,8 +15,8 @@ namespace Content.Server.Chemistry.Metabolism
class DefaultFood : IMetabolizable
{
//Rate of metabolism in units / second
private int _metabolismRate;
public int MetabolismRate => _metabolismRate;
private ReagentUnit _metabolismRate;
public ReagentUnit MetabolismRate => _metabolismRate;
//How much hunger is satiated when 1u of the reagent is metabolized
private float _nutritionFactor;
@@ -23,16 +24,16 @@ namespace Content.Server.Chemistry.Metabolism
void IExposeData.ExposeData(ObjectSerializer serializer)
{
serializer.DataField(ref _metabolismRate, "rate", 1);
serializer.DataField(ref _metabolismRate, "rate", ReagentUnit.New(1M));
serializer.DataField(ref _nutritionFactor, "nutrimentFactor", 30.0f);
}
//Remove reagent at set rate, satiate hunger if a HungerComponent can be found
int IMetabolizable.Metabolize(IEntity solutionEntity, string reagentId, float tickTime)
ReagentUnit IMetabolizable.Metabolize(IEntity solutionEntity, string reagentId, float tickTime)
{
int metabolismAmount = (int)Math.Round(MetabolismRate * tickTime);
var metabolismAmount = MetabolismRate * tickTime;
if (solutionEntity.TryGetComponent(out HungerComponent hunger))
hunger.UpdateFood(metabolismAmount * NutritionFactor);
hunger.UpdateFood(metabolismAmount.Float() * NutritionFactor);
//Return amount of reagent to be removed, remove reagent regardless of HungerComponent presence
return metabolismAmount;

View File

@@ -35,9 +35,9 @@ namespace Content.Server.Chemistry.ReactionEffects
serializer.DataField(ref _maxScale, "maxScale", 1);
}
public void React(IEntity solutionEntity, int intensity)
public void React(IEntity solutionEntity, decimal intensity)
{
float floatIntensity = intensity; //Use float to avoid truncation in scaling
float floatIntensity = (float)intensity;
if (solutionEntity == null)
return;
if(!solutionEntity.TryGetComponent(out SolutionComponent solution))

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using Content.Shared.Chemistry;
using Content.Shared.Interfaces;
using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.Prototypes;
@@ -16,7 +17,7 @@ namespace Content.Server.Chemistry
private string _id;
private string _name;
private Dictionary<string, ReactantPrototype> _reactants;
private Dictionary<string, uint> _products;
private Dictionary<string, ReagentUnit> _products;
private List<IReactionEffect> _effects;
public string ID => _id;
@@ -28,7 +29,7 @@ namespace Content.Server.Chemistry
/// <summary>
/// Reagents created when the reaction occurs.
/// </summary>
public IReadOnlyDictionary<string, uint> Products => _products;
public IReadOnlyDictionary<string, ReagentUnit> Products => _products;
/// <summary>
/// Effects to be triggered when the reaction occurs.
/// </summary>
@@ -41,7 +42,7 @@ namespace Content.Server.Chemistry
serializer.DataField(ref _id, "id", string.Empty);
serializer.DataField(ref _name, "name", string.Empty);
serializer.DataField(ref _reactants, "reactants", new Dictionary<string, ReactantPrototype>());
serializer.DataField(ref _products, "products", new Dictionary<string, uint>());
serializer.DataField(ref _products, "products", new Dictionary<string, ReagentUnit>());
serializer.DataField(ref _effects, "effects", new List<IReactionEffect>());
}
}
@@ -51,13 +52,13 @@ namespace Content.Server.Chemistry
/// </summary>
public class ReactantPrototype : IExposeData
{
private int _amount;
private ReagentUnit _amount;
private bool _catalyst;
/// <summary>
/// Minimum amount of the reactant needed for the reaction to occur.
/// </summary>
public int Amount => _amount;
public ReagentUnit Amount => _amount;
/// <summary>
/// Whether or not the reactant is a catalyst. Catalysts aren't removed when a reaction occurs.
/// </summary>
@@ -65,7 +66,7 @@ namespace Content.Server.Chemistry
public void ExposeData(ObjectSerializer serializer)
{
serializer.DataField(ref _amount, "amount", 1);
serializer.DataField(ref _amount, "amount", ReagentUnit.New(1));
serializer.DataField(ref _catalyst, "catalyst", false);
}
}

View File

@@ -1,6 +1,7 @@
using Content.Server.Cargo;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.GameObjects.Components.Cargo;
using Content.Server.GameObjects.Components.Power;
using Content.Shared.Prototypes.Cargo;
using Robust.Server.GameObjects.Components.UserInterface;
using Robust.Server.Interfaces.GameObjects;
@@ -40,6 +41,9 @@ namespace Content.Server.GameObjects.Components.Cargo
private bool _requestOnly = false;
private PowerDeviceComponent _powerDevice;
private bool Powered => _powerDevice.Powered;
public override void Initialize()
{
base.Initialize();
@@ -47,6 +51,7 @@ namespace Content.Server.GameObjects.Components.Cargo
Orders = Owner.GetComponent<CargoOrderDatabaseComponent>();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>().GetBoundUserInterface(CargoConsoleUiKey.Key);
_userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
_powerDevice = Owner.GetComponent<PowerDeviceComponent>();
_galacticBankManager.AddComponent(this);
BankId = 0;
}
@@ -66,6 +71,8 @@ namespace Content.Server.GameObjects.Components.Cargo
var message = serverMsg.Message;
if (!Orders.ConnectedToDatabase)
return;
if (!Powered)
return;
switch (message)
{
case CargoConsoleAddOrderMessage msg:
@@ -119,6 +126,8 @@ namespace Content.Server.GameObjects.Components.Cargo
{
return;
}
if (!Powered)
return;
_userInterface.Open(actor.playerSession);
}

View File

@@ -37,13 +37,13 @@ namespace Content.Server.GameObjects.Components.Chemistry
/// attempt to inject it's entire contents upon use.
/// </summary>
[ViewVariables]
private int _transferAmount;
private ReagentUnit _transferAmount;
/// <summary>
/// Initial storage volume of the injector
/// </summary>
[ViewVariables]
private int _initialMaxVolume;
private ReagentUnit _initialMaxVolume;
/// <summary>
/// The state of the injector. Determines it's attack behavior. Containers must have the
@@ -62,22 +62,14 @@ namespace Content.Server.GameObjects.Components.Chemistry
{
base.ExposeData(serializer);
serializer.DataField(ref _injectOnly, "injectOnly", false);
serializer.DataField(ref _initialMaxVolume, "initialMaxVolume", 15);
serializer.DataField(ref _transferAmount, "transferAmount", 5);
serializer.DataField(ref _initialMaxVolume, "initialMaxVolume", ReagentUnit.New(15));
serializer.DataField(ref _transferAmount, "transferAmount", ReagentUnit.New(5));
}
public override void Initialize()
protected override void Startup()
{
base.Initialize();
//Create and setup internal storage
_internalContents = new SolutionComponent();
_internalContents.InitializeFromPrototype();
_internalContents.Init();
_internalContents.MaxVolume = _initialMaxVolume;
_internalContents.Owner = Owner; //Manually set owner to avoid crash when VV'ing this
base.Startup();
_internalContents = Owner.GetComponent<SolutionComponent>();
_internalContents.Capabilities |= SolutionCaps.Injector;
//Set _toggleState based on prototype
_toggleState = _injectOnly ? InjectorToggleMode.Inject : InjectorToggleMode.Draw;
}
@@ -165,7 +157,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
}
//Get transfer amount. May be smaller than _transferAmount if not enough room
int realTransferAmount = Math.Min(_transferAmount, targetBloodstream.EmptyVolume);
var realTransferAmount = ReagentUnit.Min(_transferAmount, targetBloodstream.EmptyVolume);
if (realTransferAmount <= 0)
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, user,
@@ -193,7 +185,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
}
//Get transfer amount. May be smaller than _transferAmount if not enough room
int realTransferAmount = Math.Min(_transferAmount, targetSolution.EmptyVolume);
var realTransferAmount = ReagentUnit.Min(_transferAmount, targetSolution.EmptyVolume);
if (realTransferAmount <= 0)
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, user,
@@ -221,7 +213,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
}
//Get transfer amount. May be smaller than _transferAmount if not enough room
int realTransferAmount = Math.Min(_transferAmount, targetSolution.CurrentVolume);
var realTransferAmount = ReagentUnit.Min(_transferAmount, targetSolution.CurrentVolume);
if (realTransferAmount <= 0)
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, user,

View File

@@ -4,6 +4,7 @@ using System.Text;
using Content.Server.GameObjects.Components.Nutrition;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces;
using Content.Shared.Chemistry;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
@@ -28,13 +29,13 @@ namespace Content.Server.GameObjects.Components.Chemistry
public override string Name => "Pourable";
private int _transferAmount;
private ReagentUnit _transferAmount;
/// <summary>
/// The amount of solution to be transferred from this solution when clicking on other solutions with it.
/// </summary>
[ViewVariables]
public int TransferAmount
public ReagentUnit TransferAmount
{
get => _transferAmount;
set => _transferAmount = value;
@@ -43,7 +44,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _transferAmount, "transferAmount", 5);
serializer.DataField(ref _transferAmount, "transferAmount", ReagentUnit.New(5.0M));
}
/// <summary>
@@ -69,7 +70,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
return false;
//Get transfer amount. May be smaller than _transferAmount if not enough room
int realTransferAmount = Math.Min(attackPourable.TransferAmount, targetSolution.EmptyVolume);
var realTransferAmount = ReagentUnit.Min(attackPourable.TransferAmount, targetSolution.EmptyVolume);
if (realTransferAmount <= 0) //Special message if container is full
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, eventArgs.User,

View File

@@ -2,6 +2,7 @@
using System.Linq;
using Content.Server.GameObjects.Components.Sound;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.GameObjects.Components.Power;
using Content.Server.Interfaces;
using Content.Server.Interfaces.GameObjects;
using Content.Shared.Chemistry;
@@ -29,7 +30,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
[RegisterComponent]
[ComponentReference(typeof(IActivate))]
[ComponentReference(typeof(IAttackBy))]
public class ReagentDispenserComponent : SharedReagentDispenserComponent, IActivate, IAttackBy
public class ReagentDispenserComponent : SharedReagentDispenserComponent, IActivate, IAttackBy, ISolutionChange
{
#pragma warning disable 649
[Dependency] private readonly IServerNotifyManager _notifyManager;
@@ -41,11 +42,16 @@ namespace Content.Server.GameObjects.Components.Chemistry
[ViewVariables] private string _packPrototypeId;
[ViewVariables] private bool HasBeaker => _beakerContainer.ContainedEntity != null;
[ViewVariables] private int DispenseAmount = 10;
[ViewVariables] private ReagentUnit _dispenseAmount = ReagentUnit.New(10);
[ViewVariables]
private SolutionComponent Solution => _beakerContainer.ContainedEntity.GetComponent<SolutionComponent>();
///implementing PowerDeviceComponent
private PowerDeviceComponent _powerDevice;
private bool Powered => _powerDevice.Powered;
/// <summary>
/// Shows the serializer how to save/load this components yaml prototype.
/// </summary>
@@ -70,6 +76,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
_beakerContainer =
ContainerManagerComponent.Ensure<ContainerSlot>($"{Name}-reagentContainerContainer", Owner);
_powerDevice = Owner.GetComponent<PowerDeviceComponent>();
InitializeFromPrototype();
UpdateUserInterface();
@@ -115,22 +122,22 @@ namespace Content.Server.GameObjects.Components.Chemistry
TryClear();
break;
case UiButton.SetDispenseAmount1:
DispenseAmount = 1;
_dispenseAmount = ReagentUnit.New(1);
break;
case UiButton.SetDispenseAmount5:
DispenseAmount = 5;
_dispenseAmount = ReagentUnit.New(5);
break;
case UiButton.SetDispenseAmount10:
DispenseAmount = 10;
_dispenseAmount = ReagentUnit.New(10);
break;
case UiButton.SetDispenseAmount25:
DispenseAmount = 25;
_dispenseAmount = ReagentUnit.New(25);
break;
case UiButton.SetDispenseAmount50:
DispenseAmount = 50;
_dispenseAmount = ReagentUnit.New(50);
break;
case UiButton.SetDispenseAmount100:
DispenseAmount = 100;
_dispenseAmount = ReagentUnit.New(100);
break;
case UiButton.Dispense:
if (HasBeaker)
@@ -154,11 +161,14 @@ namespace Content.Server.GameObjects.Components.Chemistry
private bool PlayerCanUseDispenser(IEntity playerEntity)
{
//Need player entity to check if they are still able to use the dispenser
if (playerEntity == null)
if (playerEntity == null)
return false;
//Check if player can interact in their current state
if (!ActionBlockerSystem.CanInteract(playerEntity) || !ActionBlockerSystem.CanUse(playerEntity))
return false;
//Check if device is powered
if (!Powered)
return false;
return true;
}
@@ -172,13 +182,13 @@ namespace Content.Server.GameObjects.Components.Chemistry
var beaker = _beakerContainer.ContainedEntity;
if (beaker == null)
{
return new ReagentDispenserBoundUserInterfaceState(false, 0, 0,
"", Inventory, Owner.Name, null, DispenseAmount);
return new ReagentDispenserBoundUserInterfaceState(false, ReagentUnit.New(0), ReagentUnit.New(0),
"", Inventory, Owner.Name, null, _dispenseAmount);
}
var solution = beaker.GetComponent<SolutionComponent>();
return new ReagentDispenserBoundUserInterfaceState(true, solution.CurrentVolume, solution.MaxVolume,
beaker.Name, Inventory, Owner.Name, solution.ReagentList.ToList(), DispenseAmount);
beaker.Name, Inventory, Owner.Name, solution.ReagentList.ToList(), _dispenseAmount);
}
private void UpdateUserInterface()
@@ -197,7 +207,6 @@ namespace Content.Server.GameObjects.Components.Chemistry
return;
var beaker = _beakerContainer.ContainedEntity;
Solution.SolutionChanged -= HandleSolutionChangedEvent;
_beakerContainer.Remove(_beakerContainer.ContainedEntity);
UpdateUserInterface();
@@ -228,7 +237,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
if (!HasBeaker) return;
var solution = _beakerContainer.ContainedEntity.GetComponent<SolutionComponent>();
solution.TryAddReagent(Inventory[dispenseIndex].ID, DispenseAmount, out _);
solution.TryAddReagent(Inventory[dispenseIndex].ID, _dispenseAmount, out _);
UpdateUserInterface();
}
@@ -251,6 +260,9 @@ namespace Content.Server.GameObjects.Components.Chemistry
return;
}
if (!Powered)
return;
var activeHandEntity = hands.GetActiveHand?.Owner;
if (activeHandEntity == null)
{
@@ -291,7 +303,6 @@ namespace Content.Server.GameObjects.Components.Chemistry
else
{
_beakerContainer.Insert(activeHandEntity);
Solution.SolutionChanged += HandleSolutionChangedEvent;
UpdateUserInterface();
}
}
@@ -304,10 +315,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
return true;
}
private void HandleSolutionChangedEvent()
{
UpdateUserInterface();
}
void ISolutionChange.SolutionChanged(SolutionChangeEventArgs eventArgs) => UpdateUserInterface();
private void ClickSound()
{
@@ -316,5 +324,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
sound.Play("/Audio/machines/machine_switch.ogg", AudioParams.Default.WithVolume(-2f));
}
}
}
}

View File

@@ -1,27 +1,30 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
using Content.Server.Chemistry;
using Content.Server.GameObjects.Components.Nutrition;
using Content.Server.Chemistry;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces;
using Content.Shared.Chemistry;
using Content.Shared.GameObjects;
using Content.Shared.GameObjects.Components.Chemistry;
using Content.Shared.Utility;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using System.Collections.Generic;
using System.Linq;
namespace Content.Server.GameObjects.Components.Chemistry
{
/// <summary>
/// Shared ECS component that manages a liquid solution of reagents.
/// ECS component that manages a liquid solution of reagents.
/// </summary>
[RegisterComponent]
internal class SolutionComponent : Shared.GameObjects.Components.Chemistry.SolutionComponent, IExamine
internal class SolutionComponent : SharedSolutionComponent, IExamine
{
#pragma warning disable 649
[Dependency] private readonly IPrototypeManager _prototypeManager;
@@ -31,27 +34,180 @@ namespace Content.Server.GameObjects.Components.Chemistry
private IEnumerable<ReactionPrototype> _reactions;
private AudioSystem _audioSystem;
private ChemistrySystem _chemistrySystem;
private SpriteComponent _spriteComponent;
private Solution _containedSolution = new Solution();
private ReagentUnit _maxVolume;
private SolutionCaps _capabilities;
private string _fillInitState;
private int _fillInitSteps;
private string _fillPathString = "Objects/Chemistry/fillings.rsi";
private ResourcePath _fillPath;
private SpriteSpecifier _fillSprite;
/// <summary>
/// The maximum volume of the container.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public ReagentUnit MaxVolume
{
get => _maxVolume;
set => _maxVolume = value; // Note that the contents won't spill out if the capacity is reduced.
}
/// <summary>
/// The total volume of all the of the reagents in the container.
/// </summary>
[ViewVariables]
public ReagentUnit CurrentVolume => _containedSolution.TotalVolume;
/// <summary>
/// The volume without reagents remaining in the container.
/// </summary>
[ViewVariables]
public ReagentUnit EmptyVolume => MaxVolume - CurrentVolume;
/// <summary>
/// The current blended color of all the reagents in the container.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public Color SubstanceColor { get; private set; }
/// <summary>
/// The current capabilities of this container (is the top open to pour? can I inject it into another object?).
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public SolutionCaps Capabilities
{
get => _capabilities;
set => _capabilities = value;
}
[ViewVariables]
public Solution Solution
{
get => _containedSolution;
set => _containedSolution = value;
}
public IReadOnlyList<Solution.ReagentQuantity> ReagentList => _containedSolution.Contents;
/// <summary>
/// Shortcut for Capabilities PourIn flag to avoid binary operators.
/// </summary>
public bool CanPourIn => (Capabilities & SolutionCaps.PourIn) != 0;
/// <summary>
/// Shortcut for Capabilities PourOut flag to avoid binary operators.
/// </summary>
public bool CanPourOut => (Capabilities & SolutionCaps.PourOut) != 0;
/// <summary>
/// Shortcut for Capabilities Injectable flag
/// </summary>
public bool Injectable => (Capabilities & SolutionCaps.Injectable) != 0;
/// <summary>
/// Shortcut for Capabilities Injector flag
/// </summary>
public bool Injector => (Capabilities & SolutionCaps.Injector) != 0;
public bool NoExamine => (Capabilities & SolutionCaps.NoExamine) != 0;
/// <inheritdoc />
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _maxVolume, "maxVol", ReagentUnit.New(0));
serializer.DataField(ref _containedSolution, "contents", _containedSolution);
serializer.DataField(ref _capabilities, "caps", SolutionCaps.None);
serializer.DataField(ref _fillInitState, "fillingState", "");
serializer.DataField(ref _fillInitSteps, "fillingSteps", 7);
}
public override void Initialize()
{
base.Initialize();
_audioSystem = _entitySystemManager.GetEntitySystem<AudioSystem>();
_chemistrySystem = _entitySystemManager.GetEntitySystem<ChemistrySystem>();
_reactions = _prototypeManager.EnumeratePrototypes<ReactionPrototype>();
}
protected override void Startup()
{
base.Startup();
Init();
RecalculateColor();
if (!string.IsNullOrEmpty(_fillInitState))
{
_spriteComponent = Owner.GetComponent<SpriteComponent>();
_fillPath = new ResourcePath(_fillPathString);
_fillSprite = new SpriteSpecifier.Rsi(_fillPath, _fillInitState + (_fillInitSteps - 1));
_spriteComponent.AddLayerWithSprite(_fillSprite);
UpdateFillIcon();
}
}
public void Init()
public void RemoveAllSolution()
{
_reactions = _prototypeManager.EnumeratePrototypes<ReactionPrototype>();
_audioSystem = _entitySystemManager.GetEntitySystem<AudioSystem>();
_containedSolution.RemoveAllSolution();
OnSolutionChanged(false);
}
public bool TryRemoveReagent(string reagentId, ReagentUnit quantity)
{
if (!ContainsReagent(reagentId, out var currentQuantity)) return false;
_containedSolution.RemoveReagent(reagentId, quantity);
OnSolutionChanged(false);
return true;
}
/// <summary>
/// Initializes the SolutionComponent if it doesn't have an owner
/// Attempt to remove the specified quantity from this solution
/// </summary>
public void InitializeFromPrototype()
/// <param name="quantity">Quantity of this solution to remove</param>
/// <returns>Whether or not the solution was successfully removed</returns>
public bool TryRemoveSolution(ReagentUnit quantity)
{
// Because Initialize needs an Owner, Startup isn't called, etc.
IoCManager.InjectDependencies(this);
_reactions = _prototypeManager.EnumeratePrototypes<ReactionPrototype>();
if (CurrentVolume == 0)
return false;
_containedSolution.RemoveSolution(quantity);
OnSolutionChanged(false);
return true;
}
public Solution SplitSolution(ReagentUnit quantity)
{
var solutionSplit = _containedSolution.SplitSolution(quantity);
OnSolutionChanged(false);
return solutionSplit;
}
protected void RecalculateColor()
{
if (_containedSolution.TotalVolume == 0)
{
SubstanceColor = Color.Transparent;
return;
}
Color mixColor = default;
var runningTotalQuantity = ReagentUnit.New(0);
foreach (var reagent in _containedSolution)
{
runningTotalQuantity += reagent.Quantity;
if(!_prototypeManager.TryIndex(reagent.ReagentId, out ReagentPrototype proto))
continue;
if (mixColor == default)
mixColor = proto.SubstanceColor;
mixColor = Color.InterpolateBetween(mixColor, proto.SubstanceColor,
(1 / runningTotalQuantity.Float()) * reagent.Quantity.Float());
}
SubstanceColor = mixColor;
}
/// <summary>
@@ -105,8 +261,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
if ((handSolutionComp.Capabilities & SolutionCaps.PourOut) == 0 || (component.Capabilities & SolutionCaps.PourIn) == 0)
return;
var transferQuantity = Math.Min(component.MaxVolume - component.CurrentVolume, handSolutionComp.CurrentVolume);
transferQuantity = Math.Min(transferQuantity, 10);
var transferQuantity = ReagentUnit.Min(component.MaxVolume - component.CurrentVolume, handSolutionComp.CurrentVolume, ReagentUnit.New(10));
// nothing to transfer
if (transferQuantity <= 0)
@@ -120,7 +275,16 @@ namespace Content.Server.GameObjects.Components.Chemistry
void IExamine.Examine(FormattedMessage message)
{
if (NoExamine)
{
return;
}
message.AddText(_loc.GetString("Contains:\n"));
if (ReagentList.Count == 0)
{
message.AddText("Nothing.\n");
}
foreach (var reagent in ReagentList)
{
if (_prototypeManager.TryIndex(reagent.ReagentId, out ReagentPrototype proto))
@@ -185,8 +349,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
if ((handSolutionComp.Capabilities & SolutionCaps.PourIn) == 0 || (component.Capabilities & SolutionCaps.PourOut) == 0)
return;
var transferQuantity = Math.Min(handSolutionComp.MaxVolume - handSolutionComp.CurrentVolume, component.CurrentVolume);
transferQuantity = Math.Min(transferQuantity, 10);
var transferQuantity = ReagentUnit.Min(handSolutionComp.MaxVolume - handSolutionComp.CurrentVolume, component.CurrentVolume, ReagentUnit.New(10));
// pulling from an empty container, pointless to continue
if (transferQuantity <= 0)
@@ -202,10 +365,11 @@ namespace Content.Server.GameObjects.Components.Chemistry
bool checkForNewReaction = false;
while (true)
{
//TODO: make a hashmap at startup and then look up reagents in the contents for a reaction
//Check the solution for every reaction
foreach (var reaction in _reactions)
{
if (SolutionValidReaction(reaction, out int unitReactions))
if (SolutionValidReaction(reaction, out var unitReactions))
{
PerformReaction(reaction, unitReactions);
checkForNewReaction = true;
@@ -223,11 +387,12 @@ namespace Content.Server.GameObjects.Components.Chemistry
}
}
public bool TryAddReagent(string reagentId, int quantity, out int acceptedQuantity, bool skipReactionCheck = false, bool skipColor = false)
public bool TryAddReagent(string reagentId, ReagentUnit quantity, out ReagentUnit acceptedQuantity, bool skipReactionCheck = false, bool skipColor = false)
{
if (quantity > _maxVolume - _containedSolution.TotalVolume)
var toAcceptQuantity = MaxVolume - _containedSolution.TotalVolume;
if (quantity > toAcceptQuantity)
{
acceptedQuantity = _maxVolume - _containedSolution.TotalVolume;
acceptedQuantity = toAcceptQuantity;
if (acceptedQuantity == 0) return false;
}
else
@@ -241,13 +406,13 @@ namespace Content.Server.GameObjects.Components.Chemistry
}
if(!skipReactionCheck)
CheckForReaction();
OnSolutionChanged();
OnSolutionChanged(skipColor);
return true;
}
public bool TryAddSolution(Solution solution, bool skipReactionCheck = false, bool skipColor = false)
{
if (solution.TotalVolume > (_maxVolume - _containedSolution.TotalVolume))
if (solution.TotalVolume > (MaxVolume - _containedSolution.TotalVolume))
return false;
_containedSolution.AddSolution(solution);
@@ -256,7 +421,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
}
if(!skipReactionCheck)
CheckForReaction();
OnSolutionChanged();
OnSolutionChanged(skipColor);
return true;
}
@@ -267,16 +432,16 @@ namespace Content.Server.GameObjects.Components.Chemistry
/// <param name="reaction">The reaction whose reactants will be checked for in the solution.</param>
/// <param name="unitReactions">The number of times the reaction can occur with the given solution.</param>
/// <returns></returns>
private bool SolutionValidReaction(ReactionPrototype reaction, out int unitReactions)
private bool SolutionValidReaction(ReactionPrototype reaction, out ReagentUnit unitReactions)
{
unitReactions = int.MaxValue; //Set to some impossibly large number initially
unitReactions = ReagentUnit.MaxValue; //Set to some impossibly large number initially
foreach (var reactant in reaction.Reactants)
{
if (!ContainsReagent(reactant.Key, out int reagentQuantity))
if (!ContainsReagent(reactant.Key, out ReagentUnit reagentQuantity))
{
return false;
}
int currentUnitReactions = reagentQuantity / reactant.Value.Amount;
var currentUnitReactions = reagentQuantity / reactant.Value.Amount;
if (currentUnitReactions < unitReactions)
{
unitReactions = currentUnitReactions;
@@ -299,30 +464,88 @@ namespace Content.Server.GameObjects.Components.Chemistry
/// <param name="solution">Solution to be reacted.</param>
/// <param name="reaction">Reaction to occur.</param>
/// <param name="unitReactions">The number of times to cause this reaction.</param>
private void PerformReaction(ReactionPrototype reaction, int unitReactions)
private void PerformReaction(ReactionPrototype reaction, ReagentUnit unitReactions)
{
//Remove non-catalysts
foreach (var reactant in reaction.Reactants)
{
if (!reactant.Value.Catalyst)
{
int amountToRemove = unitReactions * reactant.Value.Amount;
var amountToRemove = unitReactions * reactant.Value.Amount;
TryRemoveReagent(reactant.Key, amountToRemove);
}
}
//Add products
foreach (var product in reaction.Products)
{
TryAddReagent(product.Key, (int)(unitReactions * product.Value), out int acceptedQuantity, true);
TryAddReagent(product.Key, product.Value * unitReactions, out var acceptedQuantity, true);
}
//Trigger reaction effects
foreach (var effect in reaction.Effects)
{
effect.React(Owner, unitReactions);
effect.React(Owner, unitReactions.Decimal());
}
//Play reaction sound client-side
_audioSystem.Play("/Audio/effects/chemistry/bubbles.ogg", Owner.Transform.GridPosition);
}
/// <summary>
/// Check if the solution contains the specified reagent.
/// </summary>
/// <param name="reagentId">The reagent to check for.</param>
/// <param name="quantity">Output the quantity of the reagent if it is contained, 0 if it isn't.</param>
/// <returns>Return true if the solution contains the reagent.</returns>
public bool ContainsReagent(string reagentId, out ReagentUnit quantity)
{
foreach (var reagent in _containedSolution.Contents)
{
if (reagent.ReagentId == reagentId)
{
quantity = reagent.Quantity;
return true;
}
}
quantity = ReagentUnit.New(0);
return false;
}
public string GetMajorReagentId()
{
if (_containedSolution.Contents.Count == 0)
{
return "";
}
var majorReagent = _containedSolution.Contents.OrderByDescending(reagent => reagent.Quantity).First();;
return majorReagent.ReagentId;
}
protected void UpdateFillIcon()
{
if (string.IsNullOrEmpty(_fillInitState)) return;
var percentage = (CurrentVolume / MaxVolume).Double();
var level = ContentHelpers.RoundToLevels(percentage * 100, 100, _fillInitSteps);
//Transformed glass uses special fancy sprites so we don't bother
if (level == 0 || Owner.TryGetComponent<TransformableContainerComponent>(out var transformableContainerComponent)
&& transformableContainerComponent.Transformed)
{
_spriteComponent.LayerSetColor(1, Color.Transparent);
return;
}
_fillSprite = new SpriteSpecifier.Rsi(_fillPath, _fillInitState+level);
_spriteComponent.LayerSetSprite(1, _fillSprite);
_spriteComponent.LayerSetColor(1,SubstanceColor);
}
protected virtual void OnSolutionChanged(bool skipColor)
{
if (!skipColor)
RecalculateColor();
UpdateFillIcon();
_chemistrySystem.HandleSolutionChange(Owner);
}
}
}

View File

@@ -0,0 +1,90 @@
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.Chemistry;
using ICSharpCode.SharpZipLib.Zip.Compression;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Server.GameObjects.Components.Chemistry
{
[RegisterComponent]
public class TransformableContainerComponent : Component, ISolutionChange
{
#pragma warning disable 649
[Dependency] private readonly IPrototypeManager _prototypeManager;
#pragma warning restore 649
public override string Name => "TransformableContainer";
private bool _transformed = false;
public bool Transformed { get => _transformed; }
private SpriteSpecifier _initialSprite;
private string _initialName;
private string _initialDescription;
private SpriteComponent _sprite;
private ReagentPrototype _currentReagent;
public override void Initialize()
{
base.Initialize();
_sprite = Owner.GetComponent<SpriteComponent>();
_initialSprite = new SpriteSpecifier.Rsi(new ResourcePath(_sprite.BaseRSIPath), "icon");
_initialName = Owner.Name;
_initialDescription = Owner.Description;
}
protected override void Startup()
{
base.Startup();
Owner.GetComponent<SolutionComponent>().Capabilities |= SolutionCaps.FitsInDispenser;;
}
public void CancelTransformation()
{
_currentReagent = null;
_transformed = false;
_sprite.LayerSetSprite(0, _initialSprite);
Owner.Name = _initialName;
Owner.Description = _initialDescription;
}
void ISolutionChange.SolutionChanged(SolutionChangeEventArgs eventArgs)
{
var solution = eventArgs.Owner.GetComponent<SolutionComponent>();
//Transform container into initial state when emptied
if (_currentReagent != null && solution.ReagentList.Count == 0)
{
CancelTransformation();
}
//the biggest reagent in the solution decides the appearance
var reagentId = solution.GetMajorReagentId();
//If biggest reagent didn't changed - don't change anything at all
if (_currentReagent != null && _currentReagent.ID == reagentId)
{
return;
}
//Only reagents with spritePath property can change appearance of transformable containers!
if (!string.IsNullOrWhiteSpace(reagentId) &&
_prototypeManager.TryIndex(reagentId, out ReagentPrototype proto) &&
!string.IsNullOrWhiteSpace(proto.SpriteReplacementPath))
{
var spriteSpec = new SpriteSpecifier.Rsi(new ResourcePath("Objects/Drinks/" + proto.SpriteReplacementPath),"icon");
_sprite.LayerSetSprite(0, spriteSpec);
Owner.Name = proto.Name + " glass";
Owner.Description = proto.Description;
_currentReagent = proto;
_transformed = true;
}
}
}
}

View File

@@ -0,0 +1,76 @@
using Content.Server.GameObjects.Components.Power;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces.GameTicking;
using Content.Shared.GameObjects.Components.Command;
using Robust.Server.GameObjects.Components.UserInterface;
using Robust.Server.Interfaces.GameObjects;
using Robust.Server.Interfaces.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
namespace Content.Server.GameObjects.Components.Command
{
[RegisterComponent]
[ComponentReference(typeof(IActivate))]
public class CommunicationsConsoleComponent : SharedCommunicationsConsoleComponent, IActivate
{
#pragma warning disable 649
[Dependency] private IEntitySystemManager _entitySystemManager;
#pragma warning restore 649
private BoundUserInterface _userInterface;
private PowerDeviceComponent _powerDevice;
private bool Powered => _powerDevice.Powered;
private RoundEndSystem RoundEndSystem => _entitySystemManager.GetEntitySystem<RoundEndSystem>();
public override void Initialize()
{
base.Initialize();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>().GetBoundUserInterface(CommunicationsConsoleUiKey.Key);
_userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
_powerDevice = Owner.GetComponent<PowerDeviceComponent>();
RoundEndSystem.OnRoundEndCountdownStarted += UpdateBoundInterface;
RoundEndSystem.OnRoundEndCountdownCancelled += UpdateBoundInterface;
RoundEndSystem.OnRoundEndCountdownFinished += UpdateBoundInterface;
}
private void UpdateBoundInterface()
{
_userInterface.SetState(new CommunicationsConsoleInterfaceState(RoundEndSystem.ExpectedCountdownEnd));
}
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage obj)
{
switch (obj.Message)
{
case CommunicationsConsoleCallEmergencyShuttleMessage _:
RoundEndSystem.RequestRoundEnd();
break;
case CommunicationsConsoleRecallEmergencyShuttleMessage _:
RoundEndSystem.CancelRoundEndCountdown();
break;
}
}
public void OpenUserInterface(IPlayerSession session)
{
_userInterface.Open(session);
}
void IActivate.Activate(ActivateEventArgs eventArgs)
{
if (!eventArgs.User.TryGetComponent(out IActorComponent actor))
return;
if (!Powered)
{
return;
}
OpenUserInterface(actor.playerSession);
}
}
}

View File

@@ -5,6 +5,7 @@ using Content.Server.GameObjects.Components.Stack;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces;
using Content.Shared.Construction;
using Content.Shared.GameObjects.Components;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Server.Interfaces.GameObjects;
@@ -14,7 +15,6 @@ using Robust.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.Interfaces.Random;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Map;
using Robust.Shared.ViewVariables;
using static Content.Shared.Construction.ConstructionStepMaterial;
using static Content.Shared.Construction.ConstructionStepTool;
@@ -114,7 +114,7 @@ namespace Content.Server.GameObjects.Components.Construction
{
Sprite.AddLayerWithSprite(prototype.Icon);
}
}

View File

@@ -75,7 +75,13 @@ namespace Content.Server.GameObjects
{
if (damageType == DamageType.Total)
{
throw new ArgumentException("Cannot take damage for DamageType.Total");
foreach (DamageType e in Enum.GetValues(typeof(DamageType)))
{
if (e == damageType) continue;
TakeDamage(e, amount, source, sourceMob);
}
return;
}
InitializeDamageType(damageType);

View File

@@ -150,8 +150,8 @@ namespace Content.Server.GameObjects.Components
if(!entity.Transform.IsMapTransform)
continue;
// only items that can be stored in an inventory, or a player, can be eaten by a locker
if(!entity.HasComponent<StoreableComponent>() && !entity.HasComponent<IActorComponent>())
// only items that can be stored in an inventory, or a mob, can be eaten by a locker
if (!entity.HasComponent<StoreableComponent>() && !entity.HasComponent<SpeciesComponent>())
continue;
if (!AddToContents(entity))
@@ -207,7 +207,8 @@ namespace Content.Server.GameObjects.Components
private bool AddToContents(IEntity entity)
{
var collidableComponent = Owner.GetComponent<ICollidableComponent>();
if(entity.TryGetComponent<ICollidableComponent>(out var entityCollidableComponent))
ICollidableComponent entityCollidableComponent;
if (entity.TryGetComponent(out entityCollidableComponent))
{
if(MaxSize < entityCollidableComponent.WorldAABB.Size.X
|| MaxSize < entityCollidableComponent.WorldAABB.Size.Y)
@@ -247,6 +248,10 @@ namespace Content.Server.GameObjects.Components
}
Contents.Insert(entity);
entity.Transform.WorldPosition = worldPos;
if (entityCollidableComponent != null)
{
entityCollidableComponent.CollisionEnabled = false;
}
return true;
}
return false;
@@ -256,7 +261,13 @@ namespace Content.Server.GameObjects.Components
{
foreach (var contained in Contents.ContainedEntities.ToArray())
{
Contents.Remove(contained);
if(Contents.Remove(contained))
{
if (contained.TryGetComponent<ICollidableComponent>(out var entityCollidableComponent))
{
entityCollidableComponent.CollisionEnabled = true;
}
}
}
}

View File

@@ -38,5 +38,6 @@ namespace Content.Server.GameObjects.Components.Markers
Unset = 0,
LateJoin,
Job,
Observer,
}
}

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.GameObjects;
using Content.Shared.GameObjects.Components.Medical;
using Content.Server.GameObjects.Components.Power;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.Components.Container;
using Robust.Server.GameObjects.Components.UserInterface;
@@ -24,6 +25,10 @@ namespace Content.Server.GameObjects.Components.Medical
private readonly Vector2 _ejectOffset = new Vector2(-0.5f, 0f);
public bool IsOccupied => _bodyContainer.ContainedEntity != null;
///implementing PowerDeviceComponent
private PowerDeviceComponent _powerDevice;
private bool Powered => _powerDevice.Powered;
public override void Initialize()
{
base.Initialize();
@@ -31,6 +36,7 @@ namespace Content.Server.GameObjects.Components.Medical
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
.GetBoundUserInterface(MedicalScannerUiKey.Key);
_bodyContainer = ContainerManagerComponent.Ensure<ContainerSlot>($"{Name}-bodyContainer", Owner);
_powerDevice = Owner.GetComponent<PowerDeviceComponent>();
UpdateUserInterface();
}
@@ -79,6 +85,8 @@ namespace Content.Server.GameObjects.Components.Medical
private void UpdateUserInterface()
{
if (!Powered)
return;
var newState = GetUserInterfaceState();
_userInterface.SetState(newState);
}
@@ -112,6 +120,8 @@ namespace Content.Server.GameObjects.Components.Medical
{
return;
}
if (!Powered)
return;
_userInterface.Open(actor.playerSession);
}

View File

@@ -34,29 +34,24 @@ namespace Content.Server.GameObjects.Components.Metabolism
/// Max volume of internal solution storage
/// </summary>
[ViewVariables]
private int _initialMaxVolume;
private ReagentUnit _initialMaxVolume;
/// <summary>
/// Empty volume of internal solution
/// </summary>
public int EmptyVolume => _internalSolution.EmptyVolume;
public ReagentUnit EmptyVolume => _internalSolution.EmptyVolume;
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _initialMaxVolume, "maxVolume", 250);
serializer.DataField(ref _initialMaxVolume, "maxVolume", ReagentUnit.New(250));
}
public override void Initialize()
protected override void Startup()
{
base.Initialize();
//Create and setup internal solution storage
_internalSolution = new SolutionComponent();
_internalSolution.InitializeFromPrototype();
_internalSolution.Init();
base.Startup();
_internalSolution = Owner.GetComponent<SolutionComponent>();
_internalSolution.MaxVolume = _initialMaxVolume;
_internalSolution.Owner = Owner; //Manually set owner to avoid crash when VV'ing this
}
/// <summary>
@@ -98,7 +93,7 @@ namespace Content.Server.GameObjects.Components.Metabolism
//Run metabolism code for each reagent
foreach (var metabolizable in proto.Metabolism)
{
int reagentDelta = metabolizable.Metabolize(Owner, reagent.ReagentId, tickTime);
var reagentDelta = metabolizable.Metabolize(Owner, reagent.ReagentId, tickTime);
_internalSolution.TryRemoveReagent(reagent.ReagentId, reagentDelta);
}
}

View File

@@ -1,4 +1,5 @@
using Content.Server.Mobs;
using Content.Server.GameObjects.Components.Observer;
using Content.Server.Mobs;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.ViewVariables;
@@ -46,13 +47,31 @@ namespace Content.Server.GameObjects.Components.Mobs
Mind = value;
}
public override void OnRemove()
protected override void Shutdown()
{
base.OnRemove();
base.Shutdown();
if (HasMind)
{
Mind.TransferTo(null);
var visiting = Mind.VisitingEntity;
if (visiting != null)
{
if (visiting.TryGetComponent(out GhostComponent ghost))
{
ghost.CanReturnToBody = false;
}
Mind.TransferTo(visiting);
}
else
{
var ghost = Owner.EntityManager.SpawnEntity("MobObserver", Owner.Transform.GridPosition);
ghost.Name = Mind.CharacterName;
var ghostComponent = ghost.GetComponent<GhostComponent>();
ghostComponent.CanReturnToBody = false;
Mind.TransferTo(ghost);
}
}
}
}

View File

@@ -3,9 +3,12 @@ using System.Collections.Generic;
using Content.Server.GameObjects.Components.Mobs;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces;
using Content.Server.Interfaces.GameObjects.Components.Movement;
using Content.Server.Observer;
using Content.Shared.GameObjects;
using Content.Shared.GameObjects.Components.Mobs;
using Robust.Server.GameObjects;
using Robust.Server.Interfaces.Player;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
@@ -15,7 +18,7 @@ using Robust.Shared.Serialization;
namespace Content.Server.GameObjects
{
[RegisterComponent]
public class SpeciesComponent : SharedSpeciesComponent, IActionBlocker, IOnDamageBehavior, IExAct
public class SpeciesComponent : SharedSpeciesComponent, IActionBlocker, IOnDamageBehavior, IExAct, IRelayMoveInput
{
/// <summary>
/// Damagestates are reached by reaching a certain damage threshold, they will block actions after being reached
@@ -198,6 +201,14 @@ namespace Content.Server.GameObjects
Owner.GetComponent<DamageableComponent>().TakeDamage(DamageType.Brute, bruteDamage, null);
Owner.GetComponent<DamageableComponent>().TakeDamage(DamageType.Heat, burnDamage, null);
}
void IRelayMoveInput.MoveInputPressed(IPlayerSession session)
{
if (CurrentDamageState is DeadState)
{
new Ghost().Execute(null, session, null);
}
}
}
/// <summary>

View File

@@ -56,18 +56,6 @@ namespace Content.Server.GameObjects.Components.Movement
serializer.DataField(ref _visionRadius, "vision", 8.0f);
}
/// <summary>
/// Movement speed (m/s) that the entity walks, before modifiers
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public float BaseWalkSpeed { get; set; } = PlayerInputMoverComponent.DefaultBaseWalkSpeed;
/// <summary>
/// Movement speed (m/s) that the entity sprints, before modifiers
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public float BaseSprintSpeed { get; set; } = PlayerInputMoverComponent.DefaultBaseSprintSpeed;
/// <summary>
/// Movement speed (m/s) that the entity walks, after modifiers
/// </summary>
@@ -76,14 +64,15 @@ namespace Content.Server.GameObjects.Components.Movement
{
get
{
float speed = BaseWalkSpeed;
if (Owner.TryGetComponent<MovementSpeedModifierComponent>(out MovementSpeedModifierComponent component))
if (Owner.TryGetComponent(out MovementSpeedModifierComponent component))
{
speed *= component.WalkSpeedModifier;
return component.CurrentWalkSpeed;
}
return speed;
return MovementSpeedModifierComponent.DefaultBaseWalkSpeed;
}
}
/// <summary>
/// Movement speed (m/s) that the entity walks, after modifiers
/// </summary>
@@ -92,12 +81,12 @@ namespace Content.Server.GameObjects.Components.Movement
{
get
{
float speed = BaseSprintSpeed;
if (Owner.TryGetComponent<MovementSpeedModifierComponent>(out MovementSpeedModifierComponent component))
if (Owner.TryGetComponent(out MovementSpeedModifierComponent component))
{
speed *= component.SprintSpeedModifier;
return component.CurrentSprintSpeed;
}
return speed;
return MovementSpeedModifierComponent.DefaultBaseSprintSpeed;
}
}

View File

@@ -1,14 +1,20 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Movement
{
[RegisterComponent]
public class MovementSpeedModifierComponent : Component
{
public const float DefaultBaseWalkSpeed = 4.0f;
public const float DefaultBaseSprintSpeed = 7.0f;
public override string Name => "MovementSpeedModifier";
private float _cachedWalkSpeedModifier = 1.0f;
[ViewVariables]
public float WalkSpeedModifier
{
get
@@ -18,6 +24,7 @@ namespace Content.Server.GameObjects.Components.Movement
}
}
private float _cachedSprintSpeedModifier;
[ViewVariables]
public float SprintSpeedModifier
{
get
@@ -27,6 +34,16 @@ namespace Content.Server.GameObjects.Components.Movement
}
}
[ViewVariables(VVAccess.ReadWrite)]
public float BaseWalkSpeed { get; set; }
[ViewVariables(VVAccess.ReadWrite)]
public float BaseSprintSpeed { get; set; }
[ViewVariables]
public float CurrentWalkSpeed => WalkSpeedModifier * BaseWalkSpeed;
[ViewVariables]
public float CurrentSprintSpeed => SprintSpeedModifier * BaseSprintSpeed;
/// <summary>
/// set to warn us that a component's movespeed modifier has changed
/// </summary>
@@ -37,6 +54,14 @@ namespace Content.Server.GameObjects.Components.Movement
_movespeedModifiersNeedRefresh = true;
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(this, p => p.BaseWalkSpeed, "baseWalkSpeed", 4);
serializer.DataField(this, p => p.BaseSprintSpeed, "baseSprintSpeed", 7);
}
/// <summary>
/// Recalculate movement speed with current modifiers, or return early if no change
/// </summary>

View File

@@ -22,9 +22,6 @@ namespace Content.Server.GameObjects.Components.Movement
[ComponentReference(typeof(IMoverComponent))]
public class PlayerInputMoverComponent : Component, IMoverComponent, ICollideSpecial
{
public const float DefaultBaseWalkSpeed = 4.0f;
public const float DefaultBaseSprintSpeed = 7.0f;
#pragma warning disable 649
[Dependency] private readonly IConfigurationManager _configurationManager;
#pragma warning restore 649
@@ -37,19 +34,6 @@ namespace Content.Server.GameObjects.Components.Movement
/// <inheritdoc />
public override string Name => "PlayerInputMover";
/// <summary>
/// Movement speed (m/s) that the entity walks, before modifiers
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public float BaseWalkSpeed { get; set; } = DefaultBaseWalkSpeed;
/// <summary>
/// Movement speed (m/s) that the entity sprints, before modifiers
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public float BaseSprintSpeed { get; set; } = DefaultBaseSprintSpeed;
/// <summary>
/// Movement speed (m/s) that the entity walks, after modifiers
/// </summary>
@@ -58,12 +42,12 @@ namespace Content.Server.GameObjects.Components.Movement
{
get
{
float speed = BaseWalkSpeed;
if (Owner.TryGetComponent<MovementSpeedModifierComponent>(out MovementSpeedModifierComponent component))
if (Owner.TryGetComponent(out MovementSpeedModifierComponent component))
{
speed *= component.WalkSpeedModifier;
return component.CurrentWalkSpeed;
}
return speed;
return MovementSpeedModifierComponent.DefaultBaseWalkSpeed;
}
}
/// <summary>
@@ -74,12 +58,12 @@ namespace Content.Server.GameObjects.Components.Movement
{
get
{
float speed = BaseSprintSpeed;
if (Owner.TryGetComponent<MovementSpeedModifierComponent>(out MovementSpeedModifierComponent component))
if (Owner.TryGetComponent(out MovementSpeedModifierComponent component))
{
speed *= component.SprintSpeedModifier;
return component.CurrentSprintSpeed;
}
return speed;
return MovementSpeedModifierComponent.DefaultBaseSprintSpeed;
}
}
@@ -104,12 +88,6 @@ namespace Content.Server.GameObjects.Components.Movement
/// </summary>
[ViewVariables] public bool DiagonalMovementEnabled => _configurationManager.GetCVar<bool>("game.diagonalmovement");
public override void Initialize()
{
base.Initialize();
_configurationManager.RegisterCVar("game.diagonalmovement", true, CVar.ARCHIVE);
}
/// <inheritdoc />
public override void OnAdd()
{
@@ -120,20 +98,6 @@ namespace Content.Server.GameObjects.Components.Movement
base.OnAdd();
}
/// <inheritdoc />
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
//only save the base speeds - the current speeds are transient.
serializer.DataReadWriteFunction("wspd", DefaultBaseWalkSpeed, value => BaseWalkSpeed = value, () => BaseWalkSpeed);
serializer.DataReadWriteFunction("rspd", DefaultBaseSprintSpeed, value => BaseSprintSpeed = value, () => BaseSprintSpeed);
// The velocity and moving directions is usually set from player or AI input,
// so we don't want to save/load these derived fields.
}
/// <summary>
/// Toggles one of the four cardinal directions. Each of the four directions are
/// composed into a single direction vector, <see cref="VelocityDir"/>. Enabling

View File

@@ -5,6 +5,7 @@ using Content.Server.GameObjects.EntitySystems;
using Content.Shared.Chemistry;
using Content.Shared.GameObjects.Components.Nutrition;
using Content.Shared.Interfaces;
using Content.Shared.Maths;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
@@ -32,19 +33,16 @@ namespace Content.Server.GameObjects.Components.Nutrition
[ViewVariables]
private string _finishPrototype;
public int TransferAmount => _transferAmount;
public ReagentUnit TransferAmount => _transferAmount;
[ViewVariables]
private int _transferAmount = 2;
private ReagentUnit _transferAmount = ReagentUnit.New(2);
public int MaxVolume
public ReagentUnit MaxVolume
{
get => _contents.MaxVolume;
set => _contents.MaxVolume = value;
}
private Solution _initialContents; // This is just for loading from yaml
private int _maxVolume;
private bool _despawnOnFinish;
private bool _drinking;
@@ -56,60 +54,28 @@ namespace Content.Server.GameObjects.Components.Nutrition
{
return 0;
}
return Math.Max(1, _contents.CurrentVolume / _transferAmount);
return Math.Max(1, (int)Math.Ceiling((_contents.CurrentVolume / _transferAmount).Float()));
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _initialContents, "contents", null);
serializer.DataField(ref _maxVolume, "max_volume", 0);
serializer.DataField(ref _useSound, "use_sound", "/Audio/items/drink.ogg");
// E.g. cola can when done or clear bottle, whatever
// Currently this will enforce it has the same volume but this may change.
serializer.DataField(ref _despawnOnFinish, "despawn_empty", true);
// Currently this will enforce it has the same volume but this may change. - TODO: this should be implemented in a separate component
serializer.DataField(ref _despawnOnFinish, "despawn_empty", false);
serializer.DataField(ref _finishPrototype, "spawn_on_finish", null);
}
public override void Initialize()
{
base.Initialize();
if (_contents == null)
{
if (Owner.TryGetComponent(out SolutionComponent solutionComponent))
{
_contents = solutionComponent;
}
else
{
_contents = Owner.AddComponent<SolutionComponent>();
//Ensure SolutionComponent supports click transferring if custom one not set
_contents.Capabilities = SolutionCaps.PourIn
| SolutionCaps.PourOut
| SolutionCaps.Injectable;
var pourable = Owner.AddComponent<PourableComponent>();
pourable.TransferAmount = 5;
}
}
_drinking = false;
if (_maxVolume != 0)
_contents.MaxVolume = _maxVolume;
else
_contents.MaxVolume = _initialContents.TotalVolume;
_contents.SolutionChanged += HandleSolutionChangedEvent;
}
protected override void Startup()
{
base.Startup();
if (_initialContents != null)
{
_contents.TryAddSolution(_initialContents, true, true);
}
_initialContents = null;
_contents = Owner.GetComponent<SolutionComponent>();
_contents.Capabilities = SolutionCaps.PourIn
| SolutionCaps.PourOut
| SolutionCaps.Injectable;
_drinking = false;
Owner.TryGetComponent(out AppearanceComponent appearance);
_appearanceComponent = appearance;
_appearanceComponent?.SetData(SharedFoodComponent.FoodVisuals.MaxUses, MaxVolume);
@@ -149,7 +115,7 @@ namespace Content.Server.GameObjects.Components.Nutrition
if (user.TryGetComponent(out StomachComponent stomachComponent))
{
_drinking = true;
var transferAmount = Math.Min(_transferAmount, _contents.CurrentVolume);
var transferAmount = ReagentUnit.Min(_transferAmount, _contents.CurrentVolume);
var split = _contents.SplitSolution(transferAmount);
if (stomachComponent.TryTransferSolution(split))
{
@@ -167,54 +133,6 @@ namespace Content.Server.GameObjects.Components.Nutrition
}
_drinking = false;
}
Finish(user);
}
/// <summary>
/// Trigger finish behavior in the drink if applicable.
/// Depending on the drink this will either delete it,
/// or convert it to another entity, like an empty variant.
/// </summary>
/// <param name="user">The entity that is using the drink</param>
public void Finish(IEntity user)
{
// Drink containers are mostly transient.
if (_drinking || !_despawnOnFinish || UsesLeft() > 0)
return;
var gridPos = Owner.Transform.GridPosition;
_contents.SolutionChanged -= HandleSolutionChangedEvent;
Owner.Delete();
if (_finishPrototype == null || user == null)
return;
var finisher = Owner.EntityManager.SpawnEntity(_finishPrototype, Owner.Transform.GridPosition);
if (user.TryGetComponent(out HandsComponent handsComponent) && finisher.TryGetComponent(out ItemComponent itemComponent))
{
if (handsComponent.CanPutInHand(itemComponent))
{
handsComponent.PutInHand(itemComponent);
return;
}
}
finisher.Transform.GridPosition = gridPos;
if (finisher.TryGetComponent(out DrinkComponent drinkComponent))
{
drinkComponent.MaxVolume = MaxVolume;
}
}
/// <summary>
/// Updates drink state when the solution is changed by something other
/// than this component. Without this some drinks won't properly delete
/// themselves without additional clicks/uses after them being emptied.
/// </summary>
private void HandleSolutionChangedEvent()
{
Finish(null);
}
}
}

View File

@@ -33,7 +33,7 @@ namespace Content.Server.GameObjects.Components.Nutrition
[ViewVariables]
private SolutionComponent _contents;
[ViewVariables]
private int _transferAmount;
private ReagentUnit _transferAmount;
private Solution _initialContents; // This is just for loading from yaml
@@ -44,7 +44,7 @@ namespace Content.Server.GameObjects.Components.Nutrition
serializer.DataField(ref _initialContents, "contents", null);
serializer.DataField(ref _useSound, "use_sound", "/Audio/items/eatfood.ogg");
// Default is transfer 30 units
serializer.DataField(ref _transferAmount, "transfer_amount", 5);
serializer.DataField(ref _transferAmount, "transfer_amount", ReagentUnit.New(5));
// E.g. empty chip packet when done
serializer.DataField(ref _finishPrototype, "spawn_on_finish", null);
}
@@ -78,7 +78,7 @@ namespace Content.Server.GameObjects.Components.Nutrition
_initialContents = null;
if (_contents.CurrentVolume == 0)
{
_contents.TryAddReagent("chem.Nutriment", 5, out _);
_contents.TryAddReagent("chem.Nutriment", ReagentUnit.New(5), out _);
}
Owner.TryGetComponent(out AppearanceComponent appearance);
_appearanceComponent = appearance;
@@ -99,7 +99,7 @@ namespace Content.Server.GameObjects.Components.Nutrition
{
return 0;
}
return Math.Max(1, _contents.CurrentVolume / _transferAmount);
return Math.Max(1, (int)Math.Ceiling((_contents.CurrentVolume / _transferAmount).Float()));
}
bool IUse.UseEntity(UseEntityEventArgs eventArgs)
@@ -130,7 +130,7 @@ namespace Content.Server.GameObjects.Components.Nutrition
// TODO: Add putting food back in boxes here?
if (user.TryGetComponent(out StomachComponent stomachComponent))
{
var transferAmount = Math.Min(_transferAmount, _contents.CurrentVolume);
var transferAmount = ReagentUnit.Min(_transferAmount, _contents.CurrentVolume);
var split = _contents.SplitSolution(transferAmount);
if (stomachComponent.TryTransferSolution(split))
{

View File

@@ -27,7 +27,7 @@ namespace Content.Server.GameObjects.Components.Nutrition
/// <summary>
/// Max volume of internal solution storage
/// </summary>
public int MaxVolume
public ReagentUnit MaxVolume
{
get => _stomachContents.MaxVolume;
set => _stomachContents.MaxVolume = value;
@@ -43,7 +43,7 @@ namespace Content.Server.GameObjects.Components.Nutrition
/// Initial internal solution storage volume
/// </summary>
[ViewVariables]
private int _initialMaxVolume;
private ReagentUnit _initialMaxVolume;
/// <summary>
/// Time in seconds between reagents being ingested and them being transferred to <see cref="BloodstreamComponent"/>
@@ -64,26 +64,19 @@ namespace Content.Server.GameObjects.Components.Nutrition
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _initialMaxVolume, "maxVolume", 100);
serializer.DataField(ref _initialMaxVolume, "maxVolume", ReagentUnit.New(100));
serializer.DataField(ref _digestionDelay, "digestionDelay", 20);
}
public override void Initialize()
protected override void Startup()
{
base.Initialize();
//Doesn't use Owner.AddComponent<>() to avoid cross-contamination (e.g. with blood or whatever they holds other solutions)
_stomachContents = new SolutionComponent();
_stomachContents.InitializeFromPrototype();
_stomachContents = Owner.GetComponent<SolutionComponent>();
_stomachContents.MaxVolume = _initialMaxVolume;
_stomachContents.Owner = Owner; //Manually set owner to avoid crash when VV'ing this
//Ensure bloodstream in present
if (!Owner.TryGetComponent<BloodstreamComponent>(out _bloodstream))
{
Logger.Warning(_localizationManager.GetString(
"StomachComponent entity does not have a BloodstreamComponent, which is required for it to function. Owner entity name: {0}",
Owner.Name));
"StomachComponent entity does not have a BloodstreamComponent, which is required for it to function. Owner entity name: {0}",
Owner.Name));
}
}
@@ -141,10 +134,10 @@ namespace Content.Server.GameObjects.Components.Nutrition
private class ReagentDelta
{
public readonly string ReagentId;
public readonly int Quantity;
public readonly ReagentUnit Quantity;
public float Lifetime { get; private set; }
public ReagentDelta(string reagentId, int quantity)
public ReagentDelta(string reagentId, ReagentUnit quantity)
{
ReagentId = reagentId;
Quantity = quantity;

View File

@@ -0,0 +1,76 @@
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Players;
using Content.Shared.GameObjects.Components.Observer;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.Components;
using Robust.Server.Interfaces.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Network;
using Robust.Shared.ViewVariables;
using Timer = Robust.Shared.Timers.Timer;
namespace Content.Server.GameObjects.Components.Observer
{
[RegisterComponent]
public class GhostComponent : SharedGhostComponent, IActionBlocker
{
private bool _canReturnToBody = true;
[ViewVariables(VVAccess.ReadWrite)]
public bool CanReturnToBody
{
get => _canReturnToBody;
set
{
_canReturnToBody = value;
Dirty();
}
}
public override void Initialize()
{
base.Initialize();
Owner.EnsureComponent<VisibilityComponent>().Layer = (int)VisibilityFlags.Ghost;
}
public override ComponentState GetComponentState() => new GhostComponentState(CanReturnToBody);
public override void HandleMessage(ComponentMessage message, INetChannel netChannel = null,
IComponent component = null)
{
base.HandleMessage(message, netChannel, component);
switch (message)
{
case ReturnToBodyComponentMessage reenter:
if (!Owner.TryGetComponent(out IActorComponent actor) || !CanReturnToBody) break;
if (netChannel == null || netChannel == actor.playerSession.ConnectedClient)
{
actor.playerSession.ContentData().Mind.UnVisit();
Owner.Delete();
}
break;
case PlayerAttachedMsg msg:
msg.NewPlayer.VisibilityMask |= (int)VisibilityFlags.Ghost;
Dirty();
break;
case PlayerDetachedMsg msg:
msg.OldPlayer.VisibilityMask &= ~(int)VisibilityFlags.Ghost;
break;
default:
break;
}
}
public bool CanInteract() => false;
public bool CanUse() => false;
public bool CanThrow() => false;
public bool CanDrop() => false;
public bool CanPickup() => false;
public bool CanEmote() => false;
public bool CanAttack() => false;
}
}

View File

@@ -1,7 +1,15 @@
using System;
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.Audio;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Random;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
@@ -24,9 +32,14 @@ namespace Content.Server.GameObjects.Components.Power
/// Component that represents a light bulb. Can be broken, or burned, which turns them mostly useless.
/// </summary>
[RegisterComponent]
public class LightBulbComponent : Component
public class LightBulbComponent : Component, ILand
{
#pragma warning disable 649
[Dependency] private readonly IPrototypeManager _prototypeManager;
[Dependency] private readonly IRobustRandom _random;
#pragma warning restore 649
/// <summary>
/// Invoked whenever the state of the light bulb changes.
/// </summary>
@@ -104,5 +117,18 @@ namespace Content.Server.GameObjects.Components.Power
base.Initialize();
UpdateColor();
}
public void Land(LandEventArgs eventArgs)
{
if (State == LightBulbState.Broken)
return;
var soundCollection = _prototypeManager.Index<SoundCollectionPrototype>("glassbreak");
var file = _random.Pick(soundCollection.PickFiles);
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<AudioSystem>().Play(file, Owner);
State = LightBulbState.Broken;
}
}
}

View File

@@ -62,6 +62,9 @@ namespace Content.Server.GameObjects.Components.Research
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message)
{
if (!Powered)
return;
switch (message.Message)
{
case LatheQueueRecipeMessage msg:
@@ -87,13 +90,15 @@ namespace Content.Server.GameObjects.Components.Research
case LatheServerSyncMessage msg:
if (!Owner.TryGetComponent(out TechnologyDatabaseComponent database)
|| !Owner.TryGetComponent(out ProtolatheDatabaseComponent protoDatabase)) return;
|| !Owner.TryGetComponent(out ProtolatheDatabaseComponent protoDatabase)) return;
if(database.SyncWithServer())
if (database.SyncWithServer())
protoDatabase.Sync();
break;
}
}
internal bool Produce(LatheRecipePrototype recipe)

View File

@@ -46,6 +46,8 @@ namespace Content.Server.GameObjects.Components.Research
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message)
{
if (!Owner.TryGetComponent(out TechnologyDatabaseComponent database)) return;
if (!Powered)
return;
switch (message.Message)
{

View File

@@ -3,11 +3,9 @@ using Content.Server.GameObjects.EntitySystems;
using Content.Shared.GameObjects.Components;
using Content.Shared.Interfaces;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.Reflection;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Map;
using Robust.Shared.Serialization;
using Robust.Shared.Timers;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
@@ -23,34 +21,19 @@ namespace Content.Server.GameObjects.Components.Stack
[Dependency] private readonly ISharedNotifyManager _sharedNotifyManager;
#pragma warning restore 649
private const string SerializationCache = "stack";
private int _count = 50;
private int _maxCount = 50;
private bool _throwIndividually = false;
[ViewVariables(VVAccess.ReadWrite)]
public int Count
public override int Count
{
get => _count;
get => base.Count;
set
{
_count = value;
if (_count <= 0)
base.Count = value;
if (Count <= 0)
{
Owner.Delete();
}
Dirty();
}
}
[ViewVariables]
public int MaxCount
{
get => _maxCount;
private set
{
_maxCount = value;
Dirty();
}
}
@@ -65,12 +48,6 @@ namespace Content.Server.GameObjects.Components.Stack
}
}
[ViewVariables]
public int AvailableSpace => MaxCount - Count;
[ViewVariables]
public object StackType { get; private set; }
public void Add(int amount)
{
Count += amount;
@@ -91,42 +68,6 @@ namespace Content.Server.GameObjects.Components.Stack
return false;
}
public override void ExposeData(ObjectSerializer serializer)
{
serializer.DataFieldCached(ref _maxCount, "max", 50);
serializer.DataFieldCached(ref _count, "count", MaxCount);
if (!serializer.Reading)
{
return;
}
if (serializer.TryGetCacheData(SerializationCache, out object stackType))
{
StackType = stackType;
return;
}
if (serializer.TryReadDataFieldCached("stacktype", out string raw))
{
var refl = IoCManager.Resolve<IReflectionManager>();
if (refl.TryParseEnumReference(raw, out var @enum))
{
stackType = @enum;
}
else
{
stackType = raw;
}
}
else
{
stackType = Owner.Prototype.ID;
}
serializer.SetCacheData(SerializationCache, stackType);
StackType = stackType;
}
public bool AttackBy(AttackByEventArgs eventArgs)
{
if (eventArgs.AttackWith.TryGetComponent<StackComponent>(out var stack))
@@ -175,20 +116,5 @@ namespace Content.Server.GameObjects.Components.Stack
"There is [color=lightgray]1[/color] thing in the stack",
"There are [color=lightgray]{0}[/color] things in the stack.", Count, Count));
}
public override ComponentState GetComponentState()
{
return new StackComponentState(Count, MaxCount);
}
}
public enum StackType
{
Metal,
Glass,
Cable,
Ointment,
Brutepack,
FloorTileSteel
}
}

View File

@@ -47,6 +47,8 @@ namespace Content.Server.GameObjects.Components.VendingMachines
{
return;
}
if (!Powered)
return;
var wires = Owner.GetComponent<WiresComponent>();
if (wires.IsPanelOpen)
@@ -121,6 +123,9 @@ namespace Content.Server.GameObjects.Components.VendingMachines
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage serverMsg)
{
if (!Powered)
return;
var message = serverMsg.Message;
switch (message)
{

View File

@@ -5,23 +5,23 @@ namespace Content.Server.GameObjects.EntitySystems
{
public interface IActionBlocker
{
bool CanMove();
bool CanMove() => true;
bool CanInteract();
bool CanInteract() => true;
bool CanUse();
bool CanUse() => true;
bool CanThrow();
bool CanThrow() => true;
bool CanSpeak();
bool CanSpeak() => true;
bool CanDrop();
bool CanDrop() => true;
bool CanPickup();
bool CanPickup() => true;
bool CanEmote();
bool CanEmote() => true;
bool CanAttack();
bool CanAttack() => true;
}
public class ActionBlockerSystem : EntitySystem

View File

@@ -0,0 +1,42 @@
using System;
using System.Linq;
using JetBrains.Annotations;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
namespace Content.Server.GameObjects.EntitySystems
{
/// <summary>
/// This interface gives components behavior on whether entities solution (implying SolutionComponent is in place) is changed
/// </summary>
public interface ISolutionChange
{
/// <summary>
/// Called when solution is mixed with some other solution, or when some part of the solution is removed
/// </summary>
void SolutionChanged(SolutionChangeEventArgs eventArgs);
}
public class SolutionChangeEventArgs : EventArgs
{
public IEntity Owner { get; set; }
}
[UsedImplicitly]
public class ChemistrySystem : EntitySystem
{
public void HandleSolutionChange(IEntity owner)
{
var eventArgs = new SolutionChangeEventArgs
{
Owner = owner,
};
var solutionChangeArgs = owner.GetAllComponents<ISolutionChange>().ToList();
foreach (var solutionChangeArg in solutionChangeArgs)
{
solutionChangeArg.SolutionChanged(eventArgs);
}
}
}
}

View File

@@ -3,18 +3,22 @@ using Content.Server.GameObjects.Components.Mobs;
using Content.Server.GameObjects.Components.Movement;
using Content.Server.GameObjects.Components.Sound;
using Content.Server.Interfaces.GameObjects.Components.Movement;
using Content.Server.Observer;
using Content.Shared.Audio;
using Content.Shared.GameObjects.Components.Inventory;
using Content.Shared.Maps;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Server.Interfaces.GameObjects;
using Robust.Server.Interfaces.Player;
using Robust.Server.Interfaces.Timing;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components.Transform;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Input;
using Robust.Shared.Interfaces.Configuration;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.Interfaces.Map;
@@ -23,6 +27,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
@@ -38,6 +43,7 @@ namespace Content.Server.GameObjects.EntitySystems
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager;
[Dependency] private readonly IMapManager _mapManager;
[Dependency] private readonly IRobustRandom _robustRandom;
[Dependency] private readonly IConfigurationManager _configurationManager;
#pragma warning restore 649
private AudioSystem _audioSystem;
@@ -78,6 +84,8 @@ namespace Content.Server.GameObjects.EntitySystems
SubscribeLocalEvent<PlayerDetachedSystemMessage>(PlayerDetached);
_audioSystem = EntitySystemManager.GetEntitySystem<AudioSystem>();
_configurationManager.RegisterCVar("game.diagonalmovement", true, CVar.ARCHIVE);
}
private static void PlayerAttached(PlayerAttachSystemMessage ev)
@@ -133,6 +141,7 @@ namespace Content.Server.GameObjects.EntitySystems
{
if (physics.LinearVelocity != Vector2.Zero)
physics.LinearVelocity = Vector2.Zero;
}
else
{
@@ -177,9 +186,20 @@ namespace Content.Server.GameObjects.EntitySystems
private static void HandleDirChange(ICommonSession session, Direction dir, bool state)
{
if (!TryGetAttachedComponent(session as IPlayerSession, out IMoverComponent moverComp))
var playerSes = session as IPlayerSession;
if (!TryGetAttachedComponent(playerSes, out IMoverComponent moverComp))
return;
var owner = playerSes?.AttachedEntity;
if (owner != null)
{
foreach (var comp in owner.GetAllComponents<IRelayMoveInput>())
{
comp.MoveInputPressed(playerSes);
}
}
moverComp.SetVelocityDirection(dir, state);
}

View File

@@ -0,0 +1,65 @@
using System;
using System.Threading;
using Content.Server.Interfaces.GameTicking;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.Timing;
using Robust.Shared.IoC;
using Timer = Robust.Shared.Timers.Timer;
namespace Content.Server.GameObjects.EntitySystems
{
public class RoundEndSystem : EntitySystem
{
#pragma warning disable 649
[Dependency] private IGameTicker _gameTicker;
[Dependency] private IGameTiming _gameTiming;
#pragma warning restore 649
private CancellationTokenSource _roundEndCancellationTokenSource = new CancellationTokenSource();
public bool IsRoundEndCountdownStarted { get; private set; }
public TimeSpan RoundEndCountdownTime { get; set; } = TimeSpan.FromMinutes(4);
public TimeSpan? ExpectedCountdownEnd = null;
public delegate void RoundEndCountdownStarted();
public event RoundEndCountdownStarted OnRoundEndCountdownStarted;
public delegate void RoundEndCountdownCancelled();
public event RoundEndCountdownCancelled OnRoundEndCountdownCancelled;
public delegate void RoundEndCountdownFinished();
public event RoundEndCountdownFinished OnRoundEndCountdownFinished;
public void RequestRoundEnd()
{
if (IsRoundEndCountdownStarted)
return;
IsRoundEndCountdownStarted = true;
ExpectedCountdownEnd = _gameTiming.CurTime + RoundEndCountdownTime;
Timer.Spawn(RoundEndCountdownTime, EndRound, _roundEndCancellationTokenSource.Token);
OnRoundEndCountdownStarted?.Invoke();
}
public void CancelRoundEndCountdown()
{
if (!IsRoundEndCountdownStarted)
return;
IsRoundEndCountdownStarted = false;
_roundEndCancellationTokenSource.Cancel();
_roundEndCancellationTokenSource = new CancellationTokenSource();
ExpectedCountdownEnd = null;
OnRoundEndCountdownCancelled?.Invoke();
}
private void EndRound()
{
OnRoundEndCountdownFinished?.Invoke();
_gameTicker.EndRound();
}
}
}

View File

@@ -0,0 +1,10 @@
using System;
namespace Content.Server.GameObjects
{
[Flags]
public enum VisibilityFlags
{
Ghost = 2,
}
}

View File

@@ -1,4 +1,4 @@
namespace Content.Server.GameTicking
namespace Content.Server.GameTicking
{
/// <summary>
/// A round-start setup preset, such as which antagonists to spawn.
@@ -6,6 +6,7 @@ namespace Content.Server.GameTicking
public abstract class GamePreset
{
public abstract void Start();
public virtual string ModeTitle => "Sandbox";
public virtual string Description => "Secret!";
}
}

View File

@@ -1,4 +1,4 @@
using Content.Server.GameTicking.GameRules;
using Content.Server.GameTicking.GameRules;
using Content.Server.Interfaces.GameTicking;
using Robust.Shared.IoC;
@@ -15,6 +15,7 @@ namespace Content.Server.GameTicking.GamePresets
_gameTicker.AddGameRule<RuleDeathMatch>();
}
public override string Description => "Deathmatch, go and kill everybody else to win!";
public override string ModeTitle => "Deathmatch";
public override string Description => "Kill anything that moves!";
}
}

View File

@@ -1,4 +1,4 @@
using Content.Server.Sandbox;
using Content.Server.Sandbox;
using Robust.Shared.IoC;
namespace Content.Server.GameTicking.GamePresets
@@ -14,6 +14,7 @@ namespace Content.Server.GameTicking.GamePresets
_sandboxManager.IsSandboxEnabled = true;
}
public override string Description => "Sandbox, go and build something!";
public override string ModeTitle => "Sandbox";
public override string Description => "No stress, build something!";
}
}

View File

@@ -1,10 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Content.Server.GameObjects;
using Content.Server.GameObjects.Components.Access;
using Content.Server.GameObjects.Components.Markers;
using Content.Server.GameObjects.Components.Mobs;
using Content.Server.GameObjects.Components.Observer;
using Content.Server.GameTicking.GamePresets;
using Content.Server.Interfaces;
using Content.Server.Interfaces.Chat;
@@ -13,11 +15,14 @@ using Content.Server.Mobs;
using Content.Server.Mobs.Roles;
using Content.Server.Players;
using Content.Shared;
using Content.Shared.Chat;
using Content.Shared.Jobs;
using Content.Shared.Preferences;
using Robust.Server.Interfaces;
using Robust.Server.Interfaces.Maps;
using Robust.Server.Interfaces.Player;
using Robust.Server.Player;
using Robust.Server.ServerStatus;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
@@ -34,19 +39,22 @@ using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timers;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using static Content.Shared.GameObjects.Components.Inventory.EquipmentSlotDefines;
using Timer = Robust.Shared.Timers.Timer;
namespace Content.Server.GameTicking
{
public partial class GameTicker : SharedGameTicker, IGameTicker
{
private static readonly TimeSpan UpdateRestartDelay = TimeSpan.FromSeconds(20);
private const string PlayerPrototypeName = "HumanMob_Content";
private const string ObserverPrototypeName = "MobObserver";
private const string MapFile = "Maps/stationstation.yml";
private static TimeSpan _roundStartTimeSpan;
[ViewVariables] private readonly List<GameRule> _gameRules = new List<GameRule>();
[ViewVariables] private readonly List<ManifestEntry> _manifest = new List<ManifestEntry>();
@@ -66,6 +74,10 @@ namespace Content.Server.GameTicking
[ViewVariables] private bool LobbyEnabled => _configurationManager.GetCVar<bool>("game.lobbyenabled");
[ViewVariables] private bool _updateOnRoundEnd;
private CancellationTokenSource _updateShutdownCts;
[ViewVariables]
public GameRunLevel RunLevel
{
@@ -100,6 +112,7 @@ namespace Content.Server.GameTicking
_netManager.RegisterNetMessage<MsgTickerJoinGame>(nameof(MsgTickerJoinGame));
_netManager.RegisterNetMessage<MsgTickerLobbyStatus>(nameof(MsgTickerLobbyStatus));
_netManager.RegisterNetMessage<MsgTickerLobbyInfo>(nameof(MsgTickerLobbyInfo));
_netManager.RegisterNetMessage<MsgRoundEndMessage>(nameof(MsgRoundEndMessage));
SetStartPreset(_configurationManager.GetCVar<string>("game.defaultpreset"));
@@ -108,6 +121,16 @@ namespace Content.Server.GameTicking
_initialized = true;
JobControllerInit();
_watchdogApi.UpdateReceived += WatchdogApiOnUpdateReceived;
}
private void WatchdogApiOnUpdateReceived()
{
_chatManager.DispatchServerAnnouncement(Loc.GetString(
"Update has been received, server will automatically restart for update at the end of this round."));
_updateOnRoundEnd = true;
ServerEmptyUpdateRestartCheck();
}
public void Update(FrameEventArgs frameEventArgs)
@@ -121,8 +144,17 @@ namespace Content.Server.GameTicking
public void RestartRound()
{
if (_updateOnRoundEnd)
{
_baseServer.Shutdown(
Loc.GetString("Server is shutting down for update and will automatically restart."));
return;
}
Logger.InfoS("ticker", "Restarting round!");
SendServerMessage("Restarting round...");
RunLevel = GameRunLevel.PreRoundLobby;
_resettingCleanup();
_preRoundSetup();
@@ -147,6 +179,8 @@ namespace Content.Server.GameTicking
DebugTools.Assert(RunLevel == GameRunLevel.PreRoundLobby);
Logger.InfoS("ticker", "Starting round!");
SendServerMessage("The round is starting now...");
RunLevel = GameRunLevel.InRound;
var preset = MakeGamePreset();
@@ -188,9 +222,18 @@ namespace Content.Server.GameTicking
SpawnPlayer(player, job, false);
}
_roundStartTimeSpan = IoCManager.Resolve<IGameTiming>().RealTime;
_sendStatusToAll();
}
private void SendServerMessage(string message)
{
var msg = _netManager.CreateNetMessage<MsgChatMessage>();
msg.Channel = ChatChannel.Server;
msg.Message = message;
IoCManager.Resolve<IServerNetManager>().ServerSendToAll(msg);
}
private HumanoidCharacterProfile GetPlayerProfile(IPlayerSession p) =>
(HumanoidCharacterProfile) _prefsManager.GetPreferences(p.SessionId.Username).SelectedCharacter;
@@ -200,6 +243,34 @@ namespace Content.Server.GameTicking
Logger.InfoS("ticker", "Ending round!");
RunLevel = GameRunLevel.PostRound;
//Tell every client the round has ended.
var roundEndMessage = _netManager.CreateNetMessage<MsgRoundEndMessage>();
roundEndMessage.GamemodeTitle = MakeGamePreset().ModeTitle;
//Get the timespan of the round.
roundEndMessage.RoundDuration = IoCManager.Resolve<IGameTiming>().RealTime.Subtract(_roundStartTimeSpan);
//Generate a list of basic player info to display in the end round summary.
var listOfPlayerInfo = new List<RoundEndPlayerInfo>();
foreach(var ply in _playerManager.GetAllPlayers().OrderBy(p => p.Name))
{
if(ply.AttachedEntity.TryGetComponent<MindComponent>(out var mindComponent)
&& mindComponent.HasMind)
{
var playerEndRoundInfo = new RoundEndPlayerInfo()
{
PlayerOOCName = ply.Name,
PlayerICName = mindComponent.Mind.CurrentEntity.Name,
Role = mindComponent.Mind.AllRoles.FirstOrDefault()?.Name ?? Loc.GetString("Unkown"),
Antag = false
};
listOfPlayerInfo.Add(playerEndRoundInfo);
}
}
roundEndMessage.AllPlayersEndInfo = listOfPlayerInfo;
_netManager.ServerSendToAll(roundEndMessage);
}
public void Respawn(IPlayerSession targetPlayer)
@@ -273,7 +344,7 @@ namespace Content.Server.GameTicking
private IEntity _spawnPlayerMob(Job job, bool lateJoin = true)
{
GridCoordinates coordinates = lateJoin ? _getLateJoinSpawnPoint() : _getJobSpawnPoint(job.Prototype.ID);
GridCoordinates coordinates = lateJoin ? GetLateJoinSpawnPoint() : GetJobSpawnPoint(job.Prototype.ID);
var entity = _entityManager.SpawnEntity(PlayerPrototypeName, coordinates);
if (entity.TryGetComponent(out InventoryComponent inventory))
{
@@ -299,11 +370,11 @@ namespace Content.Server.GameTicking
private IEntity _spawnObserverMob()
{
GridCoordinates coordinates = _getLateJoinSpawnPoint();
var coordinates = GetObserverSpawnPoint();
return _entityManager.SpawnEntity(ObserverPrototypeName, coordinates);
}
private GridCoordinates _getLateJoinSpawnPoint()
public GridCoordinates GetLateJoinSpawnPoint()
{
var location = _spawnPoint;
@@ -319,7 +390,7 @@ namespace Content.Server.GameTicking
return location;
}
private GridCoordinates _getJobSpawnPoint(string jobId)
public GridCoordinates GetJobSpawnPoint(string jobId)
{
var location = _spawnPoint;
@@ -336,6 +407,23 @@ namespace Content.Server.GameTicking
return location;
}
public GridCoordinates GetObserverSpawnPoint()
{
var location = _spawnPoint;
var possiblePoints = new List<GridCoordinates>();
foreach (var entity in _entityManager.GetEntities(new TypeEntityQuery(typeof(SpawnPointComponent))))
{
var point = entity.GetComponent<SpawnPointComponent>();
if (point.SpawnType == SpawnPointType.Observer)
possiblePoints.Add(entity.Transform.GridPosition);
}
if (possiblePoints.Count != 0) location = _robustRandom.Pick(possiblePoints);
return location;
}
/// <summary>
/// Cleanup that has to run to clear up anything from the previous round.
/// Stuff like wiping the previous map clean.
@@ -389,6 +477,11 @@ namespace Content.Server.GameTicking
switch (args.NewStatus)
{
case SessionStatus.Connecting:
// Cancel shutdown update timer in progress.
_updateShutdownCts?.Cancel();
break;
case SessionStatus.Connected:
{
// Always make sure the client has player data. Mind gets assigned on spawn.
@@ -443,11 +536,43 @@ namespace Content.Server.GameTicking
if (_playersInLobby.ContainsKey(session)) _playersInLobby.Remove(session);
_chatManager.DispatchServerAnnouncement($"Player {args.Session.SessionId} left server!");
ServerEmptyUpdateRestartCheck();
break;
}
}
}
/// <summary>
/// Checks whether there are still players on the server,
/// and if not starts a timer to automatically reboot the server if an update is available.
/// </summary>
private void ServerEmptyUpdateRestartCheck()
{
// Can't simple check the current connected player count since that doesn't update
// before PlayerStatusChanged gets fired.
// So in the disconnect handler we'd still see a single player otherwise.
var playersOnline = _playerManager.GetAllPlayers().Any(p => p.Status != SessionStatus.Disconnected);
if (playersOnline || !_updateOnRoundEnd)
{
// Still somebody online.
return;
}
if (_updateShutdownCts != null && !_updateShutdownCts.IsCancellationRequested)
{
// Do nothing because I guess we already have a timer running..?
return;
}
_updateShutdownCts = new CancellationTokenSource();
Timer.Spawn(UpdateRestartDelay, () =>
{
_baseServer.Shutdown(
Loc.GetString("Server is shutting down for update and will automatically restart."));
}, _updateShutdownCts.Token);
}
private void SpawnPlayer(IPlayerSession session, string jobId = null, bool lateJoin = true)
{
var character = (HumanoidCharacterProfile) _prefsManager
@@ -458,7 +583,10 @@ namespace Content.Server.GameTicking
var data = session.ContentData();
data.WipeMind();
data.Mind = new Mind(session.SessionId);
data.Mind = new Mind(session.SessionId)
{
CharacterName = character.Name
};
if (jobId == null)
{
@@ -506,12 +634,18 @@ namespace Content.Server.GameTicking
private void _spawnObserver(IPlayerSession session)
{
var name = _prefsManager
.GetPreferences(session.SessionId.Username)
.SelectedCharacter.Name;
_playerJoinGame(session);
var data = session.ContentData();
data.WipeMind();
data.Mind = new Mind(session.SessionId);
var mob = _spawnObserverMob();
mob.Name = name;
mob.GetComponent<GhostComponent>().CanReturnToBody = false;
data.Mind.TransferTo(mob);
}
@@ -559,10 +693,12 @@ namespace Content.Server.GameTicking
private string GetInfoText()
{
var gameMode = MakeGamePreset().Description;
var gmTitle = MakeGamePreset().ModeTitle;
var desc = MakeGamePreset().Description;
return _localization.GetString(@"Hi and welcome to [color=white]Space Station 14![/color]
The current game mode is [color=white]{0}[/color]", gameMode);
The current game mode is: [color=white]{0}[/color].
[color=yellow]{1}[/color]", gmTitle, desc );
}
private void UpdateInfoText()
@@ -591,6 +727,8 @@ The current game mode is [color=white]{0}[/color]", gameMode);
[Dependency] private readonly ILocalizationManager _localization;
[Dependency] private readonly IRobustRandom _robustRandom;
[Dependency] private readonly IServerPreferencesManager _prefsManager;
[Dependency] private readonly IBaseServer _baseServer;
[Dependency] private readonly IWatchdogApi _watchdogApi;
#pragma warning restore 649
}

View File

@@ -1,6 +1,7 @@
using Content.Server.GameObjects;
using Content.Server.GameObjects.Components.Mobs;
using Content.Server.GameObjects.Components.Nutrition;
using Content.Server.GameObjects.Components.Observer;
using Content.Server.Players;
using Content.Shared.GameObjects;
using Robust.Server.Console;
@@ -41,9 +42,13 @@ namespace Content.Server.GlobalVerbs
{
var userMind = user.GetComponent<IActorComponent>().playerSession.ContentData().Mind;
var targetMind = target.GetComponent<MindComponent>();
var oldEntity = userMind.CurrentEntity;
targetMind.Mind?.TransferTo(null);
userMind.TransferTo(target);
if(oldEntity.HasComponent<GhostComponent>())
oldEntity.Delete();
}
}
}

View File

@@ -18,6 +18,7 @@ namespace Content.Server.Interfaces.Chat
void EntityMe(IEntity source, string action);
void SendOOC(IPlayerSession player, string message);
void SendDeadChat(IPlayerSession player, string message);
void SendHookOOC(string sender, string message);
}

View File

@@ -8,6 +8,6 @@ namespace Content.Shared.Interfaces
/// </summary>
public interface IReactionEffect : IExposeData
{
void React(IEntity solutionEntity, int intensity);
void React(IEntity solutionEntity, decimal intensity);
}
}

View File

@@ -0,0 +1,9 @@
using Robust.Server.Interfaces.Player;
namespace Content.Server.Interfaces.GameObjects.Components.Movement
{
public interface IRelayMoveInput
{
void MoveInputPressed(IPlayerSession session);
}
}

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using Content.Server.GameTicking;
using Robust.Server.Interfaces.Player;
using Robust.Shared.Map;
using Robust.Shared.Timing;
namespace Content.Server.Interfaces.GameTicking
@@ -27,6 +28,10 @@ namespace Content.Server.Interfaces.GameTicking
void MakeJoinGame(IPlayerSession player);
void ToggleReady(IPlayerSession player, bool ready);
GridCoordinates GetLateJoinSpawnPoint();
GridCoordinates GetJobSpawnPoint(string jobId);
GridCoordinates GetObserverSpawnPoint();
// GameRule system.
T AddGameRule<T>() where T : GameRule, new();
void RemoveGameRule(GameRule rule);

View File

@@ -88,21 +88,15 @@ namespace Content.Server
}
var password = _configurationManager.GetCVar<string>("status.mommipassword");
OOCPostMessage message;
using (var streamReader = new StreamReader(request.Body, EncodingHelpers.UTF8))
using (var jsonReader = new JsonTextReader(streamReader))
OOCPostMessage message = null;
try
{
var serializer = new JsonSerializer();
try
{
message = serializer.Deserialize<OOCPostMessage>(jsonReader);
}
catch (JsonSerializationException)
{
response.StatusCode = (int) HttpStatusCode.BadRequest;
return true;
}
message = request.GetFromJson<OOCPostMessage>();
}
catch (JsonSerializationException)
{
// message null so enters block down below.
}
if (message == null)

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using Content.Server.GameObjects.Components.Mobs;
using Content.Server.Players;
using Robust.Server.Interfaces.GameObjects;
using Robust.Server.Interfaces.Player;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
@@ -48,6 +49,9 @@ namespace Content.Server.Mobs
[ViewVariables] public IEntity CurrentEntity => VisitingEntity ?? OwnedEntity;
[ViewVariables(VVAccess.ReadWrite)]
public string CharacterName { get; set; }
/// <summary>
/// The component currently owned by this mind.
/// Can be null.
@@ -139,6 +143,7 @@ namespace Content.Server.Mobs
public void TransferTo(IEntity entity)
{
MindComponent component = null;
bool alreadyAttached = false;
if (entity != null)
{
if (!entity.TryGetComponent(out component))
@@ -150,6 +155,17 @@ namespace Content.Server.Mobs
// TODO: Kick them out, maybe?
throw new ArgumentException("That entity already has a mind.", nameof(entity));
}
if (entity.TryGetComponent(out IActorComponent actor))
{
// Happens when transferring to your currently visited entity.
if (actor.playerSession != Session)
{
throw new ArgumentException("Visit target already has a session.", nameof(entity));
}
alreadyAttached = true;
}
}
OwnedMob?.InternalEjectMind();
@@ -157,7 +173,7 @@ namespace Content.Server.Mobs
OwnedMob?.InternalAssignMind(this);
// Player is CURRENTLY connected.
if (Session != null && OwnedMob != null)
if (Session != null && OwnedMob != null && !alreadyAttached)
{
Session.AttachToEntity(entity);
}

View File

@@ -0,0 +1,77 @@
using Content.Server.GameObjects;
using Content.Server.GameObjects.Components.Observer;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces.GameTicking;
using Content.Server.Players;
using Content.Shared.GameObjects;
using Robust.Server.Interfaces.Console;
using Robust.Server.Interfaces.Player;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Timers;
namespace Content.Server.Observer
{
public class Ghost : IClientCommand
{
public string Command => "ghost";
public string Description => "Give up on life and become a ghost.";
public string Help => "ghost";
public void Execute(IConsoleShell shell, IPlayerSession player, string[] args)
{
if (player == null)
{
shell.SendText((IPlayerSession) null, "Nah");
return;
}
var mind = player.ContentData().Mind;
var canReturn = player.AttachedEntity != null;
var name = player.AttachedEntity?.Name ?? player.Name;
if (player.AttachedEntity != null && player.AttachedEntity.HasComponent<GhostComponent>())
return;
if (mind.VisitingEntity != null)
{
mind.UnVisit();
mind.VisitingEntity.Delete();
}
var position = player.AttachedEntity?.Transform.GridPosition ?? IoCManager.Resolve<IGameTicker>().GetObserverSpawnPoint();
if (canReturn && player.AttachedEntity.TryGetComponent(out SpeciesComponent species))
{
switch (species.CurrentDamageState)
{
case DeadState _:
canReturn = true;
break;
case CriticalState _:
canReturn = true;
if (!player.AttachedEntity.TryGetComponent(out DamageableComponent damageable)) break;
damageable.TakeDamage(DamageType.Total, 100); // TODO: Use airloss/oxyloss instead
break;
default:
canReturn = false;
break;
}
}
var entityManager = IoCManager.Resolve<IEntityManager>();
var ghost = entityManager.SpawnEntity("MobObserver", position);
ghost.Name = mind.CharacterName;
var ghostComponent = ghost.GetComponent<GhostComponent>();
ghostComponent.CanReturnToBody = canReturn;
if(canReturn)
mind.Visit(ghost);
else
mind.TransferTo(ghost);
}
}
}

View File

@@ -1,4 +1,4 @@
using Content.Server.Cargo;
using Content.Server.Cargo;
using Content.Server.Chat;
using Content.Server.GameTicking;
using Content.Server.Interfaces;
@@ -7,7 +7,9 @@ using Content.Server.Interfaces.GameTicking;
using Content.Server.Preferences;
using Content.Server.Sandbox;
using Content.Server.Utility;
using Content.Shared.Chemistry;
using Content.Shared.Interfaces;
using Content.Shared.Interfaces.Chemistry;
using Robust.Shared.IoC;
namespace Content.Server

View File

@@ -6,7 +6,7 @@ namespace Content.Shared.Chat
/// Represents chat channels that the player can filter chat tabs by.
/// </summary>
[Flags]
public enum ChatChannel : byte
public enum ChatChannel : short
{
None = 0,
@@ -46,9 +46,14 @@ namespace Content.Shared.Chat
/// </summary>
Emotes = 64,
/// <summary>
/// Deadchat
/// </summary>
Dead = 128,
/// <summary>
/// Unspecified.
/// </summary>
Unspecified = 128,
Unspecified = 256,
}
}

View File

@@ -35,7 +35,7 @@ namespace Content.Shared.Chat
/// <summary>
/// The sending entity.
/// Only applies to <see cref="ChatChannel.Local"/> and <see cref="ChatChannel.Emotes"/>.
/// Only applies to <see cref="ChatChannel.Local"/>, <see cref="ChatChannel.Dead"/> and <see cref="ChatChannel.Emotes"/>.
/// </summary>
public EntityUid SenderEntity { get; set; }
@@ -48,6 +48,7 @@ namespace Content.Shared.Chat
switch (Channel)
{
case ChatChannel.Local:
case ChatChannel.Dead:
case ChatChannel.Emotes:
SenderEntity = buffer.ReadEntityUid();
break;
@@ -63,6 +64,7 @@ namespace Content.Shared.Chat
switch (Channel)
{
case ChatChannel.Local:
case ChatChannel.Dead:
case ChatChannel.Emotes:
buffer.Write(SenderEntity);
break;

View File

@@ -1,7 +1,7 @@
using System;
using Content.Shared.Interfaces.Chemistry;
using Content.Shared.Interfaces.Chemistry;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.IoC;
using Robust.Shared.Serialization;
namespace Content.Shared.Chemistry
@@ -10,18 +10,17 @@ namespace Content.Shared.Chemistry
class DefaultMetabolizable : IMetabolizable
{
//Rate of metabolism in units / second
private int _metabolismRate = 1;
public int MetabolismRate => _metabolismRate;
private decimal _metabolismRate = 1;
public decimal MetabolismRate => _metabolismRate;
void IExposeData.ExposeData(ObjectSerializer serializer)
{
serializer.DataField(ref _metabolismRate, "rate", 1);
}
int IMetabolizable.Metabolize(IEntity solutionEntity, string reagentId, float tickTime)
ReagentUnit IMetabolizable.Metabolize(IEntity solutionEntity, string reagentId, float tickTime)
{
int metabolismAmount = (int)Math.Round(MetabolismRate * tickTime);
return metabolismAmount;
return ReagentUnit.New(MetabolismRate * (decimal)tickTime);
}
}
}

View File

@@ -21,6 +21,7 @@ namespace Content.Shared.Chemistry
private string _description;
private Color _substanceColor;
private List<IMetabolizable> _metabolism;
private string _spritePath;
public string ID => _id;
public string Name => _name;
@@ -29,6 +30,8 @@ namespace Content.Shared.Chemistry
//List of metabolism effects this reagent has, should really only be used server-side.
public List<IMetabolizable> Metabolism => _metabolism;
public string SpriteReplacementPath => _spritePath;
public ReagentPrototype()
{
IoCManager.InjectDependencies(this);
@@ -42,6 +45,7 @@ namespace Content.Shared.Chemistry
serializer.DataField(ref _name, "name", string.Empty);
serializer.DataField(ref _description, "desc", string.Empty);
serializer.DataField(ref _substanceColor, "color", Color.White);
serializer.DataField(ref _spritePath, "spritePath", string.Empty);
if (_moduleManager.IsServerModule)
serializer.DataField(ref _metabolism, "metabolism", new List<IMetabolizable> {new DefaultMetabolizable()});

View File

@@ -0,0 +1,225 @@
using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.Serialization;
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
namespace Content.Shared.Chemistry
{
[Serializable]
public struct ReagentUnit : ISelfSerialize, IComparable<ReagentUnit>, IEquatable<ReagentUnit>
{
private int _value;
private static readonly int Shift = 2;
public static ReagentUnit MaxValue => new ReagentUnit(int.MaxValue);
private double ShiftDown()
{
return _value / Math.Pow(10, Shift);
}
private ReagentUnit(int value)
{
_value = value;
}
public static ReagentUnit New(int value)
{
return new ReagentUnit(value * (int) Math.Pow(10, Shift));
}
public static ReagentUnit New(decimal value)
{
return new ReagentUnit((int) Math.Round(value * (decimal) Math.Pow(10, Shift), MidpointRounding.AwayFromZero));
}
public static ReagentUnit New(float value)
{
return new ReagentUnit(FromFloat(value));
}
private static int FromFloat(float value)
{
return (int) Math.Round(value * (float) Math.Pow(10, Shift), MidpointRounding.AwayFromZero);
}
public static ReagentUnit New(double value)
{
return new ReagentUnit((int) Math.Round(value * Math.Pow(10, Shift), MidpointRounding.AwayFromZero));
}
public static ReagentUnit New(string value)
{
return New(FloatFromString(value));
}
private static float FloatFromString(string value)
{
return float.Parse(value, CultureInfo.InvariantCulture);
}
public static ReagentUnit operator +(ReagentUnit a) => a;
public static ReagentUnit operator -(ReagentUnit a) => new ReagentUnit(-a._value);
public static ReagentUnit operator +(ReagentUnit a, ReagentUnit b)
=> new ReagentUnit(a._value + b._value);
public static ReagentUnit operator -(ReagentUnit a, ReagentUnit b)
=> a + -b;
public static ReagentUnit operator *(ReagentUnit a, ReagentUnit b)
{
var aD = a.ShiftDown();
var bD = b.ShiftDown();
return New(aD * bD);
}
public static ReagentUnit operator *(ReagentUnit a, float b)
{
var aD = (float) a.ShiftDown();
return New(aD * b);
}
public static ReagentUnit operator *(ReagentUnit a, decimal b)
{
var aD = (decimal) a.ShiftDown();
return New(aD * b);
}
public static ReagentUnit operator *(ReagentUnit a, double b)
{
var aD = a.ShiftDown();
return New(aD * b);
}
public static ReagentUnit operator *(ReagentUnit a, int b)
{
return new ReagentUnit(a._value * b);
}
public static ReagentUnit operator /(ReagentUnit a, ReagentUnit b)
{
if (b._value == 0)
{
throw new DivideByZeroException();
}
var aD = a.ShiftDown();
var bD = b.ShiftDown();
return New(aD / bD);
}
public static bool operator <=(ReagentUnit a, int b)
{
return a.ShiftDown() <= b;
}
public static bool operator >=(ReagentUnit a, int b)
{
return a.ShiftDown() >= b;
}
public static bool operator ==(ReagentUnit a, int b)
{
return a.ShiftDown() == b;
}
public static bool operator !=(ReagentUnit a, int b)
{
return a.ShiftDown() != b;
}
public static bool operator <=(ReagentUnit a, ReagentUnit b)
{
return a._value <= b._value;
}
public static bool operator >=(ReagentUnit a, ReagentUnit b)
{
return a._value >= b._value;
}
public static bool operator <(ReagentUnit a, ReagentUnit b)
{
return a._value < b._value;
}
public static bool operator >(ReagentUnit a, ReagentUnit b)
{
return a._value > b._value;
}
public float Float()
{
return (float) ShiftDown();
}
public decimal Decimal()
{
return (decimal) ShiftDown();
}
public double Double()
{
return ShiftDown();
}
public int Int()
{
return (int) ShiftDown();
}
public static ReagentUnit Min(params ReagentUnit[] reagentUnits)
{
return reagentUnits.Min();
}
public static ReagentUnit Min(ReagentUnit a, ReagentUnit b)
{
return a < b ? a : b;
}
public override bool Equals(object obj)
{
return obj is ReagentUnit unit &&
_value == unit._value;
}
public override int GetHashCode()
{
return HashCode.Combine(_value);
}
public void Deserialize(string value)
{
_value = FromFloat(FloatFromString(value));
}
public override string ToString() => $"{ShiftDown().ToString(CultureInfo.InvariantCulture)}";
public string Serialize()
{
return ToString();
}
public bool Equals([AllowNull] ReagentUnit other)
{
return _value == other._value;
}
public int CompareTo([AllowNull] ReagentUnit other)
{
if(other._value > _value)
{
return -1;
}
if(other._value < _value)
{
return 1;
}
return 0;
}
}
}

View File

@@ -1,11 +1,13 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Content.Shared.Interfaces.Chemistry;
using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.IoC;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace Content.Shared.Chemistry
{
@@ -23,7 +25,7 @@ namespace Content.Shared.Chemistry
/// The calculated total volume of all reagents in the solution (ex. Total volume of liquid in beaker).
/// </summary>
[ViewVariables]
public int TotalVolume { get; private set; }
public ReagentUnit TotalVolume { get; private set; }
/// <summary>
/// Constructs an empty solution (ex. an empty beaker).
@@ -35,7 +37,7 @@ namespace Content.Shared.Chemistry
/// </summary>
/// <param name="reagentId">The prototype ID of the reagent to add.</param>
/// <param name="quantity">The quantity in milli-units.</param>
public Solution(string reagentId, int quantity)
public Solution(string reagentId, ReagentUnit quantity)
{
AddReagent(reagentId, quantity);
}
@@ -47,7 +49,7 @@ namespace Content.Shared.Chemistry
if (serializer.Reading)
{
TotalVolume = 0;
TotalVolume = ReagentUnit.New(0);
foreach (var reagent in _contents)
{
TotalVolume += reagent.Quantity;
@@ -60,9 +62,9 @@ namespace Content.Shared.Chemistry
/// </summary>
/// <param name="reagentId">The prototype ID of the reagent to add.</param>
/// <param name="quantity">The quantity in milli-units.</param>
public void AddReagent(string reagentId, int quantity)
public void AddReagent(string reagentId, ReagentUnit quantity)
{
if(quantity <= 0)
if (quantity <= 0)
return;
for (var i = 0; i < _contents.Count; i++)
@@ -85,7 +87,7 @@ namespace Content.Shared.Chemistry
/// </summary>
/// <param name="reagentId">The prototype ID of the reagent to add.</param>
/// <returns>The quantity in milli-units.</returns>
public int GetReagentQuantity(string reagentId)
public ReagentUnit GetReagentQuantity(string reagentId)
{
for (var i = 0; i < _contents.Count; i++)
{
@@ -93,10 +95,10 @@ namespace Content.Shared.Chemistry
return _contents[i].Quantity;
}
return 0;
return ReagentUnit.New(0);
}
public void RemoveReagent(string reagentId, int quantity)
public void RemoveReagent(string reagentId, ReagentUnit quantity)
{
if(quantity <= 0)
return;
@@ -129,12 +131,12 @@ namespace Content.Shared.Chemistry
/// Remove the specified quantity from this solution.
/// </summary>
/// <param name="quantity">The quantity of this solution to remove</param>
public void RemoveSolution(int quantity)
public void RemoveSolution(ReagentUnit quantity)
{
if(quantity <= 0)
return;
var ratio = (float)(TotalVolume - quantity) / TotalVolume;
var ratio = (TotalVolume - quantity).Decimal() / TotalVolume.Decimal();
if (ratio <= 0)
{
@@ -149,21 +151,21 @@ namespace Content.Shared.Chemistry
// quantity taken is always a little greedy, so fractional quantities get rounded up to the nearest
// whole unit. This should prevent little bits of chemical remaining because of float rounding errors.
var newQuantity = (int)Math.Floor(oldQuantity * ratio);
var newQuantity = oldQuantity * ratio;
_contents[i] = new ReagentQuantity(reagent.ReagentId, newQuantity);
}
TotalVolume = (int)Math.Floor(TotalVolume * ratio);
TotalVolume = TotalVolume * ratio;
}
public void RemoveAllSolution()
{
_contents.Clear();
TotalVolume = 0;
TotalVolume = ReagentUnit.New(0);
}
public Solution SplitSolution(int quantity)
public Solution SplitSolution(ReagentUnit quantity)
{
if (quantity <= 0)
return new Solution();
@@ -178,23 +180,26 @@ namespace Content.Shared.Chemistry
}
newSolution = new Solution();
var newTotalVolume = 0;
var ratio = (float)(TotalVolume - quantity) / TotalVolume;
var newTotalVolume = ReagentUnit.New(0M);
var remainingVolume = TotalVolume;
for (var i = 0; i < _contents.Count; i++)
{
var reagent = _contents[i];
var ratio = (remainingVolume - quantity).Decimal() / remainingVolume.Decimal();
remainingVolume -= reagent.Quantity;
var newQuantity = (int)Math.Floor(reagent.Quantity * ratio);
var newQuantity = reagent.Quantity * ratio;
var splitQuantity = reagent.Quantity - newQuantity;
_contents[i] = new ReagentQuantity(reagent.ReagentId, newQuantity);
newSolution._contents.Add(new ReagentQuantity(reagent.ReagentId, splitQuantity));
newTotalVolume += splitQuantity;
quantity -= splitQuantity;
}
TotalVolume = (int)Math.Floor(TotalVolume * ratio);
newSolution.TotalVolume = newTotalVolume;
TotalVolume -= newTotalVolume;
return newSolution;
}
@@ -228,7 +233,7 @@ namespace Content.Shared.Chemistry
public Solution Clone()
{
var volume = 0;
var volume = ReagentUnit.New(0);
var newSolution = new Solution();
for (var i = 0; i < _contents.Count; i++)
@@ -246,9 +251,9 @@ namespace Content.Shared.Chemistry
public readonly struct ReagentQuantity
{
public readonly string ReagentId;
public readonly int Quantity;
public readonly ReagentUnit Quantity;
public ReagentQuantity(string reagentId, int quantity)
public ReagentQuantity(string reagentId, ReagentUnit quantity)
{
ReagentId = reagentId;
Quantity = quantity;

View File

@@ -24,6 +24,8 @@ namespace Content.Shared.Chemistry
/// <para>Allows us to have obscenely large containers that are harder to abuse in chem dispensers
/// since they can't be placed directly in them.</para>
/// </summary>
FitsInDispenser = 16,
FitsInDispenser = 16,
NoExamine = 32,
}
}

View File

@@ -1,4 +1,5 @@
using System;
using Content.Shared.Chemistry;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
@@ -18,11 +19,11 @@ namespace Content.Shared.GameObjects.Components.Chemistry
[Serializable, NetSerializable]
protected sealed class InjectorComponentState : ComponentState
{
public int CurrentVolume { get; }
public int TotalVolume { get; }
public ReagentUnit CurrentVolume { get; }
public ReagentUnit TotalVolume { get; }
public InjectorToggleMode CurrentMode { get; }
public InjectorComponentState(int currentVolume, int totalVolume, InjectorToggleMode currentMode) : base(ContentNetIDs.REAGENT_INJECTOR)
public InjectorComponentState(ReagentUnit currentVolume, ReagentUnit totalVolume, InjectorToggleMode currentMode) : base(ContentNetIDs.REAGENT_INJECTOR)
{
CurrentVolume = currentVolume;
TotalVolume = totalVolume;

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