diff --git a/.editorconfig b/.editorconfig index b683dad667..993abbe794 100644 --- a/.editorconfig +++ b/.editorconfig @@ -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 diff --git a/Content.Client/Chat/ChatManager.cs b/Content.Client/Chat/ChatManager.cs index a2a3e04ae7..8a0fcdeabd 100644 --- a/Content.Client/Chat/ChatManager.cs +++ b/Content.Client/Chat/ChatManager.cs @@ -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); } diff --git a/Content.Client/ClientContentIoC.cs b/Content.Client/ClientContentIoC.cs index 6a8e9ea10a..690de5c18a 100644 --- a/Content.Client/ClientContentIoC.cs +++ b/Content.Client/ClientContentIoC.cs @@ -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(); IoCManager.Register(); IoCManager.Register(); + IoCManager.Register(); } } } diff --git a/Content.Client/Command/CommunicationsConsoleMenu.cs b/Content.Client/Command/CommunicationsConsoleMenu.cs new file mode 100644 index 0000000000..0a6804264c --- /dev/null +++ b/Content.Client/Command/CommunicationsConsoleMenu.cs @@ -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(); + } + } +} diff --git a/Content.Client/EntryPoint.cs b/Content.Client/EntryPoint.cs index 8eedfcc852..7e5dd43715 100644 --- a/Content.Client/EntryPoint.cs +++ b/Content.Client/EntryPoint.cs @@ -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(); factory.Register(); - factory.Register(); + factory.Register(); factory.Register(); factory.Register(); @@ -167,10 +172,7 @@ namespace Content.Client IoCManager.Resolve().LoadParallax(); IoCManager.Resolve().PlayerJoinedServer += SubscribePlayerAttachmentEvents; - - var stylesheet = new NanoStyle(); - - IoCManager.Resolve().Stylesheet = stylesheet.Stylesheet; + IoCManager.Resolve().Initialize(); IoCManager.InjectDependencies(this); @@ -225,6 +227,37 @@ namespace Content.Client IoCManager.Resolve().Initialize(); IoCManager.Resolve().Initialize(); IoCManager.Resolve().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(); + var state = (LauncherConnecting) _stateManager.CurrentState; + + if (disconnected) + { + state.SetDisconnected(); + } + } + else + { + _stateManager.RequestStateChange(); + } } public override void Update(ModUpdateLevel level, FrameEventArgs frameEventArgs) diff --git a/Content.Client/EscapeMenuOwner.cs b/Content.Client/EscapeMenuOwner.cs index 519d6e0692..5cbd33c1d8 100644 --- a/Content.Client/EscapeMenuOwner.cs +++ b/Content.Client/EscapeMenuOwner.cs @@ -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; diff --git a/Content.Client/GameObjects/Components/Actor/CharacterInfoComponent.cs b/Content.Client/GameObjects/Components/Actor/CharacterInfoComponent.cs index 18b939d90b..dfb7e3c796 100644 --- a/Content.Client/GameObjects/Components/Actor/CharacterInfoComponent.cs +++ b/Content.Client/GameObjects/Components/Actor/CharacterInfoComponent.cs @@ -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} }) } } diff --git a/Content.Client/GameObjects/Components/Chemistry/InjectorComponent.cs b/Content.Client/GameObjects/Components/Chemistry/InjectorComponent.cs index ab1bb415d1..359dbeb282 100644 --- a/Content.Client/GameObjects/Components/Chemistry/InjectorComponent.cs +++ b/Content.Client/GameObjects/Components/Chemistry/InjectorComponent.cs @@ -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; diff --git a/Content.Client/GameObjects/Components/Chemistry/ReagentDispenserWindow.cs b/Content.Client/GameObjects/Components/Chemistry/ReagentDispenserWindow.cs index d35b18346c..997b487560 100644 --- a/Content.Client/GameObjects/Components/Chemistry/ReagentDispenserWindow.cs +++ b/Content.Client/GameObjects/Components/Chemistry/ReagentDispenserWindow.cs @@ -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} } } }); diff --git a/Content.Client/GameObjects/Components/Command/CommunicationsConsoleBoundUserInterface.cs b/Content.Client/GameObjects/Components/Command/CommunicationsConsoleBoundUserInterface.cs new file mode 100644 index 0000000000..c82ded4c7a --- /dev/null +++ b/Content.Client/GameObjects/Components/Command/CommunicationsConsoleBoundUserInterface.cs @@ -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(); + } + } +} diff --git a/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs b/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs index 29511ceb4d..80fc60360c 100644 --- a/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs +++ b/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs @@ -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; + } + } } /// @@ -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. /// [ViewVariables] - public bool IsMidiOpen => _renderer.Status == MidiRendererStatus.File; + public bool IsMidiOpen => _renderer?.Status == MidiRendererStatus.File; /// /// Whether the midi renderer is listening for midi input or not. /// [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 /// 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 /// public bool CloseInput() { - if (!_renderer.CloseInput()) return false; + if (_renderer == null || !_renderer.CloseInput()) + { + return false; + } + _renderer.OnMidiEvent -= RendererOnMidiEvent; return true; - } /// public bool OpenMidi(string filename) { - if (!_renderer.OpenMidi(filename)) return false; + if (_renderer == null || !_renderer.OpenMidi(filename)) + { + return false; + } + _renderer.OnMidiEvent += RendererOnMidiEvent; return true; - } /// public bool CloseMidi() { - if (!_renderer.CloseMidi()) return false; + if (_renderer == null || !_renderer.CloseMidi()) + { + return false; + } + _renderer.OnMidiEvent -= RendererOnMidiEvent; return true; - } /// diff --git a/Content.Client/GameObjects/Components/MagicMirrorBoundUserInterface.cs b/Content.Client/GameObjects/Components/MagicMirrorBoundUserInterface.cs index 4c9245b9b3..d6fcca29fd 100644 --- a/Content.Client/GameObjects/Components/MagicMirrorBoundUserInterface.cs +++ b/Content.Client/GameObjects/Components/MagicMirrorBoundUserInterface.cs @@ -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; diff --git a/Content.Client/GameObjects/Components/Mobs/ClientStatusEffectsComponent.cs b/Content.Client/GameObjects/Components/Mobs/ClientStatusEffectsComponent.cs index 0e915772a4..e3edf3d34b 100644 --- a/Content.Client/GameObjects/Components/Mobs/ClientStatusEffectsComponent.cs +++ b/Content.Client/GameObjects/Components/Mobs/ClientStatusEffectsComponent.cs @@ -76,10 +76,6 @@ namespace Content.Client.GameObjects.Components.Mobs private void PlayerDetached() { - if (!CurrentlyControlled) - { - return; - } _ui?.Dispose(); _ui = null; } diff --git a/Content.Client/GameObjects/Components/Observer/GhostComponent.cs b/Content.Client/GameObjects/Components/Observer/GhostComponent.cs new file mode 100644 index 0000000000..c71e04a56b --- /dev/null +++ b/Content.Client/GameObjects/Components/Observer/GhostComponent.cs @@ -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() ?? 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(); + } + + } + } +} diff --git a/Content.Client/GameObjects/Components/Power/ApcBoundUserInterface.cs b/Content.Client/GameObjects/Components/Power/ApcBoundUserInterface.cs index 0abeb69183..0d89ecfddb 100644 --- a/Content.Client/GameObjects/Components/Power/ApcBoundUserInterface.cs +++ b/Content.Client/GameObjects/Components/Power/ApcBoundUserInterface.cs @@ -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); diff --git a/Content.Client/GameObjects/Components/Research/LatheBoundUserInterface.cs b/Content.Client/GameObjects/Components/Research/LatheBoundUserInterface.cs index 3273cb79f3..9e318f04a6 100644 --- a/Content.Client/GameObjects/Components/Research/LatheBoundUserInterface.cs +++ b/Content.Client/GameObjects/Components/Research/LatheBoundUserInterface.cs @@ -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(); } } } diff --git a/Content.Client/GameObjects/Components/StackComponent.cs b/Content.Client/GameObjects/Components/StackComponent.cs index 717372091a..af5dfe3e8d 100644 --- a/Content.Client/GameObjects/Components/StackComponent.cs +++ b/Content.Client/GameObjects/Components/StackComponent.cs @@ -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; diff --git a/Content.Client/GameObjects/Components/Weapons/Ranged/BallisticMagazineWeaponComponent.cs b/Content.Client/GameObjects/Components/Weapons/Ranged/BallisticMagazineWeaponComponent.cs index 1917dbcd65..d800e7cb5e 100644 --- a/Content.Client/GameObjects/Components/Weapons/Ranged/BallisticMagazineWeaponComponent.cs +++ b/Content.Client/GameObjects/Components/Weapons/Ranged/BallisticMagazineWeaponComponent.cs @@ -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} }) } }, diff --git a/Content.Client/GameObjects/Components/WelderComponent.cs b/Content.Client/GameObjects/Components/WelderComponent.cs index dc7387b559..e5a64d5ccb 100644 --- a/Content.Client/GameObjects/Components/WelderComponent.cs +++ b/Content.Client/GameObjects/Components/WelderComponent.cs @@ -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; diff --git a/Content.Client/GameTicking/ClientGameTicker.cs b/Content.Client/GameTicking/ClientGameTicker.cs index 77ca609371..1d3d278a67 100644 --- a/Content.Client/GameTicking/ClientGameTicker.cs +++ b/Content.Client/GameTicking/ClientGameTicker.cs @@ -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(nameof(MsgTickerJoinGame), JoinGame); _netManager.RegisterNetMessage(nameof(MsgTickerLobbyStatus), LobbyStatus); _netManager.RegisterNetMessage(nameof(MsgTickerLobbyInfo), LobbyInfo); + _netManager.RegisterNetMessage(nameof(MsgRoundEndMessage), RoundEnd); _initialized = true; } + + private void JoinLobby(MsgTickerJoinLobby message) { _stateManager.RequestStateChange(); @@ -64,5 +68,13 @@ namespace Content.Client.GameTicking { _stateManager.RequestStateChange(); } + + 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); + + } } } diff --git a/Content.Client/Instruments/InstrumentMenu.cs b/Content.Client/Instruments/InstrumentMenu.cs index b0880fbb48..171263e811 100644 --- a/Content.Client/Instruments/InstrumentMenu.cs +++ b/Content.Client/Instruments/InstrumentMenu.cs @@ -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; } diff --git a/Content.Client/State/GameScreenBase.cs b/Content.Client/State/GameScreenBase.cs index 4cb8896e26..3e277799dd 100644 --- a/Content.Client/State/GameScreenBase.cs +++ b/Content.Client/State/GameScreenBase.cs @@ -43,8 +43,6 @@ namespace Content.Client.State public override void Shutdown() { - _playerManager.LocalPlayer.DetachEntity(); - _inputManager.KeyBindStateChanged -= OnKeyBindStateChanged; } diff --git a/Content.Client/State/LauncherConnecting.cs b/Content.Client/State/LauncherConnecting.cs new file mode 100644 index 0000000000..3a31b1237a --- /dev/null +++ b/Content.Client/State/LauncherConnecting.cs @@ -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, + } + } +} diff --git a/Content.Client/State/MainMenu.cs b/Content.Client/State/MainMenu.cs new file mode 100644 index 0000000000..1157e9907b --- /dev/null +++ b/Content.Client/State/MainMenu.cs @@ -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 +{ + /// + /// Main menu screen that is the first screen to be displayed when the game starts. + /// + // 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+))?"); + + /// + 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); + } + + /// + 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("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("/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("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); + } + } + } +} diff --git a/Content.Client/UserInterface/Cargo/CargoConsoleMenu.cs b/Content.Client/UserInterface/Cargo/CargoConsoleMenu.cs index 30b8238d20..9de68993e6 100644 --- a/Content.Client/UserInterface/Cargo/CargoConsoleMenu.cs +++ b/Content.Client/UserInterface/Cargo/CargoConsoleMenu.cs @@ -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); diff --git a/Content.Client/UserInterface/CharacterSetupGui.cs b/Content.Client/UserInterface/CharacterSetupGui.cs index 3f4291deeb..be82a1668d 100644 --- a/Content.Client/UserInterface/CharacterSetupGui.cs +++ b/Content.Client/UserInterface/CharacterSetupGui.cs @@ -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); diff --git a/Content.Client/UserInterface/Controls/HighDivider.cs b/Content.Client/UserInterface/Controls/HighDivider.cs new file mode 100644 index 0000000000..29bdaabaee --- /dev/null +++ b/Content.Client/UserInterface/Controls/HighDivider.cs @@ -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}}); + } + } +} diff --git a/Content.Client/UserInterface/GhostGui.cs b/Content.Client/UserInterface/GhostGui.cs new file mode 100644 index 0000000000..959b3beff6 --- /dev/null +++ b/Content.Client/UserInterface/GhostGui.cs @@ -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; + } + } +} diff --git a/Content.Client/UserInterface/ItemStatusPanel.cs b/Content.Client/UserInterface/ItemStatusPanel.cs index e53a1cd522..fe94573a17 100644 --- a/Content.Client/UserInterface/ItemStatusPanel.cs +++ b/Content.Client/UserInterface/ItemStatusPanel.cs @@ -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} }) } } diff --git a/Content.Client/UserInterface/LobbyGui.cs b/Content.Client/UserInterface/LobbyGui.cs index 29624b8ced..4904eb6322 100644 --- a/Content.Client/UserInterface/LobbyGui.cs +++ b/Content.Client/UserInterface/LobbyGui.cs @@ -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) }); { diff --git a/Content.Client/UserInterface/NanoHeading.cs b/Content.Client/UserInterface/NanoHeading.cs index 30dde27fc5..d0ac77e9e7 100644 --- a/Content.Client/UserInterface/NanoHeading.cs +++ b/Content.Client/UserInterface/NanoHeading.cs @@ -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); diff --git a/Content.Client/UserInterface/RoundEndSummaryWindow.cs b/Content.Client/UserInterface/RoundEndSummaryWindow.cs new file mode 100644 index 0000000000..5e8600b188 --- /dev/null +++ b/Content.Client/UserInterface/RoundEndSummaryWindow.cs @@ -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 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(); + + } + + } + +} diff --git a/Content.Client/UserInterface/Stylesheets/IStylesheetManager.cs b/Content.Client/UserInterface/Stylesheets/IStylesheetManager.cs new file mode 100644 index 0000000000..2082ffb216 --- /dev/null +++ b/Content.Client/UserInterface/Stylesheets/IStylesheetManager.cs @@ -0,0 +1,12 @@ +using Robust.Client.UserInterface; + +namespace Content.Client.UserInterface.Stylesheets +{ + public interface IStylesheetManager + { + Stylesheet SheetNano { get; } + Stylesheet SheetSpace { get; } + + void Initialize(); + } +} diff --git a/Content.Client/UserInterface/Stylesheets/StyleBase.cs b/Content.Client/UserInterface/Stylesheets/StyleBase.cs new file mode 100644 index 0000000000..10562fad2f --- /dev/null +++ b/Content.Client/UserInterface/Stylesheets/StyleBase.cs @@ -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), + }), + }; + } + } +} diff --git a/Content.Client/UserInterface/NanoStyle.cs b/Content.Client/UserInterface/Stylesheets/StyleNano.cs similarity index 96% rename from Content.Client/UserInterface/NanoStyle.cs rename to Content.Client/UserInterface/Stylesheets/StyleNano.cs index 97cdd91310..e182012a55 100644 --- a/Content.Client/UserInterface/NanoStyle.cs +++ b/Content.Client/UserInterface/Stylesheets/StyleNano.cs @@ -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(); - 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()); } } } diff --git a/Content.Client/UserInterface/Stylesheets/StyleSpace.cs b/Content.Client/UserInterface/Stylesheets/StyleSpace.cs new file mode 100644 index 0000000000..9e4b318c6c --- /dev/null +++ b/Content.Client/UserInterface/Stylesheets/StyleSpace.cs @@ -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()); + } + } +} diff --git a/Content.Client/UserInterface/Stylesheets/StylesheetManager.cs b/Content.Client/UserInterface/Stylesheets/StylesheetManager.cs new file mode 100644 index 0000000000..49147d841e --- /dev/null +++ b/Content.Client/UserInterface/Stylesheets/StylesheetManager.cs @@ -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; + } + } +} diff --git a/Content.IntegrationTests/DummyGameTicker.cs b/Content.IntegrationTests/DummyGameTicker.cs index 5732e413ea..33bc84ac24 100644 --- a/Content.IntegrationTests/DummyGameTicker.cs +++ b/Content.IntegrationTests/DummyGameTicker.cs @@ -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() where T : GameRule, new() { return new T(); diff --git a/Content.IntegrationTests/Tests/EntityTest.cs b/Content.IntegrationTests/Tests/EntityTest.cs new file mode 100644 index 0000000000..be5f5c83c5 --- /dev/null +++ b/Content.IntegrationTests/Tests/EntityTest.cs @@ -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(); + var entityMan = server.ResolveDependency(); + var prototypeMan = server.ResolveDependency(); + var mapLoader = server.ResolveDependency(); + var pauseMan = server.ResolveDependency(); + var prototypes = new List(); + 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()) + { + 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(); + } + + } +} diff --git a/Content.Server/Administration/AGhost.cs b/Content.Server/Administration/AGhost.cs index 57d5882480..3eff686aef 100644 --- a/Content.Server/Administration/AGhost.cs +++ b/Content.Server/Administration/AGhost.cs @@ -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(); var entityManager = IoCManager.Resolve(); var ghost = entityManager.SpawnEntity("AdminObserver", player.AttachedEntity.Transform.GridPosition); - - mind.Visit(ghost); + if(canReturn) + mind.Visit(ghost); + else + mind.TransferTo(ghost); + ghost.GetComponent().CanReturnToBody = canReturn; } } } diff --git a/Content.Server/Administration/ControlMob.cs b/Content.Server/Administration/ControlMob.cs index 4a72b5dd89..8b08f3d99e 100644 --- a/Content.Server/Administration/ControlMob.cs +++ b/Content.Server/Administration/ControlMob.cs @@ -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()) + oldEntity.Delete(); + } } } diff --git a/Content.Server/Chat/ChatCommands.cs b/Content.Server/Chat/ChatCommands.cs index 04fdaeef94..00ca6a4124 100644 --- a/Content.Server/Chat/ChatCommands.cs +++ b/Content.Server/Chat/ChatCommands.cs @@ -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()) + chat.SendDeadChat(player, message); + else + chat.EntitySay(player.AttachedEntity, message); } } diff --git a/Content.Server/Chat/ChatManager.cs b/Content.Server/Chat/ChatManager.cs index 218fc31dcd..f4f3df49e0 100644 --- a/Content.Server/Chat/ChatManager.cs +++ b/Content.Server/Chat/ChatManager.cs @@ -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()).Select(p => p.ConnectedClient);; + + var msg = _netManager.CreateNetMessage(); + 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(); diff --git a/Content.Server/Chemistry/Metabolism/DefaultDrink.cs b/Content.Server/Chemistry/Metabolism/DefaultDrink.cs index e43a1d7dd3..232ea1e108 100644 --- a/Content.Server/Chemistry/Metabolism/DefaultDrink.cs +++ b/Content.Server/Chemistry/Metabolism/DefaultDrink.cs @@ -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; diff --git a/Content.Server/Chemistry/Metabolism/DefaultFood.cs b/Content.Server/Chemistry/Metabolism/DefaultFood.cs index 824721acfd..d220bb1805 100644 --- a/Content.Server/Chemistry/Metabolism/DefaultFood.cs +++ b/Content.Server/Chemistry/Metabolism/DefaultFood.cs @@ -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; diff --git a/Content.Server/Chemistry/ReactionEffects/ExplosionReactionEffect.cs b/Content.Server/Chemistry/ReactionEffects/ExplosionReactionEffect.cs index 7382a7bdc2..2bdff9b1c2 100644 --- a/Content.Server/Chemistry/ReactionEffects/ExplosionReactionEffect.cs +++ b/Content.Server/Chemistry/ReactionEffects/ExplosionReactionEffect.cs @@ -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)) diff --git a/Content.Server/Chemistry/ReactionPrototype.cs b/Content.Server/Chemistry/ReactionPrototype.cs index f5c9c8974f..36e2304b16 100644 --- a/Content.Server/Chemistry/ReactionPrototype.cs +++ b/Content.Server/Chemistry/ReactionPrototype.cs @@ -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 _reactants; - private Dictionary _products; + private Dictionary _products; private List _effects; public string ID => _id; @@ -28,7 +29,7 @@ namespace Content.Server.Chemistry /// /// Reagents created when the reaction occurs. /// - public IReadOnlyDictionary Products => _products; + public IReadOnlyDictionary Products => _products; /// /// Effects to be triggered when the reaction occurs. /// @@ -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()); - serializer.DataField(ref _products, "products", new Dictionary()); + serializer.DataField(ref _products, "products", new Dictionary()); serializer.DataField(ref _effects, "effects", new List()); } } @@ -51,13 +52,13 @@ namespace Content.Server.Chemistry /// public class ReactantPrototype : IExposeData { - private int _amount; + private ReagentUnit _amount; private bool _catalyst; /// /// Minimum amount of the reactant needed for the reaction to occur. /// - public int Amount => _amount; + public ReagentUnit Amount => _amount; /// /// Whether or not the reactant is a catalyst. Catalysts aren't removed when a reaction occurs. /// @@ -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); } } diff --git a/Content.Server/GameObjects/Components/Cargo/CargoConsoleComponent.cs b/Content.Server/GameObjects/Components/Cargo/CargoConsoleComponent.cs index b5e3fd4564..3b555c725f 100644 --- a/Content.Server/GameObjects/Components/Cargo/CargoConsoleComponent.cs +++ b/Content.Server/GameObjects/Components/Cargo/CargoConsoleComponent.cs @@ -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(); _userInterface = Owner.GetComponent().GetBoundUserInterface(CargoConsoleUiKey.Key); _userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage; + _powerDevice = Owner.GetComponent(); _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); } diff --git a/Content.Server/GameObjects/Components/Chemistry/InjectorComponent.cs b/Content.Server/GameObjects/Components/Chemistry/InjectorComponent.cs index d4140d31f4..1b33bd2583 100644 --- a/Content.Server/GameObjects/Components/Chemistry/InjectorComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/InjectorComponent.cs @@ -37,13 +37,13 @@ namespace Content.Server.GameObjects.Components.Chemistry /// attempt to inject it's entire contents upon use. /// [ViewVariables] - private int _transferAmount; + private ReagentUnit _transferAmount; /// /// Initial storage volume of the injector /// [ViewVariables] - private int _initialMaxVolume; + private ReagentUnit _initialMaxVolume; /// /// 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(); _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, diff --git a/Content.Server/GameObjects/Components/Chemistry/PourableComponent.cs b/Content.Server/GameObjects/Components/Chemistry/PourableComponent.cs index b0f47233b0..f129e30321 100644 --- a/Content.Server/GameObjects/Components/Chemistry/PourableComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/PourableComponent.cs @@ -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; /// /// The amount of solution to be transferred from this solution when clicking on other solutions with it. /// [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)); } /// @@ -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, diff --git a/Content.Server/GameObjects/Components/Chemistry/ReagentDispenserComponent.cs b/Content.Server/GameObjects/Components/Chemistry/ReagentDispenserComponent.cs index fa3a65e6c5..829edbab1a 100644 --- a/Content.Server/GameObjects/Components/Chemistry/ReagentDispenserComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/ReagentDispenserComponent.cs @@ -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(); + ///implementing PowerDeviceComponent + private PowerDeviceComponent _powerDevice; + private bool Powered => _powerDevice.Powered; + + /// /// Shows the serializer how to save/load this components yaml prototype. /// @@ -70,6 +76,7 @@ namespace Content.Server.GameObjects.Components.Chemistry _beakerContainer = ContainerManagerComponent.Ensure($"{Name}-reagentContainerContainer", Owner); + _powerDevice = Owner.GetComponent(); 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(); 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(); - 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)); } } + + } } diff --git a/Content.Server/GameObjects/Components/Chemistry/SolutionComponent.cs b/Content.Server/GameObjects/Components/Chemistry/SolutionComponent.cs index 98b064bfc2..230c23c659 100644 --- a/Content.Server/GameObjects/Components/Chemistry/SolutionComponent.cs +++ b/Content.Server/GameObjects/Components/Chemistry/SolutionComponent.cs @@ -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 { /// - /// Shared ECS component that manages a liquid solution of reagents. + /// ECS component that manages a liquid solution of reagents. /// [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 _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; + + /// + /// The maximum volume of the container. + /// + [ViewVariables(VVAccess.ReadWrite)] + public ReagentUnit MaxVolume + { + get => _maxVolume; + set => _maxVolume = value; // Note that the contents won't spill out if the capacity is reduced. + } + + /// + /// The total volume of all the of the reagents in the container. + /// + [ViewVariables] + public ReagentUnit CurrentVolume => _containedSolution.TotalVolume; + + /// + /// The volume without reagents remaining in the container. + /// + [ViewVariables] + public ReagentUnit EmptyVolume => MaxVolume - CurrentVolume; + + /// + /// The current blended color of all the reagents in the container. + /// + [ViewVariables(VVAccess.ReadWrite)] + public Color SubstanceColor { get; private set; } + + /// + /// The current capabilities of this container (is the top open to pour? can I inject it into another object?). + /// + [ViewVariables(VVAccess.ReadWrite)] + public SolutionCaps Capabilities + { + get => _capabilities; + set => _capabilities = value; + } + + [ViewVariables] + public Solution Solution + { + get => _containedSolution; + set => _containedSolution = value; + } + + public IReadOnlyList ReagentList => _containedSolution.Contents; + + /// + /// Shortcut for Capabilities PourIn flag to avoid binary operators. + /// + public bool CanPourIn => (Capabilities & SolutionCaps.PourIn) != 0; + /// + /// Shortcut for Capabilities PourOut flag to avoid binary operators. + /// + public bool CanPourOut => (Capabilities & SolutionCaps.PourOut) != 0; + /// + /// Shortcut for Capabilities Injectable flag + /// + public bool Injectable => (Capabilities & SolutionCaps.Injectable) != 0; + /// + /// Shortcut for Capabilities Injector flag + /// + public bool Injector => (Capabilities & SolutionCaps.Injector) != 0; + + public bool NoExamine => (Capabilities & SolutionCaps.NoExamine) != 0; + + /// + 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(); + _chemistrySystem = _entitySystemManager.GetEntitySystem(); + _reactions = _prototypeManager.EnumeratePrototypes(); + } protected override void Startup() { base.Startup(); - Init(); + RecalculateColor(); + if (!string.IsNullOrEmpty(_fillInitState)) + { + _spriteComponent = Owner.GetComponent(); + _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(); - _audioSystem = _entitySystemManager.GetEntitySystem(); + _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; } /// - /// Initializes the SolutionComponent if it doesn't have an owner + /// Attempt to remove the specified quantity from this solution /// - public void InitializeFromPrototype() + /// Quantity of this solution to remove + /// Whether or not the solution was successfully removed + public bool TryRemoveSolution(ReagentUnit quantity) { - // Because Initialize needs an Owner, Startup isn't called, etc. - IoCManager.InjectDependencies(this); - _reactions = _prototypeManager.EnumeratePrototypes(); + 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; } /// @@ -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 /// The reaction whose reactants will be checked for in the solution. /// The number of times the reaction can occur with the given solution. /// - 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 /// Solution to be reacted. /// Reaction to occur. /// The number of times to cause this reaction. - 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); } + + /// + /// Check if the solution contains the specified reagent. + /// + /// The reagent to check for. + /// Output the quantity of the reagent if it is contained, 0 if it isn't. + /// Return true if the solution contains the reagent. + 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(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); + } } } diff --git a/Content.Server/GameObjects/Components/Chemistry/TransformableContainerComponent.cs b/Content.Server/GameObjects/Components/Chemistry/TransformableContainerComponent.cs new file mode 100644 index 0000000000..dec60171f8 --- /dev/null +++ b/Content.Server/GameObjects/Components/Chemistry/TransformableContainerComponent.cs @@ -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(); + _initialSprite = new SpriteSpecifier.Rsi(new ResourcePath(_sprite.BaseRSIPath), "icon"); + _initialName = Owner.Name; + _initialDescription = Owner.Description; + } + + protected override void Startup() + { + base.Startup(); + Owner.GetComponent().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(); + //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; + } + } + } +} diff --git a/Content.Server/GameObjects/Components/Command/CommunicationsConsoleComponent.cs b/Content.Server/GameObjects/Components/Command/CommunicationsConsoleComponent.cs new file mode 100644 index 0000000000..a3ce594242 --- /dev/null +++ b/Content.Server/GameObjects/Components/Command/CommunicationsConsoleComponent.cs @@ -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(); + + public override void Initialize() + { + base.Initialize(); + + _userInterface = Owner.GetComponent().GetBoundUserInterface(CommunicationsConsoleUiKey.Key); + _userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage; + _powerDevice = Owner.GetComponent(); + + 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); + } + } +} diff --git a/Content.Server/GameObjects/Components/Construction/ConstructionComponent.cs b/Content.Server/GameObjects/Components/Construction/ConstructionComponent.cs index f7f3b81bbd..be64cfb14f 100644 --- a/Content.Server/GameObjects/Components/Construction/ConstructionComponent.cs +++ b/Content.Server/GameObjects/Components/Construction/ConstructionComponent.cs @@ -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); } - + } diff --git a/Content.Server/GameObjects/Components/Damage/DamageableComponent.cs b/Content.Server/GameObjects/Components/Damage/DamageableComponent.cs index 76d34a39af..d341520159 100644 --- a/Content.Server/GameObjects/Components/Damage/DamageableComponent.cs +++ b/Content.Server/GameObjects/Components/Damage/DamageableComponent.cs @@ -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); diff --git a/Content.Server/GameObjects/Components/Items/Storage/EntityStorageComponent.cs b/Content.Server/GameObjects/Components/Items/Storage/EntityStorageComponent.cs index 77eab4ac52..75fa7111c2 100644 --- a/Content.Server/GameObjects/Components/Items/Storage/EntityStorageComponent.cs +++ b/Content.Server/GameObjects/Components/Items/Storage/EntityStorageComponent.cs @@ -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() && !entity.HasComponent()) + // only items that can be stored in an inventory, or a mob, can be eaten by a locker + if (!entity.HasComponent() && !entity.HasComponent()) continue; if (!AddToContents(entity)) @@ -207,7 +207,8 @@ namespace Content.Server.GameObjects.Components private bool AddToContents(IEntity entity) { var collidableComponent = Owner.GetComponent(); - if(entity.TryGetComponent(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(out var entityCollidableComponent)) + { + entityCollidableComponent.CollisionEnabled = true; + } + } } } diff --git a/Content.Server/GameObjects/Components/Markers/SpawnPointComponent.cs b/Content.Server/GameObjects/Components/Markers/SpawnPointComponent.cs index e502a08f1a..77b01c34a1 100644 --- a/Content.Server/GameObjects/Components/Markers/SpawnPointComponent.cs +++ b/Content.Server/GameObjects/Components/Markers/SpawnPointComponent.cs @@ -38,5 +38,6 @@ namespace Content.Server.GameObjects.Components.Markers Unset = 0, LateJoin, Job, + Observer, } } diff --git a/Content.Server/GameObjects/Components/Medical/MedicalScannerComponent.cs b/Content.Server/GameObjects/Components/Medical/MedicalScannerComponent.cs index f27144bb0b..2ea653ff69 100644 --- a/Content.Server/GameObjects/Components/Medical/MedicalScannerComponent.cs +++ b/Content.Server/GameObjects/Components/Medical/MedicalScannerComponent.cs @@ -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() .GetBoundUserInterface(MedicalScannerUiKey.Key); _bodyContainer = ContainerManagerComponent.Ensure($"{Name}-bodyContainer", Owner); + _powerDevice = Owner.GetComponent(); 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); } diff --git a/Content.Server/GameObjects/Components/Metabolism/BloodstreamComponent.cs b/Content.Server/GameObjects/Components/Metabolism/BloodstreamComponent.cs index 0fa862ce82..e428c72f17 100644 --- a/Content.Server/GameObjects/Components/Metabolism/BloodstreamComponent.cs +++ b/Content.Server/GameObjects/Components/Metabolism/BloodstreamComponent.cs @@ -34,29 +34,24 @@ namespace Content.Server.GameObjects.Components.Metabolism /// Max volume of internal solution storage /// [ViewVariables] - private int _initialMaxVolume; + private ReagentUnit _initialMaxVolume; /// /// Empty volume of internal solution /// - 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(); _internalSolution.MaxVolume = _initialMaxVolume; - _internalSolution.Owner = Owner; //Manually set owner to avoid crash when VV'ing this } /// @@ -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); } } diff --git a/Content.Server/GameObjects/Components/Mobs/MindComponent.cs b/Content.Server/GameObjects/Components/Mobs/MindComponent.cs index 882d827969..0505ddd363 100644 --- a/Content.Server/GameObjects/Components/Mobs/MindComponent.cs +++ b/Content.Server/GameObjects/Components/Mobs/MindComponent.cs @@ -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.CanReturnToBody = false; + Mind.TransferTo(ghost); + } } } } diff --git a/Content.Server/GameObjects/Components/Mobs/SpeciesComponent.cs b/Content.Server/GameObjects/Components/Mobs/SpeciesComponent.cs index f3952cb92d..d3db6ac632 100644 --- a/Content.Server/GameObjects/Components/Mobs/SpeciesComponent.cs +++ b/Content.Server/GameObjects/Components/Mobs/SpeciesComponent.cs @@ -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 { /// /// 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().TakeDamage(DamageType.Brute, bruteDamage, null); Owner.GetComponent().TakeDamage(DamageType.Heat, burnDamage, null); } + + void IRelayMoveInput.MoveInputPressed(IPlayerSession session) + { + if (CurrentDamageState is DeadState) + { + new Ghost().Execute(null, session, null); + } + } } /// diff --git a/Content.Server/GameObjects/Components/Movement/AiControllerComponent.cs b/Content.Server/GameObjects/Components/Movement/AiControllerComponent.cs index 755d00dee7..f5e947d3d3 100644 --- a/Content.Server/GameObjects/Components/Movement/AiControllerComponent.cs +++ b/Content.Server/GameObjects/Components/Movement/AiControllerComponent.cs @@ -56,18 +56,6 @@ namespace Content.Server.GameObjects.Components.Movement serializer.DataField(ref _visionRadius, "vision", 8.0f); } - /// - /// Movement speed (m/s) that the entity walks, before modifiers - /// - [ViewVariables(VVAccess.ReadWrite)] - public float BaseWalkSpeed { get; set; } = PlayerInputMoverComponent.DefaultBaseWalkSpeed; - - /// - /// Movement speed (m/s) that the entity sprints, before modifiers - /// - [ViewVariables(VVAccess.ReadWrite)] - public float BaseSprintSpeed { get; set; } = PlayerInputMoverComponent.DefaultBaseSprintSpeed; - /// /// Movement speed (m/s) that the entity walks, after modifiers /// @@ -76,14 +64,15 @@ namespace Content.Server.GameObjects.Components.Movement { get { - float speed = BaseWalkSpeed; - if (Owner.TryGetComponent(out MovementSpeedModifierComponent component)) + if (Owner.TryGetComponent(out MovementSpeedModifierComponent component)) { - speed *= component.WalkSpeedModifier; + return component.CurrentWalkSpeed; } - return speed; + + return MovementSpeedModifierComponent.DefaultBaseWalkSpeed; } } + /// /// Movement speed (m/s) that the entity walks, after modifiers /// @@ -92,12 +81,12 @@ namespace Content.Server.GameObjects.Components.Movement { get { - float speed = BaseSprintSpeed; - if (Owner.TryGetComponent(out MovementSpeedModifierComponent component)) + if (Owner.TryGetComponent(out MovementSpeedModifierComponent component)) { - speed *= component.SprintSpeedModifier; + return component.CurrentSprintSpeed; } - return speed; + + return MovementSpeedModifierComponent.DefaultBaseSprintSpeed; } } diff --git a/Content.Server/GameObjects/Components/Movement/MovementSpeedModifierComponent.cs b/Content.Server/GameObjects/Components/Movement/MovementSpeedModifierComponent.cs index aaa6a38261..738884f463 100644 --- a/Content.Server/GameObjects/Components/Movement/MovementSpeedModifierComponent.cs +++ b/Content.Server/GameObjects/Components/Movement/MovementSpeedModifierComponent.cs @@ -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; + /// /// set to warn us that a component's movespeed modifier has changed /// @@ -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); + } + /// /// Recalculate movement speed with current modifiers, or return early if no change /// diff --git a/Content.Server/GameObjects/Components/Movement/PlayerInputMoverComponent.cs b/Content.Server/GameObjects/Components/Movement/PlayerInputMoverComponent.cs index 26d056ac17..f3229c82ac 100644 --- a/Content.Server/GameObjects/Components/Movement/PlayerInputMoverComponent.cs +++ b/Content.Server/GameObjects/Components/Movement/PlayerInputMoverComponent.cs @@ -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 /// public override string Name => "PlayerInputMover"; - - /// - /// Movement speed (m/s) that the entity walks, before modifiers - /// - [ViewVariables(VVAccess.ReadWrite)] - public float BaseWalkSpeed { get; set; } = DefaultBaseWalkSpeed; - - /// - /// Movement speed (m/s) that the entity sprints, before modifiers - /// - [ViewVariables(VVAccess.ReadWrite)] - public float BaseSprintSpeed { get; set; } = DefaultBaseSprintSpeed; - /// /// Movement speed (m/s) that the entity walks, after modifiers /// @@ -58,12 +42,12 @@ namespace Content.Server.GameObjects.Components.Movement { get { - float speed = BaseWalkSpeed; - if (Owner.TryGetComponent(out MovementSpeedModifierComponent component)) + if (Owner.TryGetComponent(out MovementSpeedModifierComponent component)) { - speed *= component.WalkSpeedModifier; + return component.CurrentWalkSpeed; } - return speed; + + return MovementSpeedModifierComponent.DefaultBaseWalkSpeed; } } /// @@ -74,12 +58,12 @@ namespace Content.Server.GameObjects.Components.Movement { get { - float speed = BaseSprintSpeed; - if (Owner.TryGetComponent(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 /// [ViewVariables] public bool DiagonalMovementEnabled => _configurationManager.GetCVar("game.diagonalmovement"); - public override void Initialize() - { - base.Initialize(); - _configurationManager.RegisterCVar("game.diagonalmovement", true, CVar.ARCHIVE); - } - /// public override void OnAdd() { @@ -120,20 +98,6 @@ namespace Content.Server.GameObjects.Components.Movement base.OnAdd(); } - /// - 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. - } - - /// /// Toggles one of the four cardinal directions. Each of the four directions are /// composed into a single direction vector, . Enabling diff --git a/Content.Server/GameObjects/Components/Nutrition/DrinkComponent.cs b/Content.Server/GameObjects/Components/Nutrition/DrinkComponent.cs index ab6f259b24..3a3886f52b 100644 --- a/Content.Server/GameObjects/Components/Nutrition/DrinkComponent.cs +++ b/Content.Server/GameObjects/Components/Nutrition/DrinkComponent.cs @@ -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(); - //Ensure SolutionComponent supports click transferring if custom one not set - _contents.Capabilities = SolutionCaps.PourIn - | SolutionCaps.PourOut - | SolutionCaps.Injectable; - - var pourable = Owner.AddComponent(); - 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(); + _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); - } - - /// - /// 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. - /// - /// The entity that is using the drink - 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; - } - } - - /// - /// 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. - /// - private void HandleSolutionChangedEvent() - { - Finish(null); } } } diff --git a/Content.Server/GameObjects/Components/Nutrition/FoodComponent.cs b/Content.Server/GameObjects/Components/Nutrition/FoodComponent.cs index df990b392c..a0039b14f9 100644 --- a/Content.Server/GameObjects/Components/Nutrition/FoodComponent.cs +++ b/Content.Server/GameObjects/Components/Nutrition/FoodComponent.cs @@ -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)) { diff --git a/Content.Server/GameObjects/Components/Nutrition/StomachComponent.cs b/Content.Server/GameObjects/Components/Nutrition/StomachComponent.cs index 95353351f9..db6e16831e 100644 --- a/Content.Server/GameObjects/Components/Nutrition/StomachComponent.cs +++ b/Content.Server/GameObjects/Components/Nutrition/StomachComponent.cs @@ -27,7 +27,7 @@ namespace Content.Server.GameObjects.Components.Nutrition /// /// Max volume of internal solution storage /// - 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 /// [ViewVariables] - private int _initialMaxVolume; + private ReagentUnit _initialMaxVolume; /// /// Time in seconds between reagents being ingested and them being transferred to @@ -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(); _stomachContents.MaxVolume = _initialMaxVolume; - _stomachContents.Owner = Owner; //Manually set owner to avoid crash when VV'ing this - - //Ensure bloodstream in present if (!Owner.TryGetComponent(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; diff --git a/Content.Server/GameObjects/Components/Observer/GhostComponent.cs b/Content.Server/GameObjects/Components/Observer/GhostComponent.cs new file mode 100644 index 0000000000..bcb1365b56 --- /dev/null +++ b/Content.Server/GameObjects/Components/Observer/GhostComponent.cs @@ -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().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; + } +} diff --git a/Content.Server/GameObjects/Components/Power/LightBulbComponent.cs b/Content.Server/GameObjects/Components/Power/LightBulbComponent.cs index 9316dd6be9..f8fde7b236 100644 --- a/Content.Server/GameObjects/Components/Power/LightBulbComponent.cs +++ b/Content.Server/GameObjects/Components/Power/LightBulbComponent.cs @@ -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. /// [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 + /// /// Invoked whenever the state of the light bulb changes. /// @@ -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("glassbreak"); + var file = _random.Pick(soundCollection.PickFiles); + + IoCManager.Resolve().GetEntitySystem().Play(file, Owner); + + State = LightBulbState.Broken; + } } } diff --git a/Content.Server/GameObjects/Components/Research/LatheComponent.cs b/Content.Server/GameObjects/Components/Research/LatheComponent.cs index f1020eb091..b97cc3a7cb 100644 --- a/Content.Server/GameObjects/Components/Research/LatheComponent.cs +++ b/Content.Server/GameObjects/Components/Research/LatheComponent.cs @@ -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) diff --git a/Content.Server/GameObjects/Components/Research/ResearchConsoleComponent.cs b/Content.Server/GameObjects/Components/Research/ResearchConsoleComponent.cs index e3a2d4be72..9b10ad832a 100644 --- a/Content.Server/GameObjects/Components/Research/ResearchConsoleComponent.cs +++ b/Content.Server/GameObjects/Components/Research/ResearchConsoleComponent.cs @@ -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) { diff --git a/Content.Server/GameObjects/Components/Stack/StackComponent.cs b/Content.Server/GameObjects/Components/Stack/StackComponent.cs index 9aaa605f1c..3b4bae15c5 100644 --- a/Content.Server/GameObjects/Components/Stack/StackComponent.cs +++ b/Content.Server/GameObjects/Components/Stack/StackComponent.cs @@ -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(); - 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(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 } } diff --git a/Content.Server/GameObjects/Components/VendingMachines/VendingMachineComponent.cs b/Content.Server/GameObjects/Components/VendingMachines/VendingMachineComponent.cs index 2284d14596..1a9823d8ae 100644 --- a/Content.Server/GameObjects/Components/VendingMachines/VendingMachineComponent.cs +++ b/Content.Server/GameObjects/Components/VendingMachines/VendingMachineComponent.cs @@ -47,6 +47,8 @@ namespace Content.Server.GameObjects.Components.VendingMachines { return; } + if (!Powered) + return; var wires = Owner.GetComponent(); 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) { diff --git a/Content.Server/GameObjects/EntitySystems/ActionBlockerSystem.cs b/Content.Server/GameObjects/EntitySystems/ActionBlockerSystem.cs index ce3205bb01..36de02bb79 100644 --- a/Content.Server/GameObjects/EntitySystems/ActionBlockerSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/ActionBlockerSystem.cs @@ -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 diff --git a/Content.Server/GameObjects/EntitySystems/ChemistrySystem.cs b/Content.Server/GameObjects/EntitySystems/ChemistrySystem.cs new file mode 100644 index 0000000000..69bd555460 --- /dev/null +++ b/Content.Server/GameObjects/EntitySystems/ChemistrySystem.cs @@ -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 +{ + /// + /// This interface gives components behavior on whether entities solution (implying SolutionComponent is in place) is changed + /// + public interface ISolutionChange + { + /// + /// Called when solution is mixed with some other solution, or when some part of the solution is removed + /// + 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().ToList(); + + foreach (var solutionChangeArg in solutionChangeArgs) + { + solutionChangeArg.SolutionChanged(eventArgs); + } + } + } +} diff --git a/Content.Server/GameObjects/EntitySystems/MoverSystem.cs b/Content.Server/GameObjects/EntitySystems/MoverSystem.cs index a2524b6204..82a38ca8ca 100644 --- a/Content.Server/GameObjects/EntitySystems/MoverSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/MoverSystem.cs @@ -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(PlayerDetached); _audioSystem = EntitySystemManager.GetEntitySystem(); + + _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()) + { + comp.MoveInputPressed(playerSes); + } + } + moverComp.SetVelocityDirection(dir, state); } diff --git a/Content.Server/GameObjects/EntitySystems/RoundEndSystem.cs b/Content.Server/GameObjects/EntitySystems/RoundEndSystem.cs new file mode 100644 index 0000000000..9077e94184 --- /dev/null +++ b/Content.Server/GameObjects/EntitySystems/RoundEndSystem.cs @@ -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(); + } + } +} diff --git a/Content.Server/GameObjects/VisibilityFlags.cs b/Content.Server/GameObjects/VisibilityFlags.cs new file mode 100644 index 0000000000..4acb9c988a --- /dev/null +++ b/Content.Server/GameObjects/VisibilityFlags.cs @@ -0,0 +1,10 @@ +using System; + +namespace Content.Server.GameObjects +{ + [Flags] + public enum VisibilityFlags + { + Ghost = 2, + } +} diff --git a/Content.Server/GameTicking/GamePreset.cs b/Content.Server/GameTicking/GamePreset.cs index cf7981b725..b880b162a0 100644 --- a/Content.Server/GameTicking/GamePreset.cs +++ b/Content.Server/GameTicking/GamePreset.cs @@ -1,4 +1,4 @@ -namespace Content.Server.GameTicking +namespace Content.Server.GameTicking { /// /// 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!"; } } diff --git a/Content.Server/GameTicking/GamePresets/PresetDeathMatch.cs b/Content.Server/GameTicking/GamePresets/PresetDeathMatch.cs index 11c716442d..5b866dc8fa 100644 --- a/Content.Server/GameTicking/GamePresets/PresetDeathMatch.cs +++ b/Content.Server/GameTicking/GamePresets/PresetDeathMatch.cs @@ -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(); } - 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!"; } } diff --git a/Content.Server/GameTicking/GamePresets/PresetSandbox.cs b/Content.Server/GameTicking/GamePresets/PresetSandbox.cs index f80b41ebd0..05f15c6972 100644 --- a/Content.Server/GameTicking/GamePresets/PresetSandbox.cs +++ b/Content.Server/GameTicking/GamePresets/PresetSandbox.cs @@ -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!"; } } diff --git a/Content.Server/GameTicking/GameTicker.cs b/Content.Server/GameTicking/GameTicker.cs index e8c654b2c3..764c65633a 100644 --- a/Content.Server/GameTicking/GameTicker.cs +++ b/Content.Server/GameTicking/GameTicker.cs @@ -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 _gameRules = new List(); [ViewVariables] private readonly List _manifest = new List(); @@ -66,6 +74,10 @@ namespace Content.Server.GameTicking [ViewVariables] private bool LobbyEnabled => _configurationManager.GetCVar("game.lobbyenabled"); + [ViewVariables] private bool _updateOnRoundEnd; + private CancellationTokenSource _updateShutdownCts; + + [ViewVariables] public GameRunLevel RunLevel { @@ -100,6 +112,7 @@ namespace Content.Server.GameTicking _netManager.RegisterNetMessage(nameof(MsgTickerJoinGame)); _netManager.RegisterNetMessage(nameof(MsgTickerLobbyStatus)); _netManager.RegisterNetMessage(nameof(MsgTickerLobbyInfo)); + _netManager.RegisterNetMessage(nameof(MsgRoundEndMessage)); SetStartPreset(_configurationManager.GetCVar("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().RealTime; _sendStatusToAll(); } + private void SendServerMessage(string message) + { + var msg = _netManager.CreateNetMessage(); + msg.Channel = ChatChannel.Server; + msg.Message = message; + IoCManager.Resolve().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(); + roundEndMessage.GamemodeTitle = MakeGamePreset().ModeTitle; + + //Get the timespan of the round. + roundEndMessage.RoundDuration = IoCManager.Resolve().RealTime.Subtract(_roundStartTimeSpan); + + //Generate a list of basic player info to display in the end round summary. + var listOfPlayerInfo = new List(); + foreach(var ply in _playerManager.GetAllPlayers().OrderBy(p => p.Name)) + { + if(ply.AttachedEntity.TryGetComponent(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(); + foreach (var entity in _entityManager.GetEntities(new TypeEntityQuery(typeof(SpawnPointComponent)))) + { + var point = entity.GetComponent(); + if (point.SpawnType == SpawnPointType.Observer) + possiblePoints.Add(entity.Transform.GridPosition); + } + + if (possiblePoints.Count != 0) location = _robustRandom.Pick(possiblePoints); + + return location; + } + /// /// 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; } } } + /// + /// 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. + /// + 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().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 } diff --git a/Content.Server/GlobalVerbs/ControlMobVerb.cs b/Content.Server/GlobalVerbs/ControlMobVerb.cs index 52150d57be..2da62dba18 100644 --- a/Content.Server/GlobalVerbs/ControlMobVerb.cs +++ b/Content.Server/GlobalVerbs/ControlMobVerb.cs @@ -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().playerSession.ContentData().Mind; var targetMind = target.GetComponent(); + var oldEntity = userMind.CurrentEntity; targetMind.Mind?.TransferTo(null); userMind.TransferTo(target); + + if(oldEntity.HasComponent()) + oldEntity.Delete(); } } } diff --git a/Content.Server/Interfaces/Chat/IChatManager.cs b/Content.Server/Interfaces/Chat/IChatManager.cs index 7cfe1b4f44..26af31827a 100644 --- a/Content.Server/Interfaces/Chat/IChatManager.cs +++ b/Content.Server/Interfaces/Chat/IChatManager.cs @@ -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); } diff --git a/Content.Server/Interfaces/Chemistry/IReactionEffect.cs b/Content.Server/Interfaces/Chemistry/IReactionEffect.cs index bd01799557..9e5e28071b 100644 --- a/Content.Server/Interfaces/Chemistry/IReactionEffect.cs +++ b/Content.Server/Interfaces/Chemistry/IReactionEffect.cs @@ -8,6 +8,6 @@ namespace Content.Shared.Interfaces /// public interface IReactionEffect : IExposeData { - void React(IEntity solutionEntity, int intensity); + void React(IEntity solutionEntity, decimal intensity); } } diff --git a/Content.Server/Interfaces/GameObjects/Components/Movement/IRelayMoveInput.cs b/Content.Server/Interfaces/GameObjects/Components/Movement/IRelayMoveInput.cs new file mode 100644 index 0000000000..53df4ed7e1 --- /dev/null +++ b/Content.Server/Interfaces/GameObjects/Components/Movement/IRelayMoveInput.cs @@ -0,0 +1,9 @@ +using Robust.Server.Interfaces.Player; + +namespace Content.Server.Interfaces.GameObjects.Components.Movement +{ + public interface IRelayMoveInput + { + void MoveInputPressed(IPlayerSession session); + } +} diff --git a/Content.Server/Interfaces/GameTicking/IGameTicker.cs b/Content.Server/Interfaces/GameTicking/IGameTicker.cs index e3754e1a7d..67bf2857d2 100644 --- a/Content.Server/Interfaces/GameTicking/IGameTicker.cs +++ b/Content.Server/Interfaces/GameTicking/IGameTicker.cs @@ -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() where T : GameRule, new(); void RemoveGameRule(GameRule rule); diff --git a/Content.Server/MoMMILink.cs b/Content.Server/MoMMILink.cs index 2b7bcfc8bb..0ae57f928d 100644 --- a/Content.Server/MoMMILink.cs +++ b/Content.Server/MoMMILink.cs @@ -88,21 +88,15 @@ namespace Content.Server } var password = _configurationManager.GetCVar("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(jsonReader); - } - catch (JsonSerializationException) - { - response.StatusCode = (int) HttpStatusCode.BadRequest; - return true; - } + message = request.GetFromJson(); + } + catch (JsonSerializationException) + { + // message null so enters block down below. } if (message == null) diff --git a/Content.Server/Mobs/Mind.cs b/Content.Server/Mobs/Mind.cs index 88b446928f..e931b80fbc 100644 --- a/Content.Server/Mobs/Mind.cs +++ b/Content.Server/Mobs/Mind.cs @@ -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; } + /// /// 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); } diff --git a/Content.Server/Observer/Ghost.cs b/Content.Server/Observer/Ghost.cs new file mode 100644 index 0000000000..31a1d42bd3 --- /dev/null +++ b/Content.Server/Observer/Ghost.cs @@ -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()) + return; + + if (mind.VisitingEntity != null) + { + mind.UnVisit(); + mind.VisitingEntity.Delete(); + } + + var position = player.AttachedEntity?.Transform.GridPosition ?? IoCManager.Resolve().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(); + var ghost = entityManager.SpawnEntity("MobObserver", position); + ghost.Name = mind.CharacterName; + + var ghostComponent = ghost.GetComponent(); + ghostComponent.CanReturnToBody = canReturn; + + if(canReturn) + mind.Visit(ghost); + else + mind.TransferTo(ghost); + } + } +} diff --git a/Content.Server/ServerContentIoC.cs b/Content.Server/ServerContentIoC.cs index ec9ff2a876..f4bb04b9cc 100644 --- a/Content.Server/ServerContentIoC.cs +++ b/Content.Server/ServerContentIoC.cs @@ -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 diff --git a/Content.Shared/Chat/ChatChannel.cs b/Content.Shared/Chat/ChatChannel.cs index 0aac182734..4c9da79f0b 100644 --- a/Content.Shared/Chat/ChatChannel.cs +++ b/Content.Shared/Chat/ChatChannel.cs @@ -6,7 +6,7 @@ namespace Content.Shared.Chat /// Represents chat channels that the player can filter chat tabs by. /// [Flags] - public enum ChatChannel : byte + public enum ChatChannel : short { None = 0, @@ -46,9 +46,14 @@ namespace Content.Shared.Chat /// Emotes = 64, + /// + /// Deadchat + /// + Dead = 128, + /// /// Unspecified. /// - Unspecified = 128, + Unspecified = 256, } } diff --git a/Content.Shared/Chat/MsgChatMessage.cs b/Content.Shared/Chat/MsgChatMessage.cs index de2a5f980c..10e73d881e 100644 --- a/Content.Shared/Chat/MsgChatMessage.cs +++ b/Content.Shared/Chat/MsgChatMessage.cs @@ -35,7 +35,7 @@ namespace Content.Shared.Chat /// /// The sending entity. - /// Only applies to and . + /// Only applies to , and . /// 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; diff --git a/Content.Shared/Chemistry/DefaultMetabolizable.cs b/Content.Shared/Chemistry/DefaultMetabolizable.cs index ab5816d9f0..05686ccf07 100644 --- a/Content.Shared/Chemistry/DefaultMetabolizable.cs +++ b/Content.Shared/Chemistry/DefaultMetabolizable.cs @@ -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); } } } diff --git a/Content.Shared/Chemistry/ReagentPrototype.cs b/Content.Shared/Chemistry/ReagentPrototype.cs index 65b4756047..2840ca4855 100644 --- a/Content.Shared/Chemistry/ReagentPrototype.cs +++ b/Content.Shared/Chemistry/ReagentPrototype.cs @@ -21,6 +21,7 @@ namespace Content.Shared.Chemistry private string _description; private Color _substanceColor; private List _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 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 {new DefaultMetabolizable()}); diff --git a/Content.Shared/Chemistry/ReagentUnit.cs b/Content.Shared/Chemistry/ReagentUnit.cs new file mode 100644 index 0000000000..43c2354f03 --- /dev/null +++ b/Content.Shared/Chemistry/ReagentUnit.cs @@ -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, IEquatable + { + 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; + } + } +} diff --git a/Content.Shared/Chemistry/Solution.cs b/Content.Shared/Chemistry/Solution.cs index bbb9937249..5faefd8430 100644 --- a/Content.Shared/Chemistry/Solution.cs +++ b/Content.Shared/Chemistry/Solution.cs @@ -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). /// [ViewVariables] - public int TotalVolume { get; private set; } + public ReagentUnit TotalVolume { get; private set; } /// /// Constructs an empty solution (ex. an empty beaker). @@ -35,7 +37,7 @@ namespace Content.Shared.Chemistry /// /// The prototype ID of the reagent to add. /// The quantity in milli-units. - 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 /// /// The prototype ID of the reagent to add. /// The quantity in milli-units. - 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 /// /// The prototype ID of the reagent to add. /// The quantity in milli-units. - 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. /// /// The quantity of this solution to remove - 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; diff --git a/Content.Shared/Chemistry/SolutionCaps.cs b/Content.Shared/Chemistry/SolutionCaps.cs index 66fbc71801..6f5ecb3bda 100644 --- a/Content.Shared/Chemistry/SolutionCaps.cs +++ b/Content.Shared/Chemistry/SolutionCaps.cs @@ -24,6 +24,8 @@ namespace Content.Shared.Chemistry /// Allows us to have obscenely large containers that are harder to abuse in chem dispensers /// since they can't be placed directly in them. /// - FitsInDispenser = 16, + FitsInDispenser = 16, + + NoExamine = 32, } } diff --git a/Content.Shared/GameObjects/Components/Chemistry/SharedInjectorComponent.cs b/Content.Shared/GameObjects/Components/Chemistry/SharedInjectorComponent.cs index ebbb4b11e4..4fbef4ff26 100644 --- a/Content.Shared/GameObjects/Components/Chemistry/SharedInjectorComponent.cs +++ b/Content.Shared/GameObjects/Components/Chemistry/SharedInjectorComponent.cs @@ -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; diff --git a/Content.Shared/GameObjects/Components/Chemistry/SharedReagentDispenserComponent.cs b/Content.Shared/GameObjects/Components/Chemistry/SharedReagentDispenserComponent.cs index 0066dc3d50..d9ff0581dd 100644 --- a/Content.Shared/GameObjects/Components/Chemistry/SharedReagentDispenserComponent.cs +++ b/Content.Shared/GameObjects/Components/Chemistry/SharedReagentDispenserComponent.cs @@ -26,8 +26,8 @@ namespace Content.Shared.GameObjects.Components.Chemistry public class ReagentDispenserBoundUserInterfaceState : BoundUserInterfaceState { public readonly bool HasBeaker; - public readonly int BeakerCurrentVolume; - public readonly int BeakerMaxVolume; + public readonly ReagentUnit BeakerCurrentVolume; + public readonly ReagentUnit BeakerMaxVolume; public readonly string ContainerName; /// /// A list of the reagents which this dispenser can dispense. @@ -38,10 +38,10 @@ namespace Content.Shared.GameObjects.Components.Chemistry /// public readonly List ContainerReagents; public readonly string DispenserName; - public readonly int SelectedDispenseAmount; + public readonly ReagentUnit SelectedDispenseAmount; - public ReagentDispenserBoundUserInterfaceState(bool hasBeaker, int beakerCurrentVolume, int beakerMaxVolume, string containerName, - List inventory, string dispenserName, List containerReagents, int selectedDispenseAmount) + public ReagentDispenserBoundUserInterfaceState(bool hasBeaker, ReagentUnit beakerCurrentVolume, ReagentUnit beakerMaxVolume, string containerName, + List inventory, string dispenserName, List containerReagents, ReagentUnit selectedDispenseAmount) { HasBeaker = hasBeaker; BeakerCurrentVolume = beakerCurrentVolume; diff --git a/Content.Shared/GameObjects/Components/Chemistry/SharedSolutionComponent.cs b/Content.Shared/GameObjects/Components/Chemistry/SharedSolutionComponent.cs new file mode 100644 index 0000000000..232bea635e --- /dev/null +++ b/Content.Shared/GameObjects/Components/Chemistry/SharedSolutionComponent.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using Content.Shared.Chemistry; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Maths; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; +using Robust.Shared.ViewVariables; + +namespace Content.Shared.GameObjects.Components.Chemistry +{ + public class SharedSolutionComponent : Component + { + public override string Name => "Solution"; + + /// + public sealed override uint? NetID => ContentNetIDs.SOLUTION; + + [Serializable, NetSerializable] + public class SolutionComponentState : ComponentState + { + public SolutionComponentState() : base(ContentNetIDs.SOLUTION) { } + } + + /// + public override ComponentState GetComponentState() + { + return new SolutionComponentState(); + } + + /// + public override void HandleComponentState(ComponentState curState, ComponentState nextState) + { + base.HandleComponentState(curState, nextState); + + if(curState == null) + return; + + var compState = (SolutionComponentState)curState; + + //TODO: Make me work! + } + + } +} diff --git a/Content.Shared/GameObjects/Components/Chemistry/SolutionComponent.cs b/Content.Shared/GameObjects/Components/Chemistry/SolutionComponent.cs deleted file mode 100644 index 426daa0639..0000000000 --- a/Content.Shared/GameObjects/Components/Chemistry/SolutionComponent.cs +++ /dev/null @@ -1,238 +0,0 @@ -using System; -using System.Collections.Generic; -using Content.Shared.Chemistry; -using Robust.Shared.GameObjects; -using Robust.Shared.IoC; -using Robust.Shared.Maths; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization; -using Robust.Shared.ViewVariables; - -namespace Content.Shared.GameObjects.Components.Chemistry -{ - public class SolutionComponent : Component - { -#pragma warning disable 649 - [Dependency] private readonly IPrototypeManager _prototypeManager; -#pragma warning restore 649 - - [ViewVariables] - protected Solution _containedSolution = new Solution(); - protected int _maxVolume; - private SolutionCaps _capabilities; - - /// - /// Triggered when the solution contents change. - /// - public event Action SolutionChanged; - - /// - /// The maximum volume of the container. - /// - [ViewVariables(VVAccess.ReadWrite)] - public int MaxVolume - { - get => _maxVolume; - set => _maxVolume = value; // Note that the contents won't spill out if the capacity is reduced. - } - - /// - /// The total volume of all the of the reagents in the container. - /// - [ViewVariables] - public int CurrentVolume => _containedSolution.TotalVolume; - - /// - /// The volume without reagents remaining in the container. - /// - [ViewVariables] - public int EmptyVolume => MaxVolume - CurrentVolume; - - /// - /// The current blended color of all the reagents in the container. - /// - [ViewVariables(VVAccess.ReadWrite)] - public Color SubstanceColor { get; private set; } - - /// - /// The current capabilities of this container (is the top open to pour? can I inject it into another object?). - /// - [ViewVariables(VVAccess.ReadWrite)] - public SolutionCaps Capabilities - { - get => _capabilities; - set => _capabilities = value; - } - - public IReadOnlyList ReagentList => _containedSolution.Contents; - - /// - /// Shortcut for Capabilities PourIn flag to avoid binary operators. - /// - public bool CanPourIn => (Capabilities & SolutionCaps.PourIn) != 0; - /// - /// Shortcut for Capabilities PourOut flag to avoid binary operators. - /// - public bool CanPourOut => (Capabilities & SolutionCaps.PourOut) != 0; - /// - /// Shortcut for Capabilities Injectable flag - /// - public bool Injectable => (Capabilities & SolutionCaps.Injectable) != 0; - /// - /// Shortcut for Capabilities Injector flag - /// - public bool Injector => (Capabilities & SolutionCaps.Injector) != 0; - - /// - public override string Name => "Solution"; - - /// - public sealed override uint? NetID => ContentNetIDs.SOLUTION; - - /// - public override void ExposeData(ObjectSerializer serializer) - { - base.ExposeData(serializer); - - serializer.DataField(ref _maxVolume, "maxVol", 0); - serializer.DataField(ref _containedSolution, "contents", _containedSolution); - serializer.DataField(ref _capabilities, "caps", SolutionCaps.None); - } - - /// - protected override void Startup() - { - base.Startup(); - - RecalculateColor(); - } - - /// - protected override void Shutdown() - { - base.Shutdown(); - - _containedSolution.RemoveAllSolution(); - _containedSolution = new Solution(); - } - - public void RemoveAllSolution() - { - _containedSolution.RemoveAllSolution(); - OnSolutionChanged(); - } - - public bool TryRemoveReagent(string reagentId, int quantity) - { - if (!ContainsReagent(reagentId, out var currentQuantity)) return false; - - _containedSolution.RemoveReagent(reagentId, quantity); - OnSolutionChanged(); - return true; - } - - /// - /// Attempt to remove the specified quantity from this solution - /// - /// Quantity of this solution to remove - /// Whether or not the solution was successfully removed - public bool TryRemoveSolution(int quantity) - { - if (CurrentVolume == 0) - return false; - - _containedSolution.RemoveSolution(quantity); - OnSolutionChanged(); - return true; - } - - public Solution SplitSolution(int quantity) - { - var solutionSplit = _containedSolution.SplitSolution(quantity); - OnSolutionChanged(); - return solutionSplit; - } - - protected void RecalculateColor() - { - if(_containedSolution.TotalVolume == 0) - SubstanceColor = Color.White; - - Color mixColor = default; - float runningTotalQuantity = 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 = BlendRGB(mixColor, proto.SubstanceColor, reagent.Quantity / runningTotalQuantity); - } - } - - private Color BlendRGB(Color rgb1, Color rgb2, float amount) - { - var r = (float)Math.Round(rgb1.R + (rgb2.R - rgb1.R) * amount, 1); - var g = (float)Math.Round(rgb1.G + (rgb2.G - rgb1.G) * amount, 1); - var b = (float)Math.Round(rgb1.B + (rgb2.B - rgb1.B) * amount, 1); - var alpha = (float)Math.Round(rgb1.A + (rgb2.A - rgb1.A) * amount, 1); - - return new Color(r, g, b, alpha); - } - - /// - public override ComponentState GetComponentState() - { - return new SolutionComponentState(); - } - - /// - public override void HandleComponentState(ComponentState curState, ComponentState nextState) - { - base.HandleComponentState(curState, nextState); - - if(curState == null) - return; - - var compState = (SolutionComponentState)curState; - - //TODO: Make me work! - } - - [Serializable, NetSerializable] - public class SolutionComponentState : ComponentState - { - public SolutionComponentState() : base(ContentNetIDs.SOLUTION) { } - } - - /// - /// Check if the solution contains the specified reagent. - /// - /// The reagent to check for. - /// Output the quantity of the reagent if it is contained, 0 if it isn't. - /// Return true if the solution contains the reagent. - public bool ContainsReagent(string reagentId, out int quantity) - { - foreach (var reagent in _containedSolution.Contents) - { - if (reagent.ReagentId == reagentId) - { - quantity = reagent.Quantity; - return true; - } - } - quantity = 0; - return false; - } - - protected virtual void OnSolutionChanged() - { - SolutionChanged?.Invoke(); - } - } -} diff --git a/Content.Shared/GameObjects/Components/Command/SharedCommunicationsConsoleComponent.cs b/Content.Shared/GameObjects/Components/Command/SharedCommunicationsConsoleComponent.cs new file mode 100644 index 0000000000..88fb0168fc --- /dev/null +++ b/Content.Shared/GameObjects/Components/Command/SharedCommunicationsConsoleComponent.cs @@ -0,0 +1,49 @@ +using System; +using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Components.UserInterface; +using Robust.Shared.Serialization; + +namespace Content.Shared.GameObjects.Components.Command +{ + public class SharedCommunicationsConsoleComponent : Component + { + public override string Name => "CommunicationsConsole"; + + } + + [Serializable, NetSerializable] + public class CommunicationsConsoleInterfaceState : BoundUserInterfaceState + { + public readonly TimeSpan? ExpectedCountdownEnd; + public readonly bool CountdownStarted; + + public CommunicationsConsoleInterfaceState(TimeSpan? expectedCountdownEnd = null) + { + ExpectedCountdownEnd = expectedCountdownEnd; + CountdownStarted = expectedCountdownEnd != null; + + } + } + + [Serializable, NetSerializable] + public class CommunicationsConsoleCallEmergencyShuttleMessage : BoundUserInterfaceMessage + { + public CommunicationsConsoleCallEmergencyShuttleMessage() + { + } + } + + [Serializable, NetSerializable] + public class CommunicationsConsoleRecallEmergencyShuttleMessage : BoundUserInterfaceMessage + { + public CommunicationsConsoleRecallEmergencyShuttleMessage() + { + } + } + + [Serializable, NetSerializable] + public enum CommunicationsConsoleUiKey + { + Key + } +} diff --git a/Content.Shared/GameObjects/Components/Observer/SharedGhostComponent.cs b/Content.Shared/GameObjects/Components/Observer/SharedGhostComponent.cs new file mode 100644 index 0000000000..b838c3b63e --- /dev/null +++ b/Content.Shared/GameObjects/Components/Observer/SharedGhostComponent.cs @@ -0,0 +1,29 @@ +using System; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization; + +namespace Content.Shared.GameObjects.Components.Observer +{ + public class SharedGhostComponent : Component + { + public override string Name => "Ghost"; + public override uint? NetID => ContentNetIDs.GHOST; + } + + [Serializable, NetSerializable] + public class GhostComponentState : ComponentState + { + public bool CanReturnToBody { get; } + + public GhostComponentState(bool canReturnToBody) : base(ContentNetIDs.GHOST) + { + CanReturnToBody = canReturnToBody; + } + } + + [Serializable, NetSerializable] + public class ReturnToBodyComponentMessage : ComponentMessage + { + public ReturnToBodyComponentMessage() => Directed = true; + } +} diff --git a/Content.Shared/GameObjects/Components/SharedStackComponent.cs b/Content.Shared/GameObjects/Components/SharedStackComponent.cs index 3a141d4d4d..9ceb2f69b7 100644 --- a/Content.Shared/GameObjects/Components/SharedStackComponent.cs +++ b/Content.Shared/GameObjects/Components/SharedStackComponent.cs @@ -1,16 +1,109 @@ using System; using Robust.Shared.GameObjects; +using Robust.Shared.Interfaces.Reflection; +using Robust.Shared.IoC; using Robust.Shared.Serialization; +using Robust.Shared.ViewVariables; namespace Content.Shared.GameObjects.Components { public abstract class SharedStackComponent : Component { + private const string SerializationCache = "stack"; + public sealed override string Name => "Stack"; public sealed override uint? NetID => ContentNetIDs.STACK; + private int _count; + private int _maxCount; + + [ViewVariables(VVAccess.ReadWrite)] + public virtual int Count + { + get => _count; + set + { + _count = value; + if (_count <= 0) + { + Owner.Delete(); + } + + Dirty(); + } + } + + [ViewVariables] + public int MaxCount + { + get => _maxCount; + private set + { + _maxCount = value; + Dirty(); + } + } + + [ViewVariables] public int AvailableSpace => MaxCount - Count; + + [ViewVariables] public object StackType { get; private set; } + + 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(); + if (refl.TryParseEnumReference(raw, out var @enum)) + { + stackType = @enum; + } + else + { + stackType = raw; + } + } + else + { + stackType = Owner.Prototype.ID; + } + + serializer.SetCacheData(SerializationCache, stackType); + StackType = stackType; + } + + public override ComponentState GetComponentState() + { + return new StackComponentState(Count, MaxCount); + } + + public override void HandleComponentState(ComponentState curState, ComponentState nextState) + { + if (!(curState is StackComponentState cast)) + { + return; + } + + Count = cast.Count; + MaxCount = cast.MaxCount; + } + + [Serializable, NetSerializable] - protected sealed class StackComponentState : ComponentState + private sealed class StackComponentState : ComponentState { public int Count { get; } public int MaxCount { get; } @@ -22,4 +115,14 @@ namespace Content.Shared.GameObjects.Components } } } + + public enum StackType + { + Metal, + Glass, + Cable, + Ointment, + Brutepack, + FloorTileSteel + } } diff --git a/Content.Shared/GameObjects/ContentNetIDs.cs b/Content.Shared/GameObjects/ContentNetIDs.cs index eeecefda41..ef69972f4d 100644 --- a/Content.Shared/GameObjects/ContentNetIDs.cs +++ b/Content.Shared/GameObjects/ContentNetIDs.cs @@ -41,5 +41,6 @@ public const uint HANDHELD_LIGHT = 1036; public const uint PAPER = 1037; public const uint REAGENT_INJECTOR = 1038; + public const uint GHOST = 1039; } } diff --git a/Content.Shared/Interfaces/Chemistry/IMetabolizable.cs b/Content.Shared/Interfaces/Chemistry/IMetabolizable.cs index 4b03ef51e2..1973012d52 100644 --- a/Content.Shared/Interfaces/Chemistry/IMetabolizable.cs +++ b/Content.Shared/Interfaces/Chemistry/IMetabolizable.cs @@ -19,6 +19,6 @@ namespace Content.Shared.Interfaces.Chemistry /// The reagent id /// The time since the last metabolism tick in seconds. /// The amount of reagent to be removed. The metabolizing organ should handle removing the reagent. - int Metabolize(IEntity solutionEntity, string reagentId, float tickTime); + ReagentUnit Metabolize(IEntity solutionEntity, string reagentId, float tickTime); } } diff --git a/Content.Shared/SharedGameTicker.cs b/Content.Shared/SharedGameTicker.cs index 126a44e0e9..38d3e0985e 100644 --- a/Content.Shared/SharedGameTicker.cs +++ b/Content.Shared/SharedGameTicker.cs @@ -1,5 +1,7 @@ -using System; +using System; +using System.Collections.Generic; using Lidgren.Network; +using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Network; using Robust.Shared.Network; @@ -114,5 +116,79 @@ namespace Content.Shared buffer.Write(TextBlob); } } + public struct RoundEndPlayerInfo + { + public string PlayerOOCName; + public string PlayerICName; + public string Role; + public bool Antag; + + } + + protected class MsgRoundEndMessage : NetMessage + { + + #region REQUIRED + + public const MsgGroups GROUP = MsgGroups.Command; + public const string NAME = nameof(MsgRoundEndMessage); + public MsgRoundEndMessage(INetChannel channel) : base(NAME, GROUP) { } + + #endregion + + public string GamemodeTitle; + public TimeSpan RoundDuration; + + + public uint PlayerCount; + + public List AllPlayersEndInfo; + + public override void ReadFromBuffer(NetIncomingMessage buffer) + { + GamemodeTitle = buffer.ReadString(); + + var hours = buffer.ReadInt32(); + var mins = buffer.ReadInt32(); + var seconds = buffer.ReadInt32(); + RoundDuration = new TimeSpan(hours, mins, seconds); + + PlayerCount = buffer.ReadUInt32(); + AllPlayersEndInfo = new List(); + for(var i = 0; i < PlayerCount + 1; i++) + { + var readPlayerData = new RoundEndPlayerInfo + { + PlayerOOCName = buffer.ReadString(), + PlayerICName = buffer.ReadString(), + Role = buffer.ReadString(), + Antag = buffer.ReadBoolean() + }; + + AllPlayersEndInfo.Add(readPlayerData); + } + + } + + public override void WriteToBuffer(NetOutgoingMessage buffer) + { + buffer.Write(GamemodeTitle); + buffer.Write(RoundDuration.Hours); + buffer.Write(RoundDuration.Minutes); + buffer.Write(RoundDuration.Seconds); + + + buffer.Write(PlayerCount); + foreach(var playerEndInfo in AllPlayersEndInfo) + { + buffer.Write(playerEndInfo.PlayerOOCName); + buffer.Write(playerEndInfo.PlayerICName); + buffer.Write(playerEndInfo.Role); + buffer.Write(playerEndInfo.Antag); + } + } + + } } } + diff --git a/Content.Tests/Shared/Chemistry/ReagentUnit_Tests.cs b/Content.Tests/Shared/Chemistry/ReagentUnit_Tests.cs new file mode 100644 index 0000000000..003ecf4663 --- /dev/null +++ b/Content.Tests/Shared/Chemistry/ReagentUnit_Tests.cs @@ -0,0 +1,155 @@ +using Content.Shared.Chemistry; +using NUnit.Framework; +using System; + +namespace Content.Tests.Shared.Chemistry +{ + [TestFixture, TestOf(typeof(ReagentUnit))] + public class ReagentUnit_Tests + { + [Test] + [TestCase(1, "1")] + [TestCase(0, "0")] + [TestCase(-1, "-1")] + public void ReagentUnitIntegerTests(int value, string expected) + { + var result = ReagentUnit.New(value); + Assert.AreEqual(expected, $"{result}"); + } + + [Test] + [TestCase(1.001f, "1")] + [TestCase(0.999f, "1")] + public void ReagentUnitFloatTests(float value, string expected) + { + var result = ReagentUnit.New(value); + Assert.AreEqual(expected, $"{result}"); + } + + [Test] + [TestCase(1.001d, "1")] + [TestCase(0.999d, "1")] + public void ReagentUnitDoubleTests(double value, string expected) + { + var result = ReagentUnit.New(value); + Assert.AreEqual(expected, $"{result}"); + } + + [Test] + [TestCase("1.001", "1")] + [TestCase("0.999", "1")] + public void ReagentUnitDecimalTests(string valueAsString, string expected) + { + var value = decimal.Parse(valueAsString); + var result = ReagentUnit.New(value); + Assert.AreEqual(expected, $"{result}"); + } + + [Test] + [TestCase("1.005", "1.01")] + [TestCase("0.999", "1")] + public void ReagentUnitStringTests(string value, string expected) + { + var result = ReagentUnit.New(value); + Assert.AreEqual(expected, $"{result}"); + } + + [Test] + [TestCase(1.001f, 1.001f, "2")] + [TestCase(1.001f, 1.004f, "2")] + [TestCase(1f, 1.005f, "2.01")] + [TestCase(1f, 2.005f, "3.01")] + public void CalculusPlus(float aFloat, float bFloat, string expected) + { + var a = ReagentUnit.New(aFloat); + var b = ReagentUnit.New(bFloat); + + var result = a + b; + + Assert.AreEqual(expected, $"{result}"); + } + + [Test] + [TestCase(1.001f, 1.001f, "0")] + [TestCase(1.001f, 1.004f, "0")] + [TestCase(1f, 2.005f, "-1.01")] + public void CalculusMinus(float aFloat, float bFloat, string expected) + { + var a = ReagentUnit.New(aFloat); + var b = ReagentUnit.New(bFloat); + + var result = a - b; + + Assert.AreEqual(expected, $"{result}"); + } + + [Test] + [TestCase(1.001f, 3f, "0.33")] + [TestCase(0.999f, 3f, "0.33")] + [TestCase(2.1f, 3f, "0.7")] + public void CalculusDivision(float aFloat, float bFloat, string expected) + { + var a = ReagentUnit.New(aFloat); + var b = ReagentUnit.New(bFloat); + + var result = a / b; + + Assert.AreEqual(expected, $"{result}"); + } + + [Test] + [TestCase(1.001f, 0.999f, "1")] + [TestCase(0.999f, 3f, "3")] + public void CalculusMultiplication(float aFloat, float bFloat, string expected) + { + var a = ReagentUnit.New(aFloat); + var b = ReagentUnit.New(bFloat); + + var result = a * b; + + Assert.AreEqual(expected, $"{result}"); + } + + [Test] + [TestCase(0.995f, 100)] + [TestCase(1.005f, 101)] + [TestCase(2.005f, 201)] + public void FloatRoundingTest(float a, int expected) + { + var result = (int) Math.Round(a * (float) Math.Pow(10, 2), MidpointRounding.AwayFromZero); + Assert.AreEqual(expected, result); + } + + [Test] + public void ReagentUnitMin() + { + var unorderedList = new[] + { + ReagentUnit.New(5), + ReagentUnit.New(3), + ReagentUnit.New(1), + ReagentUnit.New(2), + ReagentUnit.New(4), + }; + var min = ReagentUnit.Min(unorderedList); + Assert.AreEqual(ReagentUnit.New(1), min); + } + + [Test] + [TestCase(1, 0, false)] + [TestCase(0, 0, true)] + [TestCase(-1, 0, false)] + [TestCase(null, 0, true)] + [TestCase(1, 1, true)] + [TestCase(0, 1, false)] + [TestCase(-1, 1, false)] + [TestCase(null, 1, false)] + public void ReagentUnitEquals(int a, int b, bool expected) + { + var parameter = ReagentUnit.New(a); + var comparison = ReagentUnit.New(b); + Assert.AreEqual(comparison.Equals(parameter), parameter.Equals(comparison)); + Assert.AreEqual(expected, comparison.Equals(parameter)); + } + } +} diff --git a/Content.Tests/Shared/Chemistry/Solution_Tests.cs b/Content.Tests/Shared/Chemistry/Solution_Tests.cs index cada149224..d5d101353e 100644 --- a/Content.Tests/Shared/Chemistry/Solution_Tests.cs +++ b/Content.Tests/Shared/Chemistry/Solution_Tests.cs @@ -10,19 +10,19 @@ namespace Content.Tests.Shared.Chemistry public void AddReagentAndGetSolution() { var solution = new Solution(); - solution.AddReagent("water", 1000); + solution.AddReagent("water", ReagentUnit.New(1000)); var quantity = solution.GetReagentQuantity("water"); - Assert.That(quantity, Is.EqualTo(1000)); + Assert.That(quantity.Int(), Is.EqualTo(1000)); } [Test] public void ConstructorAddReagent() { - var solution = new Solution("water", 1000); + var solution = new Solution("water", ReagentUnit.New(1000)); var quantity = solution.GetReagentQuantity("water"); - Assert.That(quantity, Is.EqualTo(1000)); + Assert.That(quantity.Int(), Is.EqualTo(1000)); } [Test] @@ -31,223 +31,294 @@ namespace Content.Tests.Shared.Chemistry var solution = new Solution(); var quantity = solution.GetReagentQuantity("water"); - Assert.That(quantity, Is.EqualTo(0)); + Assert.That(quantity.Int(), Is.EqualTo(0)); } [Test] public void AddLessThanZeroReagentReturnsZero() { - var solution = new Solution("water", -1000); + var solution = new Solution("water", ReagentUnit.New(-1000)); var quantity = solution.GetReagentQuantity("water"); - Assert.That(quantity, Is.EqualTo(0)); + Assert.That(quantity.Int(), Is.EqualTo(0)); } [Test] public void AddingReagentsSumsProperly() { var solution = new Solution(); - solution.AddReagent("water", 1000); - solution.AddReagent("water", 2000); + solution.AddReagent("water", ReagentUnit.New(1000)); + solution.AddReagent("water", ReagentUnit.New(2000)); var quantity = solution.GetReagentQuantity("water"); - Assert.That(quantity, Is.EqualTo(3000)); + Assert.That(quantity.Int(), Is.EqualTo(3000)); } [Test] public void ReagentQuantitiesStayUnique() { var solution = new Solution(); - solution.AddReagent("water", 1000); - solution.AddReagent("fire", 2000); + solution.AddReagent("water", ReagentUnit.New(1000)); + solution.AddReagent("fire", ReagentUnit.New(2000)); - Assert.That(solution.GetReagentQuantity("water"), Is.EqualTo(1000)); - Assert.That(solution.GetReagentQuantity("fire"), Is.EqualTo(2000)); + Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(1000)); + Assert.That(solution.GetReagentQuantity("fire").Int(), Is.EqualTo(2000)); } [Test] public void TotalVolumeIsCorrect() { var solution = new Solution(); - solution.AddReagent("water", 1000); - solution.AddReagent("fire", 2000); + solution.AddReagent("water", ReagentUnit.New(1000)); + solution.AddReagent("fire", ReagentUnit.New(2000)); - Assert.That(solution.TotalVolume, Is.EqualTo(3000)); + Assert.That(solution.TotalVolume.Int(), Is.EqualTo(3000)); } [Test] public void CloningSolutionIsCorrect() { var solution = new Solution(); - solution.AddReagent("water", 1000); - solution.AddReagent("fire", 2000); + solution.AddReagent("water", ReagentUnit.New(1000)); + solution.AddReagent("fire", ReagentUnit.New(2000)); var newSolution = solution.Clone(); - Assert.That(newSolution.GetReagentQuantity("water"), Is.EqualTo(1000)); - Assert.That(newSolution.GetReagentQuantity("fire"), Is.EqualTo(2000)); - Assert.That(newSolution.TotalVolume, Is.EqualTo(3000)); + Assert.That(newSolution.GetReagentQuantity("water").Int(), Is.EqualTo(1000)); + Assert.That(newSolution.GetReagentQuantity("fire").Int(), Is.EqualTo(2000)); + Assert.That(newSolution.TotalVolume.Int(), Is.EqualTo(3000)); } [Test] public void RemoveSolutionRecalculatesProperly() { var solution = new Solution(); - solution.AddReagent("water", 1000); - solution.AddReagent("fire", 2000); + solution.AddReagent("water", ReagentUnit.New(1000)); + solution.AddReagent("fire", ReagentUnit.New(2000)); - solution.RemoveReagent("water", 500); + solution.RemoveReagent("water", ReagentUnit.New(500)); - Assert.That(solution.GetReagentQuantity("water"), Is.EqualTo(500)); - Assert.That(solution.GetReagentQuantity("fire"), Is.EqualTo(2000)); - Assert.That(solution.TotalVolume, Is.EqualTo(2500)); + Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(500)); + Assert.That(solution.GetReagentQuantity("fire").Int(), Is.EqualTo(2000)); + Assert.That(solution.TotalVolume.Int(), Is.EqualTo(2500)); } [Test] public void RemoveLessThanOneQuantityDoesNothing() { - var solution = new Solution("water", 100); + var solution = new Solution("water", ReagentUnit.New(100)); - solution.RemoveReagent("water", -100); + solution.RemoveReagent("water", ReagentUnit.New(-100)); - Assert.That(solution.GetReagentQuantity("water"), Is.EqualTo(100)); - Assert.That(solution.TotalVolume, Is.EqualTo(100)); + Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(100)); + Assert.That(solution.TotalVolume.Int(), Is.EqualTo(100)); } [Test] public void RemoveMoreThanTotalRemovesAllReagent() { - var solution = new Solution("water", 100); + var solution = new Solution("water", ReagentUnit.New(100)); - solution.RemoveReagent("water", 1000); + solution.RemoveReagent("water", ReagentUnit.New(1000)); - Assert.That(solution.GetReagentQuantity("water"), Is.EqualTo(0)); - Assert.That(solution.TotalVolume, Is.EqualTo(0)); + Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(0)); + Assert.That(solution.TotalVolume.Int(), Is.EqualTo(0)); } [Test] public void RemoveNonExistReagentDoesNothing() { - var solution = new Solution("water", 100); + var solution = new Solution("water", ReagentUnit.New(100)); - solution.RemoveReagent("fire", 1000); + solution.RemoveReagent("fire", ReagentUnit.New(1000)); - Assert.That(solution.GetReagentQuantity("water"), Is.EqualTo(100)); - Assert.That(solution.TotalVolume, Is.EqualTo(100)); + Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(100)); + Assert.That(solution.TotalVolume.Int(), Is.EqualTo(100)); } [Test] public void RemoveSolution() { - var solution = new Solution("water", 700); + var solution = new Solution("water", ReagentUnit.New(700)); - solution.RemoveSolution(500); + solution.RemoveSolution(ReagentUnit.New(500)); //Check that edited solution is correct - Assert.That(solution.GetReagentQuantity("water"), Is.EqualTo(200)); - Assert.That(solution.TotalVolume, Is.EqualTo(200)); + Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(200)); + Assert.That(solution.TotalVolume.Int(), Is.EqualTo(200)); } [Test] public void RemoveSolutionMoreThanTotalRemovesAll() { - var solution = new Solution("water", 800); + var solution = new Solution("water", ReagentUnit.New(800)); - solution.RemoveSolution(1000); + solution.RemoveSolution(ReagentUnit.New(1000)); //Check that edited solution is correct - Assert.That(solution.GetReagentQuantity("water"), Is.EqualTo(0)); - Assert.That(solution.TotalVolume, Is.EqualTo(0)); + Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(0)); + Assert.That(solution.TotalVolume.Int(), Is.EqualTo(0)); } [Test] public void RemoveSolutionRatioPreserved() { var solution = new Solution(); - solution.AddReagent("water", 1000); - solution.AddReagent("fire", 2000); + solution.AddReagent("water", ReagentUnit.New(1000)); + solution.AddReagent("fire", ReagentUnit.New(2000)); - solution.RemoveSolution(1500); + solution.RemoveSolution(ReagentUnit.New(1500)); - Assert.That(solution.GetReagentQuantity("water"), Is.EqualTo(500)); - Assert.That(solution.GetReagentQuantity("fire"), Is.EqualTo(1000)); - Assert.That(solution.TotalVolume, Is.EqualTo(1500)); + Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(500)); + Assert.That(solution.GetReagentQuantity("fire").Int(), Is.EqualTo(1000)); + Assert.That(solution.TotalVolume.Int(), Is.EqualTo(1500)); } [Test] public void RemoveSolutionLessThanOneDoesNothing() { - var solution = new Solution("water", 800); + var solution = new Solution("water", ReagentUnit.New(800)); - solution.RemoveSolution(-200); + solution.RemoveSolution(ReagentUnit.New(-200)); - Assert.That(solution.GetReagentQuantity("water"), Is.EqualTo(800)); - Assert.That(solution.TotalVolume, Is.EqualTo(800)); + Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(800)); + Assert.That(solution.TotalVolume.Int(), Is.EqualTo(800)); } [Test] public void SplitSolution() { var solution = new Solution(); - solution.AddReagent("water", 1000); - solution.AddReagent("fire", 2000); + solution.AddReagent("water", ReagentUnit.New(1000)); + solution.AddReagent("fire", ReagentUnit.New(2000)); - var splitSolution = solution.SplitSolution(750); + var splitSolution = solution.SplitSolution(ReagentUnit.New(750)); - Assert.That(solution.GetReagentQuantity("water"), Is.EqualTo(750)); - Assert.That(solution.GetReagentQuantity("fire"), Is.EqualTo(1500)); - Assert.That(solution.TotalVolume, Is.EqualTo(2250)); + Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(750)); + Assert.That(solution.GetReagentQuantity("fire").Int(), Is.EqualTo(1500)); + Assert.That(solution.TotalVolume.Int(), Is.EqualTo(2250)); - Assert.That(splitSolution.GetReagentQuantity("water"), Is.EqualTo(250)); - Assert.That(splitSolution.GetReagentQuantity("fire"), Is.EqualTo(500)); - Assert.That(splitSolution.TotalVolume, Is.EqualTo(750)); + Assert.That(splitSolution.GetReagentQuantity("water").Int(), Is.EqualTo(250)); + Assert.That(splitSolution.GetReagentQuantity("fire").Int(), Is.EqualTo(500)); + Assert.That(splitSolution.TotalVolume.Int(), Is.EqualTo(750)); + } + + [Test] + public void SplitSolutionFractional() + { + var solution = new Solution(); + solution.AddReagent("water", ReagentUnit.New(1)); + solution.AddReagent("fire", ReagentUnit.New(2)); + + var splitSolution = solution.SplitSolution(ReagentUnit.New(1)); + + Assert.That(solution.GetReagentQuantity("water").Float(), Is.EqualTo(0.67f)); + Assert.That(solution.GetReagentQuantity("fire").Float(), Is.EqualTo(1.33f)); + Assert.That(solution.TotalVolume.Int(), Is.EqualTo(2)); + + Assert.That(splitSolution.GetReagentQuantity("water").Float(), Is.EqualTo(0.33f)); + Assert.That(splitSolution.GetReagentQuantity("fire").Float(), Is.EqualTo(0.67f)); + Assert.That(splitSolution.TotalVolume.Int(), Is.EqualTo(1)); + } + + [Test] + public void SplitSolutionFractionalOpposite() + { + var solution = new Solution(); + solution.AddReagent("water", ReagentUnit.New(1)); + solution.AddReagent("fire", ReagentUnit.New(2)); + + var splitSolution = solution.SplitSolution(ReagentUnit.New(2)); + + Assert.That(solution.GetReagentQuantity("water").Float(), Is.EqualTo(0.33f)); + Assert.That(solution.GetReagentQuantity("fire").Float(), Is.EqualTo(0.67f)); + Assert.That(solution.TotalVolume.Int(), Is.EqualTo(1)); + + Assert.That(splitSolution.GetReagentQuantity("water").Float(), Is.EqualTo(0.67f)); + Assert.That(splitSolution.GetReagentQuantity("fire").Float(), Is.EqualTo(1.33f)); + Assert.That(splitSolution.TotalVolume.Int(), Is.EqualTo(2)); + } + + [Test] + [TestCase(0.03f, 0.01f, 0.02f)] + [TestCase(0.03f, 0.02f, 0.01f)] + public void SplitSolutionTinyFractionalBigSmall(float initial, float reduce, float remainder) + { + var solution = new Solution(); + solution.AddReagent("water", ReagentUnit.New(initial)); + + var splitSolution = solution.SplitSolution(ReagentUnit.New(reduce)); + + Assert.That(solution.GetReagentQuantity("water").Float(), Is.EqualTo(remainder)); + Assert.That(solution.TotalVolume.Float(), Is.EqualTo(remainder)); + + Assert.That(splitSolution.GetReagentQuantity("water").Float(), Is.EqualTo(reduce)); + Assert.That(splitSolution.TotalVolume.Float(), Is.EqualTo(reduce)); + } + + [Test] + [TestCase(2)] + [TestCase(10)] + [TestCase(100)] + [TestCase(1000)] + public void SplitRounding(int amount) + { + var solutionOne = new Solution(); + solutionOne.AddReagent("foo", ReagentUnit.New(amount)); + solutionOne.AddReagent("bar", ReagentUnit.New(amount)); + solutionOne.AddReagent("baz", ReagentUnit.New(amount)); + + var splitAmount = ReagentUnit.New(5); + var split = solutionOne.SplitSolution(splitAmount); + + Assert.That(split.TotalVolume, Is.EqualTo(splitAmount)); } [Test] public void SplitSolutionMoreThanTotalRemovesAll() { - var solution = new Solution("water", 800); + var solution = new Solution("water", ReagentUnit.New(800)); - var splitSolution = solution.SplitSolution(1000); + var splitSolution = solution.SplitSolution(ReagentUnit.New(1000)); - Assert.That(solution.GetReagentQuantity("water"), Is.EqualTo(0)); - Assert.That(solution.TotalVolume, Is.EqualTo(0)); + Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(0)); + Assert.That(solution.TotalVolume.Int(), Is.EqualTo(0)); - Assert.That(splitSolution.GetReagentQuantity("water"), Is.EqualTo(800)); - Assert.That(splitSolution.TotalVolume, Is.EqualTo(800)); + Assert.That(splitSolution.GetReagentQuantity("water").Int(), Is.EqualTo(800)); + Assert.That(splitSolution.TotalVolume.Int(), Is.EqualTo(800)); } [Test] public void SplitSolutionLessThanOneDoesNothing() { - var solution = new Solution("water", 800); + var solution = new Solution("water", ReagentUnit.New(800)); - var splitSolution = solution.SplitSolution(-200); + var splitSolution = solution.SplitSolution(ReagentUnit.New(-200)); - Assert.That(solution.GetReagentQuantity("water"), Is.EqualTo(800)); - Assert.That(solution.TotalVolume, Is.EqualTo(800)); + Assert.That(solution.GetReagentQuantity("water").Int(), Is.EqualTo(800)); + Assert.That(solution.TotalVolume.Int(), Is.EqualTo(800)); - Assert.That(splitSolution.GetReagentQuantity("water"), Is.EqualTo(0)); - Assert.That(splitSolution.TotalVolume, Is.EqualTo(0)); + Assert.That(splitSolution.GetReagentQuantity("water").Int(), Is.EqualTo(0)); + Assert.That(splitSolution.TotalVolume.Int(), Is.EqualTo(0)); } [Test] public void AddSolution() { var solutionOne = new Solution(); - solutionOne.AddReagent("water", 1000); - solutionOne.AddReagent("fire", 2000); + solutionOne.AddReagent("water", ReagentUnit.New(1000)); + solutionOne.AddReagent("fire", ReagentUnit.New(2000)); var solutionTwo = new Solution(); - solutionTwo.AddReagent("water", 500); - solutionTwo.AddReagent("earth", 1000); + solutionTwo.AddReagent("water", ReagentUnit.New(500)); + solutionTwo.AddReagent("earth", ReagentUnit.New(1000)); solutionOne.AddSolution(solutionTwo); - Assert.That(solutionOne.GetReagentQuantity("water"), Is.EqualTo(1500)); - Assert.That(solutionOne.GetReagentQuantity("fire"), Is.EqualTo(2000)); - Assert.That(solutionOne.GetReagentQuantity("earth"), Is.EqualTo(1000)); - Assert.That(solutionOne.TotalVolume, Is.EqualTo(4500)); + Assert.That(solutionOne.GetReagentQuantity("water").Int(), Is.EqualTo(1500)); + Assert.That(solutionOne.GetReagentQuantity("fire").Int(), Is.EqualTo(2000)); + Assert.That(solutionOne.GetReagentQuantity("earth").Int(), Is.EqualTo(1000)); + Assert.That(solutionOne.TotalVolume.Int(), Is.EqualTo(4500)); } } } diff --git a/Resources/Audio/effects/glassbreak1.ogg b/Resources/Audio/effects/glassbreak1.ogg new file mode 100644 index 0000000000..fe14cdeda3 Binary files /dev/null and b/Resources/Audio/effects/glassbreak1.ogg differ diff --git a/Resources/Audio/effects/glassbreak2.ogg b/Resources/Audio/effects/glassbreak2.ogg new file mode 100644 index 0000000000..79163acc19 Binary files /dev/null and b/Resources/Audio/effects/glassbreak2.ogg differ diff --git a/Resources/Audio/effects/glassbreak3.ogg b/Resources/Audio/effects/glassbreak3.ogg new file mode 100644 index 0000000000..bc45ab3332 Binary files /dev/null and b/Resources/Audio/effects/glassbreak3.ogg differ diff --git a/Resources/Groups/groups.yml b/Resources/Groups/groups.yml index d1fe708455..50b84be17f 100644 --- a/Resources/Groups/groups.yml +++ b/Resources/Groups/groups.yml @@ -11,6 +11,7 @@ - ooc - observe - toggleready + - ghost - Index: 50 Name: Moderator @@ -26,6 +27,7 @@ - showtime - observe - toggleready + - ghost - kick - listplayers - loc @@ -44,6 +46,7 @@ - aghost - observe - toggleready + - ghost - spawn - delete - tp @@ -84,6 +87,7 @@ - aghost - observe - toggleready + - ghost - spawn - delete - tp diff --git a/Resources/Prototypes/Entities/buildings/booze_dispenser.yml b/Resources/Prototypes/Entities/buildings/booze_dispenser.yml index 28010fe2b4..8e7f3ae2e8 100644 --- a/Resources/Prototypes/Entities/buildings/booze_dispenser.yml +++ b/Resources/Prototypes/Entities/buildings/booze_dispenser.yml @@ -18,3 +18,7 @@ - chem.Ale - chem.Wine - chem.Ice + - chem.Beer + - chem.Vodka + - chem.Cognac + - chem.Kahlua diff --git a/Resources/Prototypes/Entities/buildings/chem_dispenser.yml b/Resources/Prototypes/Entities/buildings/chem_dispenser.yml index da79842a68..21ba174a64 100644 --- a/Resources/Prototypes/Entities/buildings/chem_dispenser.yml +++ b/Resources/Prototypes/Entities/buildings/chem_dispenser.yml @@ -10,6 +10,7 @@ texture: Buildings/chemicals.rsi/industrial_dispenser.png - type: ReagentDispenser pack: ChemDispenserStandardInventory + - type: PowerDevice - type: reagentDispenserInventory id: ChemDispenserStandardInventory @@ -36,3 +37,4 @@ - chem.K - chem.Ra - chem.Na + - chem.U diff --git a/Resources/Prototypes/Entities/buildings/computers.yml b/Resources/Prototypes/Entities/buildings/computers.yml index fff70844a8..bc8dbf9fe4 100644 --- a/Resources/Prototypes/Entities/buildings/computers.yml +++ b/Resources/Prototypes/Entities/buildings/computers.yml @@ -176,3 +176,8 @@ - type: ComputerVisualizer2D key: generic_key screen: comm + - type: CommunicationsConsole + - type: UserInterface + interfaces: + - key: enum.CommunicationsConsoleUiKey.Key + type: CommunicationsConsoleBoundUserInterface diff --git a/Resources/Prototypes/Entities/buildings/instruments.yml b/Resources/Prototypes/Entities/buildings/instruments.yml index ce8cc881b9..2c6edf0355 100644 --- a/Resources/Prototypes/Entities/buildings/instruments.yml +++ b/Resources/Prototypes/Entities/buildings/instruments.yml @@ -1,70 +1,70 @@ -# - type: entity -# name: BaseInstrument -# id: BaseInstrument -# abstract: true -# components: -# - type: Instrument -# handheld: false -# -# - type: Clickable -# - type: InteractionOutline -# -# - type: Collidable -# shapes: -# - !type:PhysShapeAabb -# layer: 31 -# -# - type: SnapGrid -# offset: Center -# -# - type: Damageable -# - type: Destructible -# thresholdvalue: 50 -# -# - type: UserInterface -# interfaces: -# - key: enum.InstrumentUiKey.Key -# type: InstrumentBoundUserInterface -# -# - type: entity -# name: Piano -# parent: BaseInstrument -# id: PianoInstrument -# description: Play Needles Piano Now. -# components: -# - type: Instrument -# program: 1 -# - type: Sprite -# sprite: Objects/Instruments/musician.rsi -# state: piano -# - type: Icon -# sprite: Objects/Instruments/musician.rsi -# state: piano -# -# - type: entity -# name: Minimoog -# parent: BaseInstrument -# id: MinimoogInstrument -# components: -# - type: Instrument -# program: 7 -# - type: Sprite -# sprite: Objects/Instruments/musician.rsi -# state: minimoog -# - type: Icon -# sprite: Objects/Instruments/musician.rsi -# state: minimoog -# -# - type: entity -# name: Xylophone -# parent: BaseInstrument -# id: XylophoneInstrument -# components: -# - type: Instrument -# program: 13 -# - type: Sprite -# sprite: Objects/Instruments/musician.rsi -# state: xylophone -# - type: Icon -# sprite: Objects/Instruments/musician.rsi -# state: xylophone + - type: entity + name: BaseInstrument + id: BaseInstrument + abstract: true + components: + - type: Instrument + handheld: false + + - type: Clickable + - type: InteractionOutline + + - type: Collidable + shapes: + - !type:PhysShapeAabb + layer: 31 + + - type: SnapGrid + offset: Center + + - type: Damageable + - type: Destructible + thresholdvalue: 50 + + - type: UserInterface + interfaces: + - key: enum.InstrumentUiKey.Key + type: InstrumentBoundUserInterface + + - type: entity + name: Piano + parent: BaseInstrument + id: PianoInstrument + description: Play Needles Piano Now. + components: + - type: Instrument + program: 1 + - type: Sprite + sprite: Objects/Instruments/musician.rsi + state: piano + - type: Icon + sprite: Objects/Instruments/musician.rsi + state: piano + + - type: entity + name: Minimoog + parent: BaseInstrument + id: MinimoogInstrument + components: + - type: Instrument + program: 7 + - type: Sprite + sprite: Objects/Instruments/musician.rsi + state: minimoog + - type: Icon + sprite: Objects/Instruments/musician.rsi + state: minimoog + + - type: entity + name: Xylophone + parent: BaseInstrument + id: XylophoneInstrument + components: + - type: Instrument + program: 13 + - type: Sprite + sprite: Objects/Instruments/musician.rsi + state: xylophone + - type: Icon + sprite: Objects/Instruments/musician.rsi + state: xylophone diff --git a/Resources/Prototypes/Entities/buildings/medical_scanner.yml b/Resources/Prototypes/Entities/buildings/medical_scanner.yml index c583a1a12a..9ad614ce7b 100644 --- a/Resources/Prototypes/Entities/buildings/medical_scanner.yml +++ b/Resources/Prototypes/Entities/buildings/medical_scanner.yml @@ -11,7 +11,7 @@ map: ["enum.MedicalScannerVisualLayers.Machine"] - state: scanner_terminal_blue map: ["enum.MedicalScannerVisualLayers.Terminal"] - + - type: PowerDevice - type: Icon sprite: Buildings/medical_scanner.rsi state: scanner_open diff --git a/Resources/Prototypes/Entities/buildings/soda_dispenser.yml b/Resources/Prototypes/Entities/buildings/soda_dispenser.yml index e52868a841..ce0bcebb75 100644 --- a/Resources/Prototypes/Entities/buildings/soda_dispenser.yml +++ b/Resources/Prototypes/Entities/buildings/soda_dispenser.yml @@ -19,3 +19,4 @@ - chem.Tea - chem.Ice - chem.H2O + - chem.Cream diff --git a/Resources/Prototypes/Entities/items/Consumables/drinks.yml b/Resources/Prototypes/Entities/items/Consumables/drinks.yml index cbae0e107f..bb5562b769 100644 --- a/Resources/Prototypes/Entities/items/Consumables/drinks.yml +++ b/Resources/Prototypes/Entities/items/Consumables/drinks.yml @@ -6,93 +6,37 @@ id: DrinkBase abstract: true components: + - type: Solution + maxVol: 50 + - type: Pourable + transferAmount: 5 - type: Drink - despawn_empty: true + despawn_empty: false - type: Sound - type: Sprite state: icon - netsync: false - type: Icon state: icon -# Drinks below here +# Transformable container - normal glass - type: entity + name: Drinking glass parent: DrinkBase - id: DrinkAbsintheglass - name: Absinthe glass - description: A anise-flavoured spirit derived from botanicals. + id: DrinkGlass components: - - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite - sprite: Objects/Drinks/absintheglass.rsi + sprite: Objects/Drinks/glass_clear.rsi - type: Icon - sprite: Objects/Drinks/absintheglass.rsi - -- type: entity - parent: DrinkBase - id: DrinkAcidspitGlass - name: Acidspit glass - description: A drink for the daring, can be deadly if incorrectly prepared! - components: - - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - - type: Sprite - sprite: Objects/Drinks/acidspitglass.rsi - - type: Icon - sprite: Objects/Drinks/acidspitglass.rsi - -- type: entity - parent: DrinkBase - id: DrinkAlco-blue - name: Miss Blue Curacao - description: A fruity, exceptionally azure drink. Does not allow the imbiber to use the fifth magic. - components: - - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - spawn_on_finish: TrashAlcoClear - - type: Sprite - sprite: Objects/Drinks/alco-blue.rsi - - type: Icon - sprite: Objects/Drinks/alco-blue.rsi - -#- type: entity -# parent: DrinkBase -# id: DrinkAlco-red -# name: Alco-red -# description: '' -# components: -# - type: Drink# -# contents: -# - water: 1 -# spawn_on_finish: TrashAlcoClear -# - type: Sprite -# sprite: Objects/Drinks/alco-red.rsi -# - type: Icon -# sprite: Objects/Drinks/alco-red.rsi -# -#- type: entity -# parent: DrinkBase -# id: DrinkAlco-white -# name: Alco-white -# description: '' -# components: -# - type: Drink# -# contents: -# - water: 1 -# - type: Sprite -# sprite: Objects/Drinks/alco-white.rsi -# - type: Icon -# sprite: Objects/Drinks/alco-white.rsi + sprite: Objects/Drinks/glass_clear.rsi + - type: Solution + fillingState: glass + maxVol: 50 + caps: 16 + - type: Drink + despawn_empty: false + - type: Pourable + transferAmount: 5 + - type: TransformableContainer - type: entity parent: DrinkBase @@ -100,91 +44,30 @@ name: Ale description: A dark alchoholic beverage made by malted barley and yeast. components: - - type: Drink + - type: Solution + maxVol: 20 contents: reagents: - - ReagentId: chem.H2O - Quantity: 4 + - ReagentId: chem.Ale + Quantity: 20 - type: Sprite sprite: Objects/Drinks/aleglass.rsi - type: Icon sprite: Objects/Drinks/aleglass.rsi -- type: entity - parent: DrinkBase - id: DrinkAlliescocktail - name: Allies cocktail - description: A drink made from your allies, not as sweet as when made from your enemies. - components: - - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - - type: Sprite - sprite: Objects/Drinks/alliescocktail.rsi - - type: Icon - sprite: Objects/Drinks/alliescocktail.rsi - -- type: entity - parent: DrinkBase - id: DrinkAloe - name: Aloe - description: So very, very, very good. - components: - - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - - type: Sprite - sprite: Objects/Drinks/aloe.rsi - - type: Icon - sprite: Objects/Drinks/aloe.rsi - -- type: entity - parent: DrinkBase - id: DrinkAmasecglass - name: Amasec glass - description: Official drink of the Gun Club! - components: - - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - - type: Sprite - sprite: Objects/Drinks/amasecglass.rsi - - type: Icon - sprite: Objects/Drinks/amasecglass.rsi - -- type: entity - parent: DrinkBase - id: DrinkAndalusia - name: Andalusia - description: A nice, strangely named drink. - components: - - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - - type: Sprite - sprite: Objects/Drinks/andalusia.rsi - - type: Icon - sprite: Objects/Drinks/andalusia.rsi - - type: entity parent: DrinkBase id: DrinkAntifreeze name: Anti-freeze description: Ultimate refreshment. components: - - type: Drink + - type: Solution + maxVol: 20 contents: reagents: - - ReagentId: chem.H2O - Quantity: 4 + - ReagentId: chem.Antifreeze + Quantity: 20 + - type: Drink - type: Sprite sprite: Objects/Drinks/antifreeze.rsi - type: Icon @@ -196,80 +79,36 @@ name: Atomic bomb glass description: Nuclear proliferation never tasted so good. components: - - type: Drink + - type: Solution + maxVol: 20 contents: reagents: - - ReagentId: chem.H2O - Quantity: 4 + - ReagentId: chem.AtomicBomb + Quantity: 20 + - type: Drink - type: Sprite sprite: Objects/Drinks/atomicbombglass.rsi - type: Icon sprite: Objects/Drinks/atomicbombglass.rsi -- type: entity - parent: DrinkBase - id: DrinkBarefootAndPregnant - name: Barefoot - description: Barefoot and pregnant - components: - - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - - type: Sprite - sprite: Objects/Drinks/b&p.rsi - - type: Icon - sprite: Objects/Drinks/b&p.rsi - - type: entity parent: DrinkBase id: DrinkB52Glass name: B-52 glass description: Coffee, Irish Cream, and cognac. You will get bombed. components: - - type: Drink + - type: Solution + maxVol: 20 contents: reagents: - - ReagentId: chem.H2O - Quantity: 4 + - ReagentId: chem.B52 + Quantity: 20 + - type: Drink - type: Sprite sprite: Objects/Drinks/b52glass.rsi - type: Icon sprite: Objects/Drinks/b52glass.rsi -- type: entity - parent: DrinkBase - id: DrinkBahamaMama - name: Bahama mama - description: Tropical cocktail. - components: - - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - - type: Sprite - sprite: Objects/Drinks/bahama_mama.rsi - - type: Icon - sprite: Objects/Drinks/bahama_mama.rsi - -- type: entity - parent: DrinkBase - id: DrinkBananaJuice - name: Banana juice - description: The raw essence of a banana. - components: - - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - - type: Sprite - sprite: Objects/Drinks/banana.rsi - - type: Icon - sprite: Objects/Drinks/banana.rsi - - type: entity parent: DrinkBase id: DrinkBananahonkglass @@ -277,10 +116,6 @@ description: A drink from Clown Heaven. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/bananahonkglass.rsi - type: Icon @@ -307,10 +142,6 @@ description: Deny drinking this and prepare for THE LAW. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/beepskysmashglass.rsi - type: Icon @@ -322,11 +153,12 @@ name: Beer # Beer it is. Coffee. Beer? COFF-EE? BE-ER? C-O... B-E description: An alcoholic beverage made from malted grains, hops, yeast, and water. components: - - type: Drink + - type: Solution + maxVol: 20 contents: reagents: - - ReagentId: chem.H2O - Quantity: 4 + - ReagentId: chem.Beer + Quantity: 20 - type: Sprite sprite: Objects/Drinks/beer.rsi - type: Icon @@ -338,11 +170,12 @@ name: Beer glass description: An alcoholic beverage made from malted grains, hops, yeast, and water. components: - - type: Drink + - type: Solution + maxVol: 20 contents: reagents: - - ReagentId: chem.H2O - Quantity: 4 + - ReagentId: chem.Beer + Quantity: 20 - type: Sprite sprite: Objects/Drinks/beerglass.rsi - type: Icon @@ -355,10 +188,6 @@ description: A delicious blend of several different kinds of berries. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/berryjuice.rsi - type: Icon @@ -385,10 +214,6 @@ description: For the lactose-intolerant. Still as classy as a White Russian. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/blackrussianglass.rsi - type: Icon @@ -401,10 +226,6 @@ description: A strange yet pleasurable mixture made of vodka, tomato and lime juice. Tastes like liquid murder components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/bloodymaryglass.rsi - type: Icon @@ -417,10 +238,6 @@ description: Ewww... components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/booger.rsi - type: Icon @@ -433,10 +250,6 @@ description: It's just as effective as Dutch-Courage! components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/bravebullglass.rsi - type: Icon @@ -463,10 +276,6 @@ description: It's not what it sounds like... components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/brownstar.rsi - type: Icon @@ -479,10 +288,6 @@ description: A nice, strong and tasty beverage while you are reading. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/cafe_latte.rsi - type: Icon @@ -495,10 +300,6 @@ description: A handled glass pitcher. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/carafe.rsi state: icon-10 @@ -517,10 +318,6 @@ description: Has a uniquely sweet flavour of concentrated carrots. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/carrotjuice.rsi - type: Icon @@ -533,10 +330,6 @@ description: You take a tiny sip and feel a burning sensation... components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/changelingsting.rsi - type: Icon @@ -549,10 +342,6 @@ description: A heated drink consisting melted chocolate and heated milk. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/chocolateglass.rsi - type: Icon @@ -564,11 +353,12 @@ name: Coffee description: Coffee is a brewed drink prepared from roasted seeds, commonly called coffee beans, of the coffee plant. components: - - type: Drink + - type: Solution + maxVol: 20 contents: reagents: - - ReagentId: chem.H2O - Quantity: 4 + - ReagentId: chem.Coffee + Quantity: 20 - type: Sprite sprite: Objects/Drinks/coffee.rsi - type: Icon @@ -580,11 +370,12 @@ name: Cognac glass description: A sweet and strongly alchoholic drink, made after numerous distillations and years of maturing. Classy as fornication. components: - - type: Drink + - type: Solution + maxVol: 20 contents: reagents: - - ReagentId: chem.H2O - Quantity: 4 + - ReagentId: chem.Cognac + Quantity: 20 - type: Sprite sprite: Objects/Drinks/cognacglass.rsi - type: Icon @@ -596,11 +387,12 @@ name: Space cola bottle description: Cola. in space components: - - type: Drink + - type: Solution + maxVol: 20 contents: reagents: - - ReagentId: chem.H2O - Quantity: 4 + - ReagentId: chem.Cola + Quantity: 20 - type: Sprite sprite: Objects/Drinks/colabottle.rsi - type: Icon @@ -612,11 +404,12 @@ name: Cream description: Dairy product composed of the higher-fat layer skimmed from the top of milk before homogenization. components: - - type: Drink + - type: Solution + maxVol: 20 contents: reagents: - - ReagentId: chem.H2O - Quantity: 4 + - ReagentId: chem.Cream + Quantity: 20 - type: Sprite sprite: Objects/Drinks/cream.rsi - type: Icon @@ -628,11 +421,12 @@ name: Cuba libre glass description: Rum, mixed with cola. Viva la revolucion. components: - - type: Drink + - type: Solution + maxVol: 20 contents: reagents: - - ReagentId: chem.H2O - Quantity: 4 + - ReagentId: chem.CubaLibre + Quantity: 20 - type: Sprite sprite: Objects/Drinks/cubalibreglass.rsi - type: Icon @@ -645,10 +439,6 @@ description: Exotically blue, fruity drink, distilled from oranges. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/curacaoglass.rsi - type: Icon @@ -661,10 +451,6 @@ description: AHHHH!!!! # AAHHHHHH components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/demonsblood.rsi - type: Icon @@ -677,10 +463,6 @@ description: A metal flask with a leather band and golden badge belonging to the inspector. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/detflask.rsi - type: Icon @@ -693,10 +475,6 @@ description: Creepy time! components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/devilskiss.rsi - type: Icon @@ -709,10 +487,6 @@ description: A gulp a day keeps the MediBot away. That's probably for the best. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/doctorsdelightglass.rsi - type: Icon @@ -725,10 +499,6 @@ description: Only for the experienced. You think you see sand floating in the glass. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/driestmartiniglass.rsi - type: Icon @@ -741,10 +511,6 @@ description: A delicious blend of 42 different flavours components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/dr_gibb.rsi - type: Icon @@ -757,10 +523,6 @@ description: A delicious blend of 42 different flavours components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/dr_gibb_glass.rsi - type: Icon @@ -773,10 +535,6 @@ description: A relatively sweet and fruity 46 proof liquor. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/emeraldglass.rsi - type: Icon @@ -789,10 +547,6 @@ description: '' components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/energy_drink.rsi - type: Icon @@ -805,10 +559,6 @@ description: The surprise is, it's green! components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/erikasurprise.rsi - type: Icon @@ -821,10 +571,6 @@ description: A metal flask belonging to the captain components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/flask.rsi - type: Icon @@ -837,10 +583,6 @@ description: '' components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/flask_old.rsi - type: Icon @@ -853,10 +595,6 @@ description: Whoah, this stuff looks volatile! components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/gargleblasterglass.rsi - type: Icon @@ -869,10 +607,6 @@ description: Refreshingly lemony, deliciously dry. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/ginfizzglass.rsi - type: Icon @@ -885,10 +619,6 @@ description: An all time classic, mild cocktail. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/gintonicglass.rsi - type: Icon @@ -901,10 +631,6 @@ description: 100 proof cinnamon schnapps, made for alcoholic teen girls on spring break. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/ginvodkaglass.rsi - type: Icon @@ -917,10 +643,6 @@ description: This appears to be beer mixed with milk. Disgusting. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/glass_brown.rsi - type: Icon @@ -933,10 +655,6 @@ description: Either someone's failure at cocktail making or attempt in alchohol production. In any case, do you really want to drink that? components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/glass_brown2.rsi - type: Icon @@ -949,10 +667,6 @@ description: You've really hit rock bottom now... your liver packed its bags and left last night. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/glass_clear.rsi - type: Icon @@ -965,10 +679,6 @@ description: The sweet-sour juice of limes. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/glass_green.rsi - type: Icon @@ -981,10 +691,6 @@ description: Liquid extract of the orange tree fruit, produced by squeezing or reaming oranges. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/glass_orange.rsi - type: Icon @@ -997,10 +703,6 @@ description: Juice made from tomatoes, usually used as a beverage, either plain or in cocktails components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/glass_red.rsi - type: Icon @@ -1012,11 +714,12 @@ name: Milk description: An opaque white liquid produced by the mammary glands of mammals. components: - - type: Drink + - type: Solution + maxVol: 20 contents: reagents: - - ReagentId: chem.H2O - Quantity: 4 + - ReagentId: chem.Milk + Quantity: 20 - type: Sprite sprite: Objects/Drinks/glass_white.rsi - type: Icon @@ -1030,10 +733,6 @@ description: '' components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/glass_yellow.rsi - type: Icon @@ -1046,10 +745,6 @@ description: 100 proof cinnamon schnapps, made for alcoholic teen girls on spring break. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/goldschlagerglass.rsi - type: Icon @@ -1062,10 +757,6 @@ description: The juice is often sold in stores or fermented and made into wine, brandy, or vinegar. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/grapejuice.rsi - type: Icon @@ -1078,10 +769,6 @@ description: Sweetened drink with a grape flavor and a deep purple color. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/grapesoda.rsi - type: Icon @@ -1094,10 +781,6 @@ description: Sweet and tangy, a bar syrup used to add color or flavor to drinks. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/grenadinebottle.rsi - type: Icon @@ -1110,10 +793,6 @@ description: Made in the modern day with proper pomegranate substitute. Who uses real fruit, anyways? components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/grenadineglass.rsi - type: Icon @@ -1126,10 +805,6 @@ description: Watered-down rum, pirate approved! components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/grogglass.rsi - type: Icon @@ -1142,10 +817,6 @@ description: Sweetened drink with a grape flavor and a deep purple color. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/gsodaglass.rsi - type: Icon @@ -1158,10 +829,6 @@ description: The noodles are boiled, the flavors are artificial, just like being back in school. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/hell.rsi - type: Icon @@ -1174,10 +841,6 @@ description: You just don't get it maaaan. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/hippiesdelightglass.rsi - type: Icon @@ -1190,10 +853,6 @@ description: A heated drink consisting melted chocolate and heated milk. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/hot_coco.rsi - type: Icon @@ -1205,11 +864,12 @@ name: Coffee description: Coffee is a brewed drink prepared from roasted seeds, commonly called coffee beans, of the coffee plant. components: - - type: Drink + - type: Solution + maxVol: 20 contents: reagents: - - ReagentId: chem.H2O - Quantity: 4 + - ReagentId: chem.Coffee + Quantity: 20 - type: Sprite sprite: Objects/Drinks/hot_coffee.rsi - type: Icon @@ -1222,10 +882,6 @@ description: Coffee and ice, refreshing and cool. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/icedcoffeeglass.rsi - type: Icon @@ -1238,10 +894,6 @@ description: The liquor cabinet, brought together in a delicious mix. Intended for middle-aged alcoholic women only. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/icedteaglass.rsi - type: Icon @@ -1254,10 +906,6 @@ description: A beer which is so cold the air around it freezes. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/iced_beerglass.rsi - type: Icon @@ -1269,11 +917,12 @@ name: Ice glass description: Water frozen into a solid state. components: - - type: Drink + - type: Solution + maxVol: 20 contents: reagents: - - ReagentId: chem.H2O - Quantity: 1 + - ReagentId: chem.Ice + Quantity: 20 - type: Sprite sprite: Objects/Drinks/iceglass.rsi - type: Icon @@ -1285,11 +934,12 @@ name: Irish car bomb description: Mmm, tastes like chocolate cake... components: - - type: Drink + - type: Solution + maxVol: 20 contents: reagents: - - ReagentId: chem.H2O - Quantity: 4 + - ReagentId: chem.IrishCarBomb + Quantity: 20 - type: Sprite sprite: Objects/Drinks/irishcarbomb.rsi - type: Icon @@ -1301,11 +951,12 @@ name: Irish coffee glass description: Coffee, and alcohol. More fun than a Mimosa to drink in the morning. components: - - type: Drink + - type: Solution + maxVol: 20 contents: reagents: - - ReagentId: chem.H2O - Quantity: 4 + - ReagentId: chem.IrishCoffee + Quantity: 20 - type: Sprite sprite: Objects/Drinks/irishcoffeeglass.rsi - type: Icon @@ -1317,11 +968,12 @@ name: Irish cream glass description: Whiskey-imbued cream, what else would you expect from the Irish. components: - - type: Drink + - type: Solution + maxVol: 20 contents: reagents: - - ReagentId: chem.H2O - Quantity: 4 + - ReagentId: chem.IrishCream + Quantity: 20 - type: Sprite sprite: Objects/Drinks/irishcreamglass.rsi - type: Icon @@ -1334,10 +986,6 @@ description: The hipster's cup components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/jar.rsi - type: Icon @@ -1350,10 +998,6 @@ description: '' components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/jar_metroid.rsi - type: Icon @@ -1366,10 +1010,6 @@ description: You can't really tell what this is. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/jar_what.rsi - type: Icon @@ -1381,11 +1021,12 @@ name: Kahlua glass description: A widely known, Mexican coffee-flavoured liqueur. components: - - type: Drink + - type: Solution + maxVol: 20 contents: reagents: - - ReagentId: chem.H2O - Quantity: 4 + - ReagentId: chem.Kahlua + Quantity: 20 - type: Sprite sprite: Objects/Drinks/kahluaglass.rsi - type: Icon @@ -1398,10 +1039,6 @@ description: Long live the guy who everyone had mistaken for a girl. Baka! components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/kiraspecial.rsi - type: Icon @@ -1414,10 +1051,6 @@ description: Drink using lemon juice, water, and a sweetener such as cane sugar or honey. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/lemonade.rsi - type: Icon @@ -1430,10 +1063,6 @@ description: Drink using lemon juice, water, and a sweetener such as cane sugar or honey. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/lemonadeglass.rsi - type: Icon @@ -1446,10 +1075,6 @@ description: '' components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/lemonglass.rsi - type: Icon @@ -1462,10 +1087,6 @@ description: Used to make lemonade, soft drinks, and cocktails. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/lemonjuice.rsi - type: Icon @@ -1478,10 +1099,6 @@ description: A tangy substance made of lime and lemon. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/lemonlime.rsi - type: Icon @@ -1494,10 +1111,6 @@ description: The sweet-sour juice of limes. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/limejuice.rsi - type: Icon @@ -1510,10 +1123,6 @@ description: A flask with a Lithium Atom symbol on it. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/lithiumflask.rsi - type: Icon @@ -1526,10 +1135,6 @@ description: The liquor cabinet, brought together in a delicious mix. Intended for middle-aged alcoholic women only. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/longislandicedteaglass.rsi - type: Icon @@ -1542,10 +1147,6 @@ description: The Detective's undercover drink of choice. He never could stomach gin... components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/manhattanglass.rsi - type: Icon @@ -1557,11 +1158,12 @@ name: The manly dorf glass description: Beer and Ale, brought together in a delicious mix. Intended for true men only. components: - - type: Drink + - type: Solution + maxVol: 20 contents: reagents: - - ReagentId: chem.H2O - Quantity: 4 + - ReagentId: chem.ManlyDorf + Quantity: 20 - type: Sprite sprite: Objects/Drinks/manlydorfglass.rsi - type: Icon @@ -1574,10 +1176,6 @@ description: On the rocks with salt on the rim. Arriba~! components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/margaritaglass.rsi - type: Icon @@ -1590,10 +1188,6 @@ description: Vodka with Gin. Not quite how 007 enjoyed it, but still delicious. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/martiniglass.rsi - type: Icon @@ -1606,10 +1200,6 @@ description: A Viking's drink, though a cheap one. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/meadglass.rsi - type: Icon @@ -1621,11 +1211,12 @@ name: Milk jug description: An opaque white liquid produced by the mammary glands of mammals. components: - - type: Drink + - type: Solution + maxVol: 20 contents: reagents: - - ReagentId: chem.H2O - Quantity: 4 + - ReagentId: chem.Milk + Quantity: 20 - type: Sprite sprite: Objects/Drinks/milk.rsi - type: Icon @@ -1638,10 +1229,6 @@ description: Sweet, cold beverage that is usually made from milk components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/milkshake.rsi - type: Icon @@ -1654,10 +1241,6 @@ description: '' components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/mojito.rsi - type: Icon @@ -1670,10 +1253,6 @@ description: A strong neurotoxin that puts the subject into a death-like state. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/neurotoxinglass.rsi - type: Icon @@ -1686,10 +1265,6 @@ description: Absolutely nothing. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/nothing.rsi - type: Icon @@ -1702,10 +1277,6 @@ description: Fortified dessert wine made from cabernet sauvignon, saperavi and other grapes. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/ntcahors.rsi - type: Icon @@ -1718,10 +1289,6 @@ description: Cola, cola never changes. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/nuka_colaglass.rsi - type: Icon @@ -1734,10 +1301,6 @@ description: Liquid extract of the orange tree fruit, produced by squeezing or reaming oranges. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/orangejuice.rsi - type: Icon @@ -1750,10 +1313,6 @@ description: Tequila with silver in it, a favorite of alcoholic women in the club scene. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/patronglass.rsi - type: Icon @@ -1766,10 +1325,6 @@ description: A tasty juice blended from various kinds of very deadly and toxic berries. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/poisonberryjuice.rsi - type: Icon @@ -1782,10 +1337,6 @@ description: A scientist's drink of choice, for pondering ways to blow up the station. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/proj_manhattanglass.rsi - type: Icon @@ -1798,10 +1349,6 @@ description: Is this even wine? Toxic! Hallucinogenic! Probably consumed in boatloads by your superiors! components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/pwineglass.rsi - type: Icon @@ -1814,10 +1361,6 @@ description: '' components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/rag.rsi - type: Icon @@ -1830,10 +1373,6 @@ description: '' components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/rag_lit.rsi - type: Icon @@ -1846,10 +1385,6 @@ description: '' components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/rag_small.rsi - type: Icon @@ -1862,10 +1397,6 @@ description: '' components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/rag_small_lit.rsi - type: Icon @@ -1878,10 +1409,6 @@ description: Just add 10ml water, self heats! A taste that reminds you of your school years. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/ramen.rsi - type: Icon @@ -1894,10 +1421,6 @@ description: The true Viking's drink! Even though it has a strange red color. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/red_meadglass.rsi - type: Icon @@ -1910,10 +1433,6 @@ description: The secret of the sanctuary of the Libarian... components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/rewriter.rsi - type: Icon @@ -1926,10 +1445,6 @@ description: Distilled alcoholic drink made from saltwater. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/rumglass.rsi - type: Icon @@ -1942,10 +1457,6 @@ description: A spicy Vodka! Might be a little hot for the little guys! components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/sbitenglass.rsi - type: Icon @@ -1958,10 +1469,6 @@ description: Vodka, mixed with plain ol' orange juice. The result is surprisingly delicious. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/screwdriverglass.rsi - type: Icon @@ -1974,10 +1481,6 @@ description: 'Comprised of: White soda, blue curacao, melon liquor.' components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/sdreamglass.rsi - type: Icon @@ -1990,10 +1493,6 @@ description: '' components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/shake-blue.rsi - type: Icon @@ -2007,10 +1506,6 @@ description: '' components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/shake-empty.rsi - type: Icon @@ -2023,10 +1518,6 @@ description: '' components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/shake-meat.rsi - type: Icon @@ -2039,10 +1530,6 @@ description: '' components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/shake-robo.rsi - type: Icon @@ -2055,10 +1542,6 @@ description: '' components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/shake-white.rsi - type: Icon @@ -2072,10 +1555,6 @@ description: '' components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/shaker.rsi - type: Icon @@ -2088,10 +1567,6 @@ description: A shiny metal flask. It appears to have a Greek symbol inscribed on it. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/shinyflask.rsi - type: Icon @@ -2104,10 +1579,6 @@ description: '' components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/shotglass.rsi - type: Icon @@ -2120,10 +1591,6 @@ description: A drink from Mime Heaven. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/silencerglass.rsi - type: Icon @@ -2136,10 +1603,6 @@ description: A blue-space beverage! components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/singulo.rsi - type: Icon @@ -2152,10 +1615,6 @@ description: A cold refreshment components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/snowwhite.rsi - type: Icon @@ -2168,10 +1627,6 @@ description: Water containing dissolved carbon dioxide gas. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/sodawater.rsi - type: Icon @@ -2184,10 +1639,6 @@ description: An opaque white liquid made from soybeans. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/soymilk.rsi - type: Icon @@ -2200,10 +1651,6 @@ description: A coffee drink made with espresso and steamed soy milk. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/soy_latte.rsi - type: Icon @@ -2216,10 +1663,6 @@ description: Tastes like a hull breach in your mouth. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/space-up_bottle.rsi - type: Icon @@ -2232,10 +1675,6 @@ description: Tastes like a hull breach in your mouth. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/space-up_glass.rsi - type: Icon @@ -2248,10 +1687,6 @@ description: Blows right through you like a space wind. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/space_mountain_wind_bottle.rsi - type: Icon @@ -2264,10 +1699,6 @@ description: Blows right through you like a space wind. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/space_mountain_wind_glass.rsi - type: Icon @@ -2279,11 +1710,12 @@ name: Syndicate bomb description: Tastes like terrorism! components: - - type: Drink + - type: Solution + maxVol: 20 contents: reagents: - - ReagentId: chem.H2O - Quantity: 4 + - ReagentId: chem.SyndicateBomb + Quantity: 20 - type: Sprite sprite: Objects/Drinks/syndicatebomb.rsi - type: Icon @@ -2295,11 +1727,12 @@ name: Teacup description: A plain white porcelain teacup. components: - - type: Drink + - type: Solution + maxVol: 20 contents: reagents: - - ReagentId: chem.H2O - Quantity: 4 + - ReagentId: chem.Tea + Quantity: 20 - type: Sprite sprite: Objects/Drinks/teacup.rsi state: icon-1 @@ -2317,11 +1750,12 @@ name: Tea glass description: Tasty black tea. Contains caffeine. components: - - type: Drink + - type: Solution + maxVol: 20 contents: reagents: - - ReagentId: chem.H2O - Quantity: 4 + - ReagentId: chem.Tea + Quantity: 20 - type: Sprite sprite: Objects/Drinks/teaglass.rsi - type: Icon @@ -2333,11 +1767,12 @@ name: Teapot # Short and stout description: An elegant teapot. It simply oozes class. components: - - type: Drink + - type: Solution + maxVol: 20 contents: reagents: - - ReagentId: chem.H2O - Quantity: 4 + - ReagentId: chem.Tea + Quantity: 20 - type: Sprite sprite: Objects/Drinks/teapot.rsi - type: Icon @@ -2350,10 +1785,6 @@ description: A strong and mildly flavoured, mexican produced spirit. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/tequillaglass.rsi - type: Icon @@ -2366,10 +1797,6 @@ description: Tequila and orange juice. Much like a Screwdriver, only Mexican~ components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/tequillasunriseglass.rsi - type: Icon @@ -2382,10 +1809,6 @@ description: A potent mixture of caffeine and alcohol. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/thirteen_loko_glass.rsi - type: Icon @@ -2398,10 +1821,6 @@ description: Made for a woman, strong enough for a man. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/threemileislandglass.rsi - type: Icon @@ -2414,10 +1833,6 @@ description: Juice made from tomatoes, usually used as a beverage, either plain or in cocktails components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/tomatojuice.rsi - type: Icon @@ -2430,10 +1845,6 @@ description: A carbonated soft drink in which quinine is dissolved. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/tonic.rsi - type: Icon @@ -2446,10 +1857,6 @@ description: This thing is ON FIRE! CALL THE DAMN SHUTTLE!" components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/toxinsspecialglass.rsi - type: Icon @@ -2462,10 +1869,6 @@ description: Keeping your drinks at the perfect temperature since 1892. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/vacuumflask.rsi - type: Icon @@ -2478,10 +1881,6 @@ description: Aromatized, fortified white wine flavored with various botanicals. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/vermouthglass.rsi - type: Icon @@ -2494,10 +1893,6 @@ description: For when a gin and tonic isn't russian enough. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/vodkatonicglass.rsi - type: Icon @@ -2510,10 +1905,6 @@ description: Stay hydrated components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/water.rsi - type: Icon @@ -2526,10 +1917,6 @@ description: Simple clean water of unknown origin. You think that maybe you dont want to know it. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/waterbottle.rsi - type: Icon @@ -2542,10 +1929,6 @@ description: Delicious juice made from watermelon. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/watermelon.rsi - type: Icon @@ -2558,10 +1941,6 @@ description: A paper water cup. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/water_cup.rsi state: icon-1 @@ -2579,11 +1958,12 @@ name: Whiskey cola glass description: Whiskey, mixed with cola. Surprisingly refreshing. components: - - type: Drink + - type: Solution + maxVol: 20 contents: reagents: - - ReagentId: chem.H2O - Quantity: 4 + - ReagentId: chem.WhiskeyCola + Quantity: 20 - type: Sprite sprite: Objects/Drinks/whiskeycolaglass.rsi - type: Icon @@ -2595,11 +1975,12 @@ name: Special blend whiskey glass description: Just when you thought regular station whiskey was good... This silky, amber goodness has to come along and ruin everything. components: - - type: Drink + - type: Solution + maxVol: 20 contents: reagents: - - ReagentId: chem.H2O - Quantity: 4 + - ReagentId: chem.Whiskey + Quantity: 20 - type: Sprite sprite: Objects/Drinks/whiskeyglass.rsi - type: Icon @@ -2612,10 +1993,6 @@ description: For the more refined griffon. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/whiskeysodaglass.rsi - type: Icon @@ -2628,10 +2005,6 @@ description: For the more refined griffon. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/whiskeysodaglass2.rsi - type: Icon @@ -2644,10 +2017,6 @@ description: That's just, like, your opinion, man... components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/whiterussianglass.rsi - type: Icon @@ -2659,11 +2028,12 @@ name: Wine glass description: An premium alchoholic beverage made from distilled grape juice. components: - - type: Drink + - type: Solution + maxVol: 20 contents: reagents: - - ReagentId: chem.H2O - Quantity: 4 + - ReagentId: chem.Wine + Quantity: 20 - type: Sprite sprite: Objects/Drinks/wineglass.rsi - type: Icon diff --git a/Resources/Prototypes/Entities/items/Consumables/drinks_bottles.yml b/Resources/Prototypes/Entities/items/Consumables/drinks_bottles.yml index bfc997e01b..c719d7e7f5 100644 --- a/Resources/Prototypes/Entities/items/Consumables/drinks_bottles.yml +++ b/Resources/Prototypes/Entities/items/Consumables/drinks_bottles.yml @@ -5,12 +5,6 @@ description: One sip of this and you just know you're gonna have a good time. components: - type: Drink - max_volume: 10 - spawn_on_finish: DrinkBottleAbsinthe - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 10 - type: Sprite sprite: Objects/Drinks/absinthebottle.rsi - type: Icon @@ -23,12 +17,6 @@ description: A bottle of 46 proof Emeraldine Melon Liquor. Sweet and light. components: - type: Drink - max_volume: 10 - spawn_on_finish: DrinkBottleAlcoClear - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 10 - type: Sprite sprite: Objects/Drinks/alco-green.rsi - type: Icon @@ -40,13 +28,12 @@ name: Magm-Ale description: A true dorf's drink of choice. components: - - type: Drink - max_volume: 10 - spawn_on_finish: DrinkBottleAle + - type: Solution + maxVol: 80 contents: reagents: - - ReagentId: chem.H2O - Quantity: 10 + - ReagentId: chem.Ale + Quantity: 80 - type: Sprite sprite: Objects/Drinks/alebottle.rsi - type: Icon @@ -59,12 +46,6 @@ description: A bottle filled with nothing components: - type: Drink - max_volume: 10 - spawn_on_finish: DrinkBottleAlcoClear - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 10 - type: Sprite sprite: Objects/Drinks/bottleofnothing.rsi - type: Icon @@ -76,13 +57,12 @@ name: Cognac bottle description: A sweet and strongly alchoholic drink, made after numerous distillations and years of maturing. You might as well not scream 'SHITCURITY' this time. components: - - type: Drink - max_volume: 10 - spawn_on_finish: DrinkBottleCognac + - type: Solution + maxVol: 80 contents: reagents: - - ReagentId: chem.H2O - Quantity: 10 + - ReagentId: chem.Cognac + Quantity: 80 - type: Sprite sprite: Objects/Drinks/cognacbottle.rsi - type: Icon @@ -95,12 +75,6 @@ description: A bottle of high quality gin, produced in the New London Space Station. components: - type: Drink - max_volume: 10 - spawn_on_finish: DrinkBottleGin - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 10 - type: Sprite sprite: Objects/Drinks/ginbottle.rsi - type: Icon @@ -113,12 +87,6 @@ description: 100 proof cinnamon schnapps, made for alcoholic teen girls on spring break. components: - type: Drink - max_volume: 10 - spawn_on_finish: DrinkBottleGoldschlager - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 10 - type: Sprite sprite: Objects/Drinks/goldschlagerbottle.rsi - type: Icon @@ -130,13 +98,12 @@ name: Kahlua bottle description: A widely known, Mexican coffee-flavoured liqueur. In production since 1936, HONK components: - - type: Drink - max_volume: 10 - spawn_on_finish: DrinkBottleKahlua + - type: Solution + maxVol: 80 contents: reagents: - ReagentId: chem.H2O - Quantity: 10 + Quantity: 80 - type: Sprite sprite: Objects/Drinks/kahluabottle.rsi - type: Icon @@ -149,12 +116,6 @@ description: Silver laced tequilla, served in space night clubs across the galaxy. components: - type: Drink - max_volume: 10 - spawn_on_finish: DrinkBottlePatron - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 10 - type: Sprite sprite: Objects/Drinks/patronbottle.rsi - type: Icon @@ -167,12 +128,6 @@ description: What a delightful packaging for a surely high quality wine! The vintage must be amazing! components: - type: Drink - max_volume: 10 - spawn_on_finish: DrinkBottlePoisonWine - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 10 - type: Sprite sprite: Objects/Drinks/pwinebottle.rsi - type: Icon @@ -185,12 +140,6 @@ description: This isn't just rum, oh no. It's practically GRIFF in a bottle. components: - type: Drink - max_volume: 10 - spawn_on_finish: DrinkBottleRum - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 10 - type: Sprite sprite: Objects/Drinks/rumbottle.rsi - type: Icon @@ -203,12 +152,6 @@ description: Made from premium petroleum distillates, pure thalidomide and other fine quality ingredients! components: - type: Drink - max_volume: 10 - spawn_on_finish: DrinkBottleTequila - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 10 - type: Sprite sprite: Objects/Drinks/tequillabottle.rsi - type: Icon @@ -221,12 +164,6 @@ description: Sweet, sweet dryness~ components: - type: Drink - max_volume: 10 - spawn_on_finish: DrinkBottleVermouth - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 10 - type: Sprite sprite: Objects/Drinks/vermouthbottle.rsi - type: Icon @@ -238,13 +175,12 @@ name: Vodka bottle description: Aah, vodka. Prime choice of drink AND fuel by Russians worldwide. components: - - type: Drink - max_volume: 10 - spawn_on_finish: DrinkBottleVodka + - type: Solution + maxVol: 80 contents: reagents: - - ReagentId: chem.H2O - Quantity: 10 + - ReagentId: chem.Vodka + Quantity: 80 - type: Sprite sprite: Objects/Drinks/vodkabottle.rsi - type: Icon @@ -256,13 +192,12 @@ name: Uncle Git's special reserve description: A premium single-malt whiskey, gently matured inside the tunnels of a nuclear shelter. TUNNEL WHISKEY RULES. components: - - type: Drink - max_volume: 10 - spawn_on_finish: DrinkBottleWhiskey + - type: Solution + maxVol: 80 contents: reagents: - - ReagentId: chem.H2O - Quantity: 10 + - ReagentId: chem.Whiskey + Quantity: 80 - type: Sprite sprite: Objects/Drinks/whiskeybottle.rsi - type: Icon @@ -274,13 +209,12 @@ name: Doublebearded bearded special wine bottle description: A faint aura of unease and asspainery surrounds the bottle. components: - - type: Drink - max_volume: 10 - spawn_on_finish: DrinkBottleWine + - type: Solution + maxVol: 80 contents: reagents: - - ReagentId: chem.H2O - Quantity: 10 + - ReagentId: chem.Wine + Quantity: 80 - type: Sprite sprite: Objects/Drinks/winebottle.rsi - type: Icon diff --git a/Resources/Prototypes/Entities/items/Consumables/drinks_cans.yml b/Resources/Prototypes/Entities/items/Consumables/drinks_cans.yml index 4d776b2446..ba497f7751 100644 --- a/Resources/Prototypes/Entities/items/Consumables/drinks_cans.yml +++ b/Resources/Prototypes/Entities/items/Consumables/drinks_cans.yml @@ -30,11 +30,12 @@ name: Space cola description: A refreshing beverage. components: - - type: Drink + - type: Solution + maxVol: 20 contents: reagents: - - ReagentId: chem.H2O - Quantity: 4 + - ReagentId: chem.Cola + Quantity: 20 - type: Sprite sprite: Objects/Drinks/cola.rsi - type: Icon @@ -62,10 +63,6 @@ description: '' components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/ice_tea_can.rsi - type: Icon @@ -93,10 +90,6 @@ description: You wanted ORANGE. It gave you Lemon Lime. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/lemon-lime.rsi - type: Icon @@ -124,10 +117,6 @@ description: '' components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/purple_can.rsi - type: Icon @@ -155,10 +144,6 @@ description: Blows right through you like a space wind. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/space_mountain_wind.rsi - type: Icon @@ -186,10 +171,6 @@ description: Tastes like a hull breach in your mouth. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/space-up.rsi - type: Icon @@ -217,10 +198,6 @@ description: The taste of a star in liquid form. And, a bit of tuna...? components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/starkist.rsi - type: Icon @@ -248,10 +225,6 @@ description: The MBO has advised crew members that consumption of Thirteen Loko may result in seizures, blindness, drunkeness, or even death. Please Drink Responsibly. components: - type: Drink - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 4 - type: Sprite sprite: Objects/Drinks/thirteen_loko.rsi - type: Icon diff --git a/Resources/Prototypes/Entities/items/Consumables/drinks_cups.yml b/Resources/Prototypes/Entities/items/Consumables/drinks_cups.yml index 904dd9dc1d..0b29b551fa 100644 --- a/Resources/Prototypes/Entities/items/Consumables/drinks_cups.yml +++ b/Resources/Prototypes/Entities/items/Consumables/drinks_cups.yml @@ -5,8 +5,11 @@ name: Base cup abstract: true components: + - type: Solution + maxVol: 20 + - type: Pourable + transferAmount: 5 - type: Drink - max_volume: 4 despawn_empty: false - type: Sound - type: Sprite @@ -20,8 +23,8 @@ name: Golden cup description: A golden cup components: - - type: Drink - max_volume: 10 + - type: Solution + maxVol: 10 - type: Sprite sprite: Objects/Drinks/golden_cup.rsi - type: Icon @@ -33,8 +36,8 @@ name: Insulated pitcher description: A stainless steel insulated pitcher. Everyone's best friend in the morning. components: - - type: Drink - max_volume: 15 + - type: Solution + maxVol: 15 - type: Sprite sprite: Objects/Drinks/pitcher.rsi state: icon-6 @@ -52,8 +55,8 @@ name: Mug description: A plain white mug. components: - - type: Drink - max_volume: 4 + - type: Solution + maxVol: 10 - type: Sprite sprite: Objects/Drinks/mug.rsi state: icon-3 @@ -71,8 +74,8 @@ name: Mug Black description: A sleek black mug. components: - - type: Drink - max_volume: 4 + - type: Solution + maxVol: 10 - type: Sprite sprite: Objects/Drinks/mug_black.rsi state: icon-3 @@ -90,8 +93,8 @@ name: Mug Blue description: A blue and black mug. components: - - type: Drink - max_volume: 4 + - type: Solution + maxVol: 10 - type: Sprite sprite: Objects/Drinks/mug_blue.rsi state: icon-3 @@ -109,8 +112,8 @@ name: Mug Green description: A pale green and pink mug. components: - - type: Drink - max_volume: 4 + - type: Solution + maxVol: 10 - type: Sprite sprite: Objects/Drinks/mug_green.rsi state: icon-3 @@ -128,8 +131,8 @@ name: Mug Heart description: A white mug, it prominently features a red heart. components: - - type: Drink - max_volume: 4 + - type: Solution + maxVol: 10 - type: Sprite sprite: Objects/Drinks/mug_heart.rsi state: icon-3 @@ -147,8 +150,8 @@ name: Mug Metal description: A metal mug. You're not sure which metal. components: - - type: Drink - max_volume: 4 + - type: Solution + maxVol: 10 - type: Sprite sprite: Objects/Drinks/mug_metal.rsi state: icon-3 @@ -166,8 +169,8 @@ name: Mug Moebius description: A mug with a Moebius Laboratories logo on it. Not even your morning coffee is safe from corporate advertising. components: - - type: Drink - max_volume: 4 + - type: Solution + maxVol: 10 - type: Sprite sprite: Objects/Drinks/mug_moebius.rsi state: icon-3 @@ -185,8 +188,8 @@ name: "#1 mug" description: "A white mug, it prominently features a #1." components: - - type: Drink - max_volume: 4 + - type: Solution + maxVol: 10 - type: Sprite sprite: Objects/Drinks/mug_one.rsi state: icon-3 @@ -204,8 +207,8 @@ name: Mug Rainbow description: A rainbow mug. The colors are almost as blinding as a welder. components: - - type: Drink - max_volume: 4 + - type: Solution + maxVol: 10 - type: Sprite sprite: Objects/Drinks/mug_rainbow.rsi state: icon-3 @@ -223,8 +226,8 @@ name: Mug Red description: A red and black mug. components: - - type: Drink - max_volume: 4 + - type: Solution + maxVol: 10 - type: Sprite sprite: Objects/Drinks/mug_red.rsi state: icon-3 diff --git a/Resources/Prototypes/Entities/items/Consumables/trash_drinks.yml b/Resources/Prototypes/Entities/items/Consumables/trash_drinks.yml index e180de55d2..cdba3d3ad6 100644 --- a/Resources/Prototypes/Entities/items/Consumables/trash_drinks.yml +++ b/Resources/Prototypes/Entities/items/Consumables/trash_drinks.yml @@ -10,13 +10,12 @@ state: icon - type: Icon state: icon + - type: Solution + maxVol: 10 + - type: Pourable + transferAmount: 5 - type: Drink despawn_empty: false - max_volume: 10 - contents: - reagents: - - ReagentId: chem.H2O - Quantity: 0 # Containers - type: entity @@ -89,19 +88,6 @@ - type: Icon sprite: Objects/TrashDrinks/ginbottle_empty.rsi -# Couldn't think of a nice place to put this -- type: entity - name: Empty glass - parent: DrinkBottleBase - id: DrinkEmptyGlass - components: - - type: Sprite - sprite: Objects/TrashDrinks/alebottle_empty.rsi - - type: Icon - sprite: Objects/TrashDrinks/alebottle_empty.rsi - - type: Solution - max_volume: 4 - - type: entity name: Goldschlager bottle parent: DrinkBottleBase diff --git a/Resources/Prototypes/Entities/items/chemistry.yml b/Resources/Prototypes/Entities/items/chemistry.yml index cca0065844..b13c4bae14 100644 --- a/Resources/Prototypes/Entities/items/chemistry.yml +++ b/Resources/Prototypes/Entities/items/chemistry.yml @@ -9,10 +9,11 @@ - type: Icon texture: Objects/Chemistry/chemicals.rsi/beaker.png - type: Solution + fillingState: beaker maxVol: 50 caps: 27 - type: Pourable - transferAmount: 5 + transferAmount: 5.0 - type: entity name: Large Beaker @@ -25,10 +26,11 @@ - type: Icon texture: Objects/Chemistry/chemicals.rsi/beakerlarge.png - type: Solution + fillingState: beakerlarge maxVol: 100 caps: 27 - type: Pourable - transferAmount: 5 + transferAmount: 5.0 - type: entity name: Dropper @@ -41,10 +43,12 @@ - type: Icon texture: Objects/Chemistry/chemicals.rsi/dropper.png - type: Solution + fillingState: dropper + fillingSteps: 2 maxVol: 5 caps: 19 - type: Pourable - transferAmount: 5 + transferAmount: 5.0 - type: entity name: Syringe @@ -53,10 +57,12 @@ id: Syringe components: - type: Sprite - texture: Objects/Chemistry/chemicals.rsi/syringeproj.png + texture: Objects/Chemistry/syringe.rsi/0.png - type: Icon - texture: Objects/Chemistry/chemicals.rsi/syringeproj.png + texture: Objects/Chemistry/syringe.rsi/0.png - type: Solution + fillingState: syringe + fillingSteps: 5 maxVol: 15 caps: 19 - type: Injector diff --git a/Resources/Prototypes/Entities/mobs/human.yml b/Resources/Prototypes/Entities/mobs/human.yml index 50f789e8e0..0815ecdd04 100644 --- a/Resources/Prototypes/Entities/mobs/human.yml +++ b/Resources/Prototypes/Entities/mobs/human.yml @@ -14,11 +14,15 @@ - type: Hunger - type: Thirst # Organs - - type: Stomach - maxVolume: 100 - digestionDelay: 20 + - type: Solution + maxVol: 250 + caps: 32 - type: Bloodstream - maxVolume: 250 + max_volume: 100 + - type: Stomach + max_volume: 250 + digestionDelay: 20 + - type: Inventory - type: Constructor @@ -147,8 +151,6 @@ hands: - left - right - # Organs - - type: Stomach - type: Inventory - type: Sprite diff --git a/Resources/Prototypes/Entities/mobs/observer.yml b/Resources/Prototypes/Entities/mobs/observer.yml index c99257b300..2a6b6d90b5 100644 --- a/Resources/Prototypes/Entities/mobs/observer.yml +++ b/Resources/Prototypes/Entities/mobs/observer.yml @@ -15,3 +15,12 @@ - type: Examiner DoRangeCheck: false - type: IgnorePause + - type: Ghost + - type: Sprite + netsync: false + drawdepth: Mobs + texture: Mob/observer.png + + - type: MovementSpeedModifier + baseSprintSpeed: 14 + baseWalkSpeed: 7 diff --git a/Resources/Prototypes/Reactions/drinks.yml b/Resources/Prototypes/Reactions/drinks.yml new file mode 100644 index 0000000000..d3dda3f121 --- /dev/null +++ b/Resources/Prototypes/Reactions/drinks.yml @@ -0,0 +1,103 @@ +- type: reaction + id: react.ManlyDorf + reactants: + chem.Beer: + amount: 1 + chem.Ale: + amount: 2 + products: + chem.ManlyDorf: 3 + +- type: reaction + id: react.CubaLibre + reactants: + chem.Cola: + amount: 1 + chem.Rum: + amount: 2 + products: + chem.CubaLibre: 3 + +- type: reaction + id: react.IrishCream + reactants: + chem.Cream: + amount: 1 + chem.Whiskey: + amount: 2 + products: + chem.IrishCream: 3 + +- type: reaction + id: react.IrishCoffee + reactants: + chem.IrishCream: + amount: 2 + chem.Coffee: + amount: 2 + products: + chem.IrishCoffee: 4 + +- type: reaction + id: react.IrishCarBomb + reactants: + chem.IrishCream: + amount: 1 + chem.Ale: + amount: 1 + products: + chem.IrishCarBomb: 2 + +- type: reaction + id: react.B52 + reactants: + chem.IrishCarBomb: + amount: 1 + chem.Kahlua: + amount: 1 + chem.Cognac: + amount: 1 + products: + chem.B52: 3 + +- type: reaction + id: react.AtomicBomb + reactants: + chem.B52: + amount: 10 + chem.U: + amount: 1 + products: + chem.AtomicBomb: 11 + +- type: reaction + id: react.WhiskeyCola + reactants: + chem.Whiskey: + amount: 2 + chem.Cola: + amount: 1 + products: + chem.WhiskeyCola: 3 + +- type: reaction + id: react.SyndicateBomb + reactants: + chem.WhiskeyCola: + amount: 1 + chem.Beer: + amount: 1 + products: + chem.SyndicateBomb: 2 + +- type: reaction + id: react.Antifreeze + reactants: + chem.Vodka: + amount: 2 + chem.Cream: + amount: 1 + chem.Ice: + amount: 1 + products: + chem.Antifreeze: 4 \ No newline at end of file diff --git a/Resources/Prototypes/Reagents/chemicals.yml b/Resources/Prototypes/Reagents/chemicals.yml index a5e29b8487..e8d0b18dd3 100644 --- a/Resources/Prototypes/Reagents/chemicals.yml +++ b/Resources/Prototypes/Reagents/chemicals.yml @@ -2,6 +2,7 @@ id: chem.Nutriment name: Nutriment desc: Generic nutrition + color: "#664330" metabolism: - !type:DefaultFood rate: 1 @@ -10,11 +11,13 @@ id: chem.H2SO4 name: Sulfuric Acid desc: A highly corrosive, oily, colorless liquid. + color: "#BF8C00" - type: reagent id: chem.H2O name: Water desc: A tasty colorless liquid. + color: "#808080" metabolism: - !type:DefaultDrink rate: 1 @@ -29,6 +32,7 @@ id: chem.Plasma name: Plasma desc: Funky, space-magic pixie dust. You probably shouldn't eat this, but we both know you will anyways. + color: "#500064" - type: reagent id: chem.Ethanol diff --git a/Resources/Prototypes/Reagents/drinks.yml b/Resources/Prototypes/Reagents/drinks.yml index eb804d8d8a..4cffd3be62 100644 --- a/Resources/Prototypes/Reagents/drinks.yml +++ b/Resources/Prototypes/Reagents/drinks.yml @@ -2,21 +2,126 @@ id: chem.Whiskey name: Whiskey desc: An alcoholic beverage made from fermented grain mash + color: "#664300" + spritePath: whiskeyglass.rsi - type: reagent id: chem.Ale name: Ale desc: A type of beer brewed using a warm fermentation method, resulting in a sweet, full-bodied and fruity taste. + color: "#664300" + spritePath: aleglass.rsi - type: reagent id: chem.Wine name: Wine desc: An alcoholic drink made from fermented grapes + color: "#7E4043" + spritePath: wineglass.rsi + +- type: reagent + id: chem.Beer + name: Beer + desc: A cold pint of pale lager. + color: "#664300" + spritePath: beerglass.rsi + +- type: reagent + id: chem.Vodka + name: Vodka + desc: The glass contain wodka. Xynta. + color: "#664300" + +- type: reagent + id: chem.Kahlua + name: Kahlua + desc: A widely known, Mexican coffee-flavoured liqueur. In production since 1936! + color: "#664300" + spritePath: kahluaglass.rsi + +- type: reagent + id: chem.Cognac + name: Cognac + desc: A sweet and strongly alcoholic drink, twice distilled and left to mature for several years. Classy as fornication. + color: "#AB3C05" + spritePath: cognacglass.rsi + +- type: reagent + id: chem.ManlyDorf + name: Manly Dorf + desc: A dwarfy concoction made from ale and beer. Intended for stout dwarves only. + color: "#664300" + spritePath: manlydorfglass.rsi + +- type: reagent + id: chem.CubaLibre + name: Cuba Libre + desc: A classic mix of rum and cola. + color: "#3E1B00" + spritePath: cubalibreglass.rsi + +- type: reagent + id: chem.IrishCarBomb + name: Irish Car Bomb + desc: A troubling mixture of irish cream and ale. + color: "#2E6671" + spritePath: irishcarbomb.rsi + +- type: reagent + id: chem.IrishCoffee + name: Irish Coffee + desc: Coffee served with irish cream. Regular cream just isn't the same! + color: "#664300" + spritePath: irishcoffeeglass.rsi + +- type: reagent + id: chem.IrishCream + name: Irish Cream + desc: Whiskey-imbued cream. What else could you expect from the Irish. + color: "#664300" + spritePath: irishcreamglass.rsi + +- type: reagent + id: chem.B52 + name: B-52 + desc: Coffee, irish cream, and cognac. You will get bombed. + color: "#664300" + spritePath: b52glass.rsi + +- type: reagent + id: chem.AtomicBomb + name: Atomic Bomb + desc: Nuclear proliferation never tasted so good. + color: "#666300" + spritePath: atomicbombglass.rsi + +- type: reagent + id: chem.WhiskeyCola + name: Whiskey Cola + desc: An innocent-looking mixture of cola and whiskey. Delicious. + color: "#3E1B00" + spritePath: whiskeycolaglass.rsi + +- type: reagent + id: chem.SyndicateBomb + name: Syndicate Bomb + desc: Somebody set us up the bomb! + color: "#2E6671" + spritePath: syndicatebomb.rsi + +- type: reagent + id: chem.Antifreeze + name: Antifreeze + desc: The ultimate refreshment. + color: "#664300" + spritePath: antifreeze.rsi + - type: reagent id: chem.Cola name: Cola desc: A sweet, carbonated soft drink. Caffeine free. + color: "#100800" metabolism: - !type:DefaultDrink rate: 1 @@ -25,6 +130,7 @@ id: chem.Coffee name: Coffee desc: A drink made from brewed coffee beans. Contains a moderate amount of caffeine. + color: "#664300" metabolism: - !type:DefaultDrink rate: 1 @@ -33,6 +139,25 @@ id: chem.Tea name: Tea desc: A made by boiling leaves of the tea tree, Camellia sinensis. + color: "#101000" metabolism: - !type:DefaultDrink rate: 1 + +- type: reagent + id: chem.Cream + name: Cream + desc: The fatty, still liquid part of milk. Why don't you mix this with sum scotch, eh? + color: "#DFD7AF" + metabolism: + - !type:DefaultDrink + rate: 1 + +- type: reagent + id: chem.Milk + name: Milk + desc: An opaque white liquid produced by the mammary glands of mammals. + color: "#DFDFDF" + metabolism: + - !type:DefaultDrink + rate: 1 \ No newline at end of file diff --git a/Resources/Prototypes/Reagents/elements.yml b/Resources/Prototypes/Reagents/elements.yml index 938a27a4c7..f60bb120d0 100644 --- a/Resources/Prototypes/Reagents/elements.yml +++ b/Resources/Prototypes/Reagents/elements.yml @@ -2,11 +2,13 @@ id: chem.H name: Hydrogen desc: A light, flammable gas. + color: "#808080" - type: reagent id: chem.O name: Oxygen desc: An oxidizing, colorless gas. + color: "#808080" - type: reagent id: chem.S @@ -36,6 +38,7 @@ id: chem.N name: Nitrogen desc: A colorless, odorless unreactive gas. Highly stable. + color: "#808080" - type: reagent id: chem.Fe @@ -47,6 +50,7 @@ id: chem.F name: Fluorine desc: A highly toxic pale yellow gas. Extremely reactive. + color: "#808080" - type: reagent id: chem.Si @@ -95,3 +99,9 @@ name: Sodium desc: A silvery-white alkali metal. Highly reactive in it's pure form. color: "#c6c8cc" + +- type: reagent + id: chem.U + name: Uranium + desc: A silvery-white metallic chemical element in the actinide series, weakly radioactive. + color: "#00ff06" \ No newline at end of file diff --git a/Resources/Prototypes/SoundCollections/glassbreak.yml b/Resources/Prototypes/SoundCollections/glassbreak.yml new file mode 100644 index 0000000000..ee3d9278bb --- /dev/null +++ b/Resources/Prototypes/SoundCollections/glassbreak.yml @@ -0,0 +1,6 @@ +- type: sound_collection + id: glassbreak + files: + - /Audio/effects/glassbreak1.ogg + - /Audio/effects/glassbreak2.ogg + - /Audio/effects/glassbreak3.ogg diff --git a/Resources/Textures/Buildings/smes.rsi/smes-oc0.png b/Resources/Textures/Buildings/smes.rsi/smes-oc0.png index 2a8a9cb961..2dd8b598ef 100644 Binary files a/Resources/Textures/Buildings/smes.rsi/smes-oc0.png and b/Resources/Textures/Buildings/smes.rsi/smes-oc0.png differ diff --git a/Resources/Textures/Buildings/smes.rsi/smes-oc1.png b/Resources/Textures/Buildings/smes.rsi/smes-oc1.png index 66095d016b..32bfb2c017 100644 Binary files a/Resources/Textures/Buildings/smes.rsi/smes-oc1.png and b/Resources/Textures/Buildings/smes.rsi/smes-oc1.png differ diff --git a/Resources/Textures/Buildings/smes.rsi/smes-oc2.png b/Resources/Textures/Buildings/smes.rsi/smes-oc2.png index 2b39f4a827..32bfb2c017 100644 Binary files a/Resources/Textures/Buildings/smes.rsi/smes-oc2.png and b/Resources/Textures/Buildings/smes.rsi/smes-oc2.png differ diff --git a/Resources/Textures/Buildings/smes.rsi/smes-og1.png b/Resources/Textures/Buildings/smes.rsi/smes-og1.png index 741ed22138..de43a4325e 100644 Binary files a/Resources/Textures/Buildings/smes.rsi/smes-og1.png and b/Resources/Textures/Buildings/smes.rsi/smes-og1.png differ diff --git a/Resources/Textures/Buildings/smes.rsi/smes-og2.png b/Resources/Textures/Buildings/smes.rsi/smes-og2.png index 0db9f1a6ef..fe80c8d645 100644 Binary files a/Resources/Textures/Buildings/smes.rsi/smes-og2.png and b/Resources/Textures/Buildings/smes.rsi/smes-og2.png differ diff --git a/Resources/Textures/Buildings/smes.rsi/smes-og3.png b/Resources/Textures/Buildings/smes.rsi/smes-og3.png index 30ea3f0722..0716a2643b 100644 Binary files a/Resources/Textures/Buildings/smes.rsi/smes-og3.png and b/Resources/Textures/Buildings/smes.rsi/smes-og3.png differ diff --git a/Resources/Textures/Buildings/smes.rsi/smes-og4.png b/Resources/Textures/Buildings/smes.rsi/smes-og4.png index 6ea122e0f4..ce6b70e2f0 100644 Binary files a/Resources/Textures/Buildings/smes.rsi/smes-og4.png and b/Resources/Textures/Buildings/smes.rsi/smes-og4.png differ diff --git a/Resources/Textures/Buildings/smes.rsi/smes-og5.png b/Resources/Textures/Buildings/smes.rsi/smes-og5.png index 63579e63ab..5b58c2358d 100644 Binary files a/Resources/Textures/Buildings/smes.rsi/smes-og5.png and b/Resources/Textures/Buildings/smes.rsi/smes-og5.png differ diff --git a/Resources/Textures/Buildings/smes.rsi/smes-op0.png b/Resources/Textures/Buildings/smes.rsi/smes-op0.png index 7a6e915b53..50bdab855c 100644 Binary files a/Resources/Textures/Buildings/smes.rsi/smes-op0.png and b/Resources/Textures/Buildings/smes.rsi/smes-op0.png differ diff --git a/Resources/Textures/Buildings/smes.rsi/smes-op1.png b/Resources/Textures/Buildings/smes.rsi/smes-op1.png index b98f33d5c6..ec45e860a8 100644 Binary files a/Resources/Textures/Buildings/smes.rsi/smes-op1.png and b/Resources/Textures/Buildings/smes.rsi/smes-op1.png differ diff --git a/Resources/Textures/Buildings/smes.rsi/smes-op2.png b/Resources/Textures/Buildings/smes.rsi/smes-op2.png index 55ce2f53f5..eb2e2ff6ff 100644 Binary files a/Resources/Textures/Buildings/smes.rsi/smes-op2.png and b/Resources/Textures/Buildings/smes.rsi/smes-op2.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/backpack1.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/backpack1.png new file mode 100644 index 0000000000..87af9277bb Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/backpack1.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/backpack2.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/backpack2.png new file mode 100644 index 0000000000..b77569d5ed Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/backpack2.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/backpackmob1.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/backpackmob1.png new file mode 100644 index 0000000000..1cc753cd78 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/backpackmob1.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/backpackmob2.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/backpackmob2.png new file mode 100644 index 0000000000..1a01ae51cf Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/backpackmob2.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/beaker1.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/beaker1.png new file mode 100644 index 0000000000..ceb623570e Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/beaker1.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/beaker2.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/beaker2.png new file mode 100644 index 0000000000..40209c2f66 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/beaker2.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/beaker3.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/beaker3.png new file mode 100644 index 0000000000..62c4528ae1 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/beaker3.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/beaker4.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/beaker4.png new file mode 100644 index 0000000000..489b48b78a Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/beaker4.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/beaker5.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/beaker5.png new file mode 100644 index 0000000000..ca719e883a Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/beaker5.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/beaker6.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/beaker6.png new file mode 100644 index 0000000000..581a50d15f Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/beaker6.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/beakerlarge1.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/beakerlarge1.png new file mode 100644 index 0000000000..5ecf243047 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/beakerlarge1.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/beakerlarge2.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/beakerlarge2.png new file mode 100644 index 0000000000..05e647f110 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/beakerlarge2.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/beakerlarge3.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/beakerlarge3.png new file mode 100644 index 0000000000..0a464113d7 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/beakerlarge3.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/beakerlarge4.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/beakerlarge4.png new file mode 100644 index 0000000000..c16054a21a Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/beakerlarge4.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/beakerlarge5.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/beakerlarge5.png new file mode 100644 index 0000000000..363e3a1bfc Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/beakerlarge5.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/beakerlarge6.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/beakerlarge6.png new file mode 100644 index 0000000000..64126589da Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/beakerlarge6.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-1-1.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-1-1.png new file mode 100644 index 0000000000..aaed076693 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-1-1.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-1-2.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-1-2.png new file mode 100644 index 0000000000..2243651f0d Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-1-2.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-1-3.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-1-3.png new file mode 100644 index 0000000000..a58b538a81 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-1-3.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-1-4.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-1-4.png new file mode 100644 index 0000000000..a71c865d58 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-1-4.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-1-5.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-1-5.png new file mode 100644 index 0000000000..971eadab5f Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-1-5.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-1-6.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-1-6.png new file mode 100644 index 0000000000..21e86f3d5f Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-1-6.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-2-1.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-2-1.png new file mode 100644 index 0000000000..ac1ecd5cff Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-2-1.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-2-2.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-2-2.png new file mode 100644 index 0000000000..c937cccb3a Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-2-2.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-2-3.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-2-3.png new file mode 100644 index 0000000000..116ffb9bd3 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-2-3.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-2-4.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-2-4.png new file mode 100644 index 0000000000..925bf0a422 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-2-4.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-2-5.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-2-5.png new file mode 100644 index 0000000000..6764263d74 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-2-5.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-2-6.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-2-6.png new file mode 100644 index 0000000000..d82ea80c1f Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-2-6.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-3-1.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-3-1.png new file mode 100644 index 0000000000..aaed076693 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-3-1.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-3-2.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-3-2.png new file mode 100644 index 0000000000..3704a1363d Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-3-2.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-3-3.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-3-3.png new file mode 100644 index 0000000000..4e36539ba7 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-3-3.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-3-4.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-3-4.png new file mode 100644 index 0000000000..ee9ef2c432 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-3-4.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-3-5.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-3-5.png new file mode 100644 index 0000000000..6cb238455c Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-3-5.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-3-6.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-3-6.png new file mode 100644 index 0000000000..cbb8b6983f Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-3-6.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-4-1.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-4-1.png new file mode 100644 index 0000000000..aaed076693 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-4-1.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-4-2.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-4-2.png new file mode 100644 index 0000000000..6e62bae22c Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-4-2.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-4-3.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-4-3.png new file mode 100644 index 0000000000..dd1a77ce62 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-4-3.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-4-4.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-4-4.png new file mode 100644 index 0000000000..379c84c5bd Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-4-4.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-4-5.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-4-5.png new file mode 100644 index 0000000000..fb06bd67c8 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-4-5.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-4-6.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-4-6.png new file mode 100644 index 0000000000..e2563d0dec Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/bottle-4-6.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/dropper1.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/dropper1.png new file mode 100644 index 0000000000..999d333ab8 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/dropper1.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/glass1.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/glass1.png new file mode 100644 index 0000000000..d9ee584491 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/glass1.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/glass2.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/glass2.png new file mode 100644 index 0000000000..6d5dc6152c Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/glass2.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/glass3.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/glass3.png new file mode 100644 index 0000000000..2290157755 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/glass3.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/glass4.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/glass4.png new file mode 100644 index 0000000000..f6d616da8a Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/glass4.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/glass5.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/glass5.png new file mode 100644 index 0000000000..6d5d981ab6 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/glass5.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/glass6.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/glass6.png new file mode 100644 index 0000000000..384795d0b8 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/glass6.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/largebottle1.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/largebottle1.png new file mode 100644 index 0000000000..c00df70a59 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/largebottle1.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/largebottle2.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/largebottle2.png new file mode 100644 index 0000000000..33d921d8f2 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/largebottle2.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/largebottle3.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/largebottle3.png new file mode 100644 index 0000000000..1f964fe37a Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/largebottle3.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/largebottle4.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/largebottle4.png new file mode 100644 index 0000000000..61fd62c4db Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/largebottle4.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/largebottle5.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/largebottle5.png new file mode 100644 index 0000000000..7ef3ec2c2d Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/largebottle5.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/largebottle6.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/largebottle6.png new file mode 100644 index 0000000000..414c770679 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/largebottle6.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/meta.json b/Resources/Textures/Objects/Chemistry/fillings.rsi/meta.json new file mode 100644 index 0000000000..6c606829b1 --- /dev/null +++ b/Resources/Textures/Objects/Chemistry/fillings.rsi/meta.json @@ -0,0 +1 @@ +{"version": 1, "size": {"x": 32, "y": 32}, "states": [{"name": "backpack1", "directions": 1, "delays": [[1.0]]}, {"name": "backpack2", "directions": 1, "delays": [[1.0]]}, {"name": "backpackmob1", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "backpackmob2", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "beaker1", "directions": 1, "delays": [[1.0]]}, {"name": "beaker2", "directions": 1, "delays": [[1.0]]}, {"name": "beaker3", "directions": 1, "delays": [[1.0]]}, {"name": "beaker4", "directions": 1, "delays": [[1.0]]}, {"name": "beaker5", "directions": 1, "delays": [[1.0]]}, {"name": "beaker6", "directions": 1, "delays": [[1.0]]}, {"name": "beakerlarge1", "directions": 1, "delays": [[1.0]]}, {"name": "beakerlarge2", "directions": 1, "delays": [[1.0]]}, {"name": "beakerlarge3", "directions": 1, "delays": [[1.0]]}, {"name": "beakerlarge4", "directions": 1, "delays": [[1.0]]}, {"name": "beakerlarge5", "directions": 1, "delays": [[1.0]]}, {"name": "beakerlarge6", "directions": 1, "delays": [[1.0]]}, {"name": "bottle-1-1", "directions": 1, "delays": [[1.0]]}, {"name": "bottle-1-2", "directions": 1, "delays": [[1.0]]}, {"name": "bottle-1-3", "directions": 1, "delays": [[1.0]]}, {"name": "bottle-1-4", "directions": 1, "delays": [[1.0]]}, {"name": "bottle-1-5", "directions": 1, "delays": [[1.0]]}, {"name": "bottle-1-6", "directions": 1, "delays": [[1.0]]}, {"name": "bottle-2-1", "directions": 1, "delays": [[1.0]]}, {"name": "bottle-2-2", "directions": 1, "delays": [[1.0]]}, {"name": "bottle-2-3", "directions": 1, "delays": [[1.0]]}, {"name": "bottle-2-4", "directions": 1, "delays": [[1.0]]}, {"name": "bottle-2-5", "directions": 1, "delays": [[1.0]]}, {"name": "bottle-2-6", "directions": 1, "delays": [[1.0]]}, {"name": "bottle-3-1", "directions": 1, "delays": [[1.0]]}, {"name": "bottle-3-2", "directions": 1, "delays": [[1.0]]}, {"name": "bottle-3-3", "directions": 1, "delays": [[1.0]]}, {"name": "bottle-3-4", "directions": 1, "delays": [[1.0]]}, {"name": "bottle-3-5", "directions": 1, "delays": [[1.0]]}, {"name": "bottle-3-6", "directions": 1, "delays": [[1.0]]}, {"name": "bottle-4-1", "directions": 1, "delays": [[1.0]]}, {"name": "bottle-4-2", "directions": 1, "delays": [[1.0]]}, {"name": "bottle-4-3", "directions": 1, "delays": [[1.0]]}, {"name": "bottle-4-4", "directions": 1, "delays": [[1.0]]}, {"name": "bottle-4-5", "directions": 1, "delays": [[1.0]]}, {"name": "bottle-4-6", "directions": 1, "delays": [[1.0]]}, {"name": "dropper1", "directions": 1, "delays": [[1.0]]}, {"name": "glass1", "directions": 1, "delays": [[1.0]]}, {"name": "glass2", "directions": 1, "delays": [[1.0]]}, {"name": "glass3", "directions": 1, "delays": [[1.0]]}, {"name": "glass4", "directions": 1, "delays": [[1.0]]}, {"name": "glass5", "directions": 1, "delays": [[1.0]]}, {"name": "glass6", "directions": 1, "delays": [[1.0]]}, {"name": "largebottle1", "directions": 1, "delays": [[1.0]]}, {"name": "largebottle2", "directions": 1, "delays": [[1.0]]}, {"name": "largebottle3", "directions": 1, "delays": [[1.0]]}, {"name": "largebottle4", "directions": 1, "delays": [[1.0]]}, {"name": "largebottle5", "directions": 1, "delays": [[1.0]]}, {"name": "largebottle6", "directions": 1, "delays": [[1.0]]}, {"name": "smallbottle1", "directions": 1, "delays": [[1.0]]}, {"name": "smallbottle2", "directions": 1, "delays": [[1.0]]}, {"name": "smallbottle3", "directions": 1, "delays": [[1.0]]}, {"name": "smallbottle4", "directions": 1, "delays": [[1.0]]}, {"name": "smallbottle5", "directions": 1, "delays": [[1.0]]}, {"name": "smallbottle6", "directions": 1, "delays": [[1.0]]}, {"name": "syringe1", "directions": 1, "delays": [[1.0]]}, {"name": "syringe2", "directions": 1, "delays": [[1.0]]}, {"name": "syringe3", "directions": 1, "delays": [[1.0]]}, {"name": "syringe4", "directions": 1, "delays": [[1.0]]}, {"name": "vial1", "directions": 1, "delays": [[1.0]]}, {"name": "vial2", "directions": 1, "delays": [[1.0]]}, {"name": "vial3", "directions": 1, "delays": [[1.0]]}, {"name": "vial4", "directions": 1, "delays": [[1.0]]}, {"name": "vial5", "directions": 1, "delays": [[1.0]]}, {"name": "vial6", "directions": 1, "delays": [[1.0]]}]} \ No newline at end of file diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/smallbottle1.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/smallbottle1.png new file mode 100644 index 0000000000..bafddb3bdd Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/smallbottle1.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/smallbottle2.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/smallbottle2.png new file mode 100644 index 0000000000..344e45c81d Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/smallbottle2.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/smallbottle3.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/smallbottle3.png new file mode 100644 index 0000000000..7c6941e780 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/smallbottle3.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/smallbottle4.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/smallbottle4.png new file mode 100644 index 0000000000..149f77ad9a Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/smallbottle4.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/smallbottle5.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/smallbottle5.png new file mode 100644 index 0000000000..78ebfcc3a4 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/smallbottle5.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/smallbottle6.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/smallbottle6.png new file mode 100644 index 0000000000..4f392a2288 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/smallbottle6.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/syringe1.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/syringe1.png new file mode 100644 index 0000000000..74cfa72ab4 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/syringe1.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/syringe2.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/syringe2.png new file mode 100644 index 0000000000..74cfa72ab4 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/syringe2.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/syringe3.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/syringe3.png new file mode 100644 index 0000000000..9aa237b136 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/syringe3.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/syringe4.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/syringe4.png new file mode 100644 index 0000000000..d8b7e8a701 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/syringe4.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/vial1.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/vial1.png new file mode 100644 index 0000000000..3c1e8f3bf0 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/vial1.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/vial2.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/vial2.png new file mode 100644 index 0000000000..ee61d2a54b Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/vial2.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/vial3.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/vial3.png new file mode 100644 index 0000000000..997d2dae2f Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/vial3.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/vial4.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/vial4.png new file mode 100644 index 0000000000..fdd0af6277 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/vial4.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/vial5.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/vial5.png new file mode 100644 index 0000000000..900d85422b Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/vial5.png differ diff --git a/Resources/Textures/Objects/Chemistry/fillings.rsi/vial6.png b/Resources/Textures/Objects/Chemistry/fillings.rsi/vial6.png new file mode 100644 index 0000000000..d9e5ae957f Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/fillings.rsi/vial6.png differ diff --git a/Resources/Textures/Objects/Chemistry/syringe.rsi/0.png b/Resources/Textures/Objects/Chemistry/syringe.rsi/0.png new file mode 100644 index 0000000000..9cf92274d4 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/syringe.rsi/0.png differ diff --git a/Resources/Textures/Objects/Chemistry/syringe.rsi/1.png b/Resources/Textures/Objects/Chemistry/syringe.rsi/1.png new file mode 100644 index 0000000000..2d455682f4 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/syringe.rsi/1.png differ diff --git a/Resources/Textures/Objects/Chemistry/syringe.rsi/10.png b/Resources/Textures/Objects/Chemistry/syringe.rsi/10.png new file mode 100644 index 0000000000..35e22c6454 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/syringe.rsi/10.png differ diff --git a/Resources/Textures/Objects/Chemistry/syringe.rsi/15.png b/Resources/Textures/Objects/Chemistry/syringe.rsi/15.png new file mode 100644 index 0000000000..834ae95f9f Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/syringe.rsi/15.png differ diff --git a/Resources/Textures/Objects/Chemistry/syringe.rsi/5.png b/Resources/Textures/Objects/Chemistry/syringe.rsi/5.png new file mode 100644 index 0000000000..b898840c30 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/syringe.rsi/5.png differ diff --git a/Resources/Textures/Objects/Chemistry/syringe.rsi/autoinjector.png b/Resources/Textures/Objects/Chemistry/syringe.rsi/autoinjector.png new file mode 100644 index 0000000000..b54b0ee4cb Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/syringe.rsi/autoinjector.png differ diff --git a/Resources/Textures/Objects/Chemistry/syringe.rsi/autoinjector0.png b/Resources/Textures/Objects/Chemistry/syringe.rsi/autoinjector0.png new file mode 100644 index 0000000000..c19b268183 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/syringe.rsi/autoinjector0.png differ diff --git a/Resources/Textures/Objects/Chemistry/syringe.rsi/autoinjector_black.png b/Resources/Textures/Objects/Chemistry/syringe.rsi/autoinjector_black.png new file mode 100644 index 0000000000..e5c55e28ca Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/syringe.rsi/autoinjector_black.png differ diff --git a/Resources/Textures/Objects/Chemistry/syringe.rsi/autoinjector_black0.png b/Resources/Textures/Objects/Chemistry/syringe.rsi/autoinjector_black0.png new file mode 100644 index 0000000000..700d31bfcd Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/syringe.rsi/autoinjector_black0.png differ diff --git a/Resources/Textures/Objects/Chemistry/syringe.rsi/autoinjector_red.png b/Resources/Textures/Objects/Chemistry/syringe.rsi/autoinjector_red.png new file mode 100644 index 0000000000..bbf23c5f9e Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/syringe.rsi/autoinjector_red.png differ diff --git a/Resources/Textures/Objects/Chemistry/syringe.rsi/autoinjector_red0.png b/Resources/Textures/Objects/Chemistry/syringe.rsi/autoinjector_red0.png new file mode 100644 index 0000000000..cc36291fba Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/syringe.rsi/autoinjector_red0.png differ diff --git a/Resources/Textures/Objects/Chemistry/syringe.rsi/borghypo.png b/Resources/Textures/Objects/Chemistry/syringe.rsi/borghypo.png new file mode 100644 index 0000000000..e96d92d793 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/syringe.rsi/borghypo.png differ diff --git a/Resources/Textures/Objects/Chemistry/syringe.rsi/borghypo_s.png b/Resources/Textures/Objects/Chemistry/syringe.rsi/borghypo_s.png new file mode 100644 index 0000000000..4adc13c448 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/syringe.rsi/borghypo_s.png differ diff --git a/Resources/Textures/Objects/Chemistry/syringe.rsi/broken.png b/Resources/Textures/Objects/Chemistry/syringe.rsi/broken.png new file mode 100644 index 0000000000..aee78e21a5 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/syringe.rsi/broken.png differ diff --git a/Resources/Textures/Objects/Chemistry/syringe.rsi/combat_hypo.png b/Resources/Textures/Objects/Chemistry/syringe.rsi/combat_hypo.png new file mode 100644 index 0000000000..022d0a1988 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/syringe.rsi/combat_hypo.png differ diff --git a/Resources/Textures/Objects/Chemistry/syringe.rsi/draw.png b/Resources/Textures/Objects/Chemistry/syringe.rsi/draw.png new file mode 100644 index 0000000000..1bc212413e Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/syringe.rsi/draw.png differ diff --git a/Resources/Textures/Objects/Chemistry/syringe.rsi/hypo.png b/Resources/Textures/Objects/Chemistry/syringe.rsi/hypo.png new file mode 100644 index 0000000000..cab29e5b0a Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/syringe.rsi/hypo.png differ diff --git a/Resources/Textures/Objects/Chemistry/syringe.rsi/inject.png b/Resources/Textures/Objects/Chemistry/syringe.rsi/inject.png new file mode 100644 index 0000000000..e03bd1d144 Binary files /dev/null and b/Resources/Textures/Objects/Chemistry/syringe.rsi/inject.png differ diff --git a/Resources/Textures/Objects/Chemistry/syringe.rsi/meta.json b/Resources/Textures/Objects/Chemistry/syringe.rsi/meta.json new file mode 100644 index 0000000000..a07d2954f6 --- /dev/null +++ b/Resources/Textures/Objects/Chemistry/syringe.rsi/meta.json @@ -0,0 +1 @@ +{"version": 1, "size": {"x": 32, "y": 32}, "states": [{"name": "0", "directions": 1, "delays": [[1.0]]}, {"name": "1", "directions": 1, "delays": [[1.0]]}, {"name": "10", "directions": 1, "delays": [[1.0]]}, {"name": "15", "directions": 1, "delays": [[1.0]]}, {"name": "5", "directions": 1, "delays": [[1.0]]}, {"name": "autoinjector", "directions": 1, "delays": [[1.0]]}, {"name": "autoinjector0", "directions": 1, "delays": [[1.0]]}, {"name": "autoinjector_black", "directions": 1, "delays": [[1.0]]}, {"name": "autoinjector_black0", "directions": 1, "delays": [[1.0]]}, {"name": "autoinjector_red", "directions": 1, "delays": [[1.0]]}, {"name": "autoinjector_red0", "directions": 1, "delays": [[1.0]]}, {"name": "borghypo", "directions": 1, "delays": [[1.0]]}, {"name": "borghypo_s", "directions": 1, "delays": [[1.0]]}, {"name": "broken", "directions": 1, "delays": [[1.0]]}, {"name": "combat_hypo", "directions": 1, "delays": [[1.0]]}, {"name": "draw", "directions": 1, "delays": [[1.0]]}, {"name": "hypo", "directions": 1, "delays": [[1.0]]}, {"name": "inject", "directions": 1, "delays": [[1.0]]}]} \ No newline at end of file diff --git a/Resources/keybinds.yml b/Resources/keybinds.yml index 7076f916eb..504ec81af9 100644 --- a/Resources/keybinds.yml +++ b/Resources/keybinds.yml @@ -23,9 +23,9 @@ binds: - function: MoveDown type: State key: S -#- function: Run -# type: State -# key: Shift +- function: Run + type: State + key: Shift - function: ShowEscapeMenu type: State key: Escape diff --git a/RobustToolbox b/RobustToolbox index a21bad1f1b..12fb54904a 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit a21bad1f1b423cd1d000eb6b69293c70614f9f15 +Subproject commit 12fb54904af95b7f2083fb51d649ffa3b2a817fd diff --git a/SpaceStation14.sln.DotSettings b/SpaceStation14.sln.DotSettings index 582298cec4..ee76b7776b 100644 --- a/SpaceStation14.sln.DotSettings +++ b/SpaceStation14.sln.DotSettings @@ -52,5 +52,6 @@ True True True + True True True