diff --git a/Content.Client/GameObjects/Components/Instruments/InstrumentBoundUserInterface.cs b/Content.Client/GameObjects/Components/Instruments/InstrumentBoundUserInterface.cs new file mode 100644 index 0000000000..bf5ab69d58 --- /dev/null +++ b/Content.Client/GameObjects/Components/Instruments/InstrumentBoundUserInterface.cs @@ -0,0 +1,36 @@ +using Content.Client.Instruments; +using Robust.Client.GameObjects.Components.UserInterface; +using Robust.Shared.ViewVariables; + +namespace Content.Client.GameObjects.Components.Instruments +{ + public class InstrumentBoundUserInterface : BoundUserInterface + { + [ViewVariables] + private InstrumentMenu _instrumentMenu; + + public InstrumentComponent Instrument { get; set; } + + public InstrumentBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) + { + } + + protected override void Open() + { + if (!Owner.Owner.TryGetComponent(out var instrument)) return; + + Instrument = instrument; + _instrumentMenu = new InstrumentMenu(this); + _instrumentMenu.OnClose += Close; + + _instrumentMenu.OpenCentered(); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (!disposing) return; + _instrumentMenu?.Dispose(); + } + } +} diff --git a/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs b/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs new file mode 100644 index 0000000000..6182016ea4 --- /dev/null +++ b/Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs @@ -0,0 +1,175 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using Content.Shared.GameObjects.Components.Instruments; +using OpenTK.Platform.Windows; +using Robust.Shared.GameObjects; +using Robust.Client.Audio.Midi; +using Robust.Client.GameObjects.EntitySystems; +using Robust.Client.Interfaces.Graphics; +using Robust.Client.Interfaces.UserInterface; +using Robust.Client.Reflection; +using Robust.Shared.Audio.Midi; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Network; +using Robust.Shared.Interfaces.Reflection; +using Robust.Shared.Interfaces.Serialization; +using Robust.Shared.Interfaces.Timing; +using Robust.Shared.IoC; +using Robust.Shared.Serialization; +using Robust.Shared.ViewVariables; +using Timer = Robust.Shared.Timers.Timer; + + +namespace Content.Client.GameObjects.Components.Instruments +{ + [RegisterComponent] + public class InstrumentComponent : SharedInstrumentComponent + { + /// + /// Called when a midi song stops playing. + /// + public event Action OnMidiPlaybackEnded; + +#pragma warning disable 649 + [Dependency] private IMidiManager _midiManager; + [Dependency] private IFileDialogManager _fileDialogManager; + [Dependency] private readonly IGameTiming _timing; +#pragma warning restore 649 + + private IMidiRenderer _renderer; + private int _instrumentProgram = 1; + + /// + /// A queue of MidiEvents to be sent to the server. + /// + private Queue _eventQueue = new Queue(); + + /// + /// Whether a midi song will loop or not. + /// + [ViewVariables(VVAccess.ReadWrite)] + public bool LoopMidi + { + get => _renderer.LoopMidi; + set => _renderer.LoopMidi = value; + } + + /// + /// Changes the instrument the midi renderer will play. + /// + [ViewVariables(VVAccess.ReadWrite)] + public int InstrumentProgram + { + get => _instrumentProgram; + set { + _instrumentProgram = value; + _renderer.MidiProgram = _instrumentProgram; + } + } + + /// + /// Whether there's a midi song being played or not. + /// + [ViewVariables] + 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 override void Initialize() + { + base.Initialize(); + IoCManager.InjectDependencies(this); + _renderer = _midiManager.GetNewRenderer(); + _renderer.MidiProgram = _instrumentProgram; + _renderer.TrackingEntity = Owner; + _renderer.OnMidiPlayerFinished += () => { OnMidiPlaybackEnded?.Invoke(); }; + } + + protected override void Shutdown() + { + base.Shutdown(); + _renderer?.Dispose(); + } + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + serializer.DataField(ref _instrumentProgram, "program", 1); + } + + public override void HandleMessage(ComponentMessage message, INetChannel netChannel = null, IComponent component = null) + { + base.HandleMessage(message, netChannel, component); + switch (message) + { + case InstrumentMidiEventMessage midiEventMessage: + // If we're the ones sending the MidiEvents, we ignore this message. + if (IsInputOpen || IsMidiOpen) break; + Timer.Spawn((int) (500 + _timing.CurTime.TotalMilliseconds - midiEventMessage.Timestamp), + () => _renderer.SendMidiEvent(midiEventMessage.MidiEvent)); + break; + + case InstrumentStopMidiMessage _: + _renderer.StopAllNotes(); + if(IsInputOpen) CloseInput(); + if(IsMidiOpen) CloseMidi(); + break; + } + } + + /// + public bool OpenInput() + { + if (_renderer.OpenInput()) + { + _renderer.OnMidiEvent += RendererOnMidiEvent; + return true; + } + + return false; + } + + /// + public bool CloseInput() + { + if (!_renderer.CloseInput()) return false; + _renderer.OnMidiEvent -= RendererOnMidiEvent; + return true; + + } + + /// + public bool OpenMidi(string filename) + { + if (!_renderer.OpenMidi(filename)) return false; + _renderer.OnMidiEvent += RendererOnMidiEvent; + return true; + + } + + /// + public bool CloseMidi() + { + if (!_renderer.CloseMidi()) return false; + _renderer.OnMidiEvent -= RendererOnMidiEvent; + return true; + + } + + /// + /// Called whenever the renderer receives a midi event. + /// + /// The received midi event + private void RendererOnMidiEvent(MidiEvent midiEvent) + { + SendNetworkMessage(new InstrumentMidiEventMessage(midiEvent, _timing.CurTime.TotalMilliseconds)); + } + } +} diff --git a/Content.Client/Instruments/InstrumentMenu.cs b/Content.Client/Instruments/InstrumentMenu.cs new file mode 100644 index 0000000000..6f00b9695f --- /dev/null +++ b/Content.Client/Instruments/InstrumentMenu.cs @@ -0,0 +1,197 @@ +using Content.Client.GameObjects.Components.Instruments; +using Robust.Client.Audio.Midi; +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.Log; +using Robust.Shared.Maths; + +namespace Content.Client.Instruments +{ + public class InstrumentMenu : SS14Window + { +#pragma warning disable 649 + [Dependency] private IMidiManager _midiManager; + [Dependency] private IFileDialogManager _fileDialogManager; +#pragma warning restore 649 + + private InstrumentBoundUserInterface _owner; + private Button midiLoopButton; + private Button midiStopButton; + private Button midiInputButton; + + protected override Vector2? CustomSize => (400, 150); + + public InstrumentMenu(InstrumentBoundUserInterface owner) + { + IoCManager.InjectDependencies(this); + Title = "Instrument"; + + _owner = owner; + + _owner.Instrument.OnMidiPlaybackEnded += InstrumentOnMidiPlaybackEnded; + + var margin = new MarginContainer() + { + SizeFlagsVertical = SizeFlags.FillExpand, + SizeFlagsHorizontal = SizeFlags.FillExpand, + MarginTop = 5f, + MarginLeft = 5f, + MarginRight = -5f, + MarginBottom = -5f, + }; + + margin.SetAnchorAndMarginPreset(LayoutPreset.Wide); + + var vBox = new VBoxContainer() + { + SizeFlagsVertical = SizeFlags.FillExpand, + SeparationOverride = 5, + }; + + vBox.SetAnchorAndMarginPreset(LayoutPreset.Wide); + + var hBoxTopButtons = new HBoxContainer() + { + SizeFlagsHorizontal = SizeFlags.FillExpand, + SizeFlagsVertical = SizeFlags.FillExpand, + SizeFlagsStretchRatio = 1, + Align = BoxContainer.AlignMode.Center + }; + + midiInputButton = new Button() + { + Text = "MIDI Input", + TextAlign = Button.AlignMode.Center, + SizeFlagsHorizontal = SizeFlags.FillExpand, + SizeFlagsStretchRatio = 1, + ToggleMode = true, + Pressed = _owner.Instrument.IsInputOpen, + }; + + midiInputButton.OnToggled += MidiInputButtonOnOnToggled; + + var topSpacer = new Control() + { + SizeFlagsHorizontal = SizeFlags.FillExpand, + SizeFlagsStretchRatio = 2, + }; + + var midiFileButton = new Button() + { + Text = "Open File", + TextAlign = Button.AlignMode.Center, + SizeFlagsHorizontal = SizeFlags.FillExpand, + SizeFlagsStretchRatio = 1, + }; + + midiFileButton.OnPressed += MidiFileButtonOnOnPressed; + + var hBoxBottomButtons = new HBoxContainer() + { + SizeFlagsHorizontal = SizeFlags.FillExpand, + SizeFlagsVertical = SizeFlags.FillExpand, + SizeFlagsStretchRatio = 1, + Align = BoxContainer.AlignMode.Center + }; + + midiLoopButton = new Button() + { + Text = "Loop", + TextAlign = Button.AlignMode.Center, + SizeFlagsHorizontal = SizeFlags.FillExpand, + SizeFlagsStretchRatio = 1, + ToggleMode = true, + Disabled = !_owner.Instrument.IsMidiOpen, + Pressed = _owner.Instrument.LoopMidi, + }; + + midiLoopButton.OnToggled += MidiLoopButtonOnOnToggled; + + var bottomSpacer = new Control() + { + SizeFlagsHorizontal = SizeFlags.FillExpand, + SizeFlagsStretchRatio = 2, + }; + + midiStopButton = new Button() + { + Text = "Stop", + TextAlign = Button.AlignMode.Center, + SizeFlagsHorizontal = SizeFlags.FillExpand, + SizeFlagsStretchRatio = 1, + Disabled = !_owner.Instrument.IsMidiOpen, + }; + + midiStopButton.OnPressed += MidiStopButtonOnPressed; + + hBoxBottomButtons.AddChild(midiLoopButton); + hBoxBottomButtons.AddChild(bottomSpacer); + hBoxBottomButtons.AddChild(midiStopButton); + + hBoxTopButtons.AddChild(midiInputButton); + hBoxTopButtons.AddChild(topSpacer); + hBoxTopButtons.AddChild(midiFileButton); + + vBox.AddChild(hBoxTopButtons); + vBox.AddChild(hBoxBottomButtons); + + margin.AddChild(vBox); + + Contents.AddChild(margin); + } + + private void InstrumentOnMidiPlaybackEnded() + { + MidiPlaybackSetButtonsDisabled(true); + } + + public void MidiPlaybackSetButtonsDisabled(bool disabled) + { + midiLoopButton.Disabled = disabled; + midiStopButton.Disabled = disabled; + } + + private async void MidiFileButtonOnOnPressed(BaseButton.ButtonEventArgs obj) + { + var filename = await _fileDialogManager.OpenFile(); + + if (filename == null) return; + + if (!_midiManager.IsMidiFile(filename)) + { + Logger.Warning($"Not a midi file! Chosen file: {filename}"); + return; + } + + if (!_owner.Instrument.OpenMidi(filename)) return; + MidiPlaybackSetButtonsDisabled(false); + if(midiInputButton.Pressed) + midiInputButton.Pressed = false; + } + + private void MidiInputButtonOnOnToggled(BaseButton.ButtonToggledEventArgs obj) + { + if (obj.Pressed) + { + MidiStopButtonOnPressed(null); + _owner.Instrument.OpenInput(); + } + else + _owner.Instrument.CloseInput(); + } + + private void MidiStopButtonOnPressed(BaseButton.ButtonEventArgs obj) + { + MidiPlaybackSetButtonsDisabled(true); + _owner.Instrument.CloseMidi(); + } + + private void MidiLoopButtonOnOnToggled(BaseButton.ButtonToggledEventArgs obj) + { + _owner.Instrument.LoopMidi = obj.Pressed; + } + } +} diff --git a/Content.Server/GameObjects/Components/GUI/ServerHandsComponent.cs b/Content.Server/GameObjects/Components/GUI/ServerHandsComponent.cs index 496e049eb7..4abc0e8224 100644 --- a/Content.Server/GameObjects/Components/GUI/ServerHandsComponent.cs +++ b/Content.Server/GameObjects/Components/GUI/ServerHandsComponent.cs @@ -145,6 +145,8 @@ namespace Content.Server.GameObjects item.Owner.Transform.LocalPosition = Vector2.Zero; } + _entitySystemManager.GetEntitySystem().HandSelectedInteraction(Owner, item.Owner); + return success; } diff --git a/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs b/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs new file mode 100644 index 0000000000..bd81840479 --- /dev/null +++ b/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs @@ -0,0 +1,126 @@ +using Content.Server.GameObjects.EntitySystems; +using Content.Shared.GameObjects.Components.Instruments; +using Robust.Server.GameObjects; +using Robust.Server.GameObjects.Components.UserInterface; +using Robust.Server.Interfaces.GameObjects; +using Robust.Server.Interfaces.Player; +using Robust.Shared.GameObjects; +using Robust.Shared.Interfaces.GameObjects; +using Robust.Shared.Interfaces.Network; +using Robust.Shared.Serialization; +using Robust.Shared.ViewVariables; + +namespace Content.Server.GameObjects.Components.Instruments +{ + [RegisterComponent] + [ComponentReference(typeof(IActivate))] + public class InstrumentComponent : SharedInstrumentComponent, + IDropped, IHandSelected, IHandDeselected, IActivate, IUse, IThrown + { + /// + /// The client channel currently playing the instrument, or null if there's none. + /// + private INetChannel _instrumentPlayer; + private bool _handheld; + + [ViewVariables] + private BoundUserInterface _userInterface; + + /// + /// Whether the instrument is an item which can be held or not. + /// + [ViewVariables] + public bool Handheld => _handheld; + + public override void Initialize() + { + base.Initialize(); + _userInterface = Owner.GetComponent().GetBoundUserInterface(InstrumentUiKey.Key); + _userInterface.OnClosed += UserInterfaceOnClosed; + } + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + serializer.DataField(ref _handheld, "handheld", false); + } + + public override void HandleMessage(ComponentMessage message, INetChannel netChannel = null, IComponent component = null) + { + base.HandleMessage(message, netChannel, component); + // If the client that sent the message isn't the client playing this instrument, we ignore it. + if (netChannel != _instrumentPlayer) return; + switch (message) + { + case InstrumentMidiEventMessage midiEventMsg: + SendNetworkMessage(midiEventMsg); + break; + } + } + + public void Dropped(DroppedEventArgs eventArgs) + { + SendNetworkMessage(new InstrumentStopMidiMessage()); + _instrumentPlayer = null; + _userInterface.CloseAll(); + } + + public void Thrown(ThrownEventArgs eventArgs) + { + SendNetworkMessage(new InstrumentStopMidiMessage()); + _instrumentPlayer = null; + _userInterface.CloseAll(); + } + + public void HandSelected(HandSelectedEventArgs eventArgs) + { + var session = eventArgs.User?.GetComponent()?.playerSession; + + if (session == null) return; + + _instrumentPlayer = session.ConnectedClient; + } + + public void HandDeselected(HandDeselectedEventArgs eventArgs) + { + SendNetworkMessage(new InstrumentStopMidiMessage()); + _userInterface.CloseAll(); + } + + public void Activate(ActivateEventArgs eventArgs) + { + if (Handheld || !eventArgs.User.TryGetComponent(out IActorComponent actor)) + return; + + if (_instrumentPlayer != null) + return; + + _instrumentPlayer = actor.playerSession.ConnectedClient; + OpenUserInterface(actor.playerSession); + } + + public bool UseEntity(UseEntityEventArgs eventArgs) + { + if (!eventArgs.User.TryGetComponent(out IActorComponent actor)) + return false; + + if(_instrumentPlayer == actor.playerSession.ConnectedClient) + OpenUserInterface(actor.playerSession); + return false; + } + + private void UserInterfaceOnClosed(ServerBoundUserInterfaceMessage obj) + { + if (!Handheld && obj.Session.ConnectedClient == _instrumentPlayer) + { + _instrumentPlayer = null; + SendNetworkMessage(new InstrumentStopMidiMessage()); + } + } + + private void OpenUserInterface(IPlayerSession session) + { + _userInterface.Open(session); + } + } +} diff --git a/Content.Server/GameObjects/Components/Mobs/DamageStates.cs b/Content.Server/GameObjects/Components/Mobs/DamageStates.cs index 03cf782205..dbf27240f5 100644 --- a/Content.Server/GameObjects/Components/Mobs/DamageStates.cs +++ b/Content.Server/GameObjects/Components/Mobs/DamageStates.cs @@ -57,6 +57,11 @@ namespace Content.Server.GameObjects return true; } + bool IActionBlocker.CanDrop() + { + return true; + } + bool IActionBlocker.CanEmote() { return true; @@ -103,6 +108,11 @@ namespace Content.Server.GameObjects return false; } + bool IActionBlocker.CanDrop() + { + return false; + } + bool IActionBlocker.CanEmote() { return false; @@ -169,6 +179,11 @@ namespace Content.Server.GameObjects return false; } + bool IActionBlocker.CanDrop() + { + return false; + } + bool IActionBlocker.CanEmote() { return false; diff --git a/Content.Server/GameObjects/Components/Mobs/SpeciesComponent.cs b/Content.Server/GameObjects/Components/Mobs/SpeciesComponent.cs index f290dc2985..e5e8b97fa9 100644 --- a/Content.Server/GameObjects/Components/Mobs/SpeciesComponent.cs +++ b/Content.Server/GameObjects/Components/Mobs/SpeciesComponent.cs @@ -101,6 +101,11 @@ namespace Content.Server.GameObjects bool IActionBlocker.CanSpeak() { return CurrentDamageState.CanSpeak(); + } + + bool IActionBlocker.CanDrop() + { + return CurrentDamageState.CanDrop(); } bool IActionBlocker.CanEmote() diff --git a/Content.Server/GameObjects/Components/Research/TechnologyDatabaseComponent.cs b/Content.Server/GameObjects/Components/Research/TechnologyDatabaseComponent.cs index 4a99a71277..1d17fbcb12 100644 --- a/Content.Server/GameObjects/Components/Research/TechnologyDatabaseComponent.cs +++ b/Content.Server/GameObjects/Components/Research/TechnologyDatabaseComponent.cs @@ -5,7 +5,7 @@ using Robust.Shared.GameObjects; namespace Content.Server.GameObjects.Components.Research { [RegisterComponent] - public class TechnologyDatabaseComponent : SharedTechnologyDatabaseComponent + public class TechnologyDatabaseComponent : SharedTechnologyDatabaseComponent { public override ComponentState GetComponentState() { diff --git a/Content.Server/GameObjects/EntitySystems/ActionBlockerSystem.cs b/Content.Server/GameObjects/EntitySystems/ActionBlockerSystem.cs index 0800ae829d..fa2c4f200d 100644 --- a/Content.Server/GameObjects/EntitySystems/ActionBlockerSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/ActionBlockerSystem.cs @@ -15,6 +15,8 @@ namespace Content.Server.GameObjects.EntitySystems bool CanSpeak(); + bool CanDrop(); + bool CanEmote(); } @@ -70,6 +72,16 @@ namespace Content.Server.GameObjects.EntitySystems return canspeak; } + public static bool CanDrop(IEntity entity) + { + bool candrop = true; + foreach (var actionblockercomponents in entity.GetAllComponents()) + { + candrop &= actionblockercomponents.CanDrop(); + } + return candrop; + } + public static bool CanEmote(IEntity entity) { bool canemote = true; diff --git a/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs b/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs index 7493eb7e3b..f469d65164 100644 --- a/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/Click/InteractionSystem.cs @@ -183,6 +183,60 @@ namespace Content.Server.GameObjects.EntitySystems public GridCoordinates ClickLocation { get; } } + /// + /// This interface gives components behavior when they're held on the selected hand. + /// + public interface IHandSelected + { + void HandSelected(HandSelectedEventArgs eventArgs); + } + + public class HandSelectedEventArgs : EventArgs + { + public HandSelectedEventArgs(IEntity user) + { + User = user; + } + + public IEntity User { get; } + } + + /// + /// This interface gives components behavior when they're held on a deselected hand. + /// + public interface IHandDeselected + { + void HandDeselected(HandDeselectedEventArgs eventArgs); + } + + public class HandDeselectedEventArgs : EventArgs + { + public HandDeselectedEventArgs(IEntity user) + { + User = user; + } + + public IEntity User { get; } + } + + /// + /// This interface gives components behavior when they're dropped by a mob. + /// + public interface IDropped + { + void Dropped(DroppedEventArgs eventArgs); + } + + public class DroppedEventArgs : EventArgs + { + public DroppedEventArgs(IEntity user) + { + User = user; + } + + public IEntity User { get; } + } + /// /// Governs interactions during clicking on entities /// @@ -512,8 +566,8 @@ namespace Content.Server.GameObjects.EntitySystems } /// - /// Activates the Use behavior of an object - /// Verifies that the user is capable of doing the use interaction first + /// Activates the Throw behavior of an object + /// Verifies that the user is capable of doing the throw interaction first /// public bool TryThrowInteraction(IEntity user, IEntity item) { @@ -568,6 +622,86 @@ namespace Content.Server.GameObjects.EntitySystems } } + /// + /// Activates the Dropped behavior of an object + /// Verifies that the user is capable of doing the drop interaction first + /// + public bool TryDroppedInteraction(IEntity user, IEntity item) + { + if (user == null || item == null || !ActionBlockerSystem.CanDrop(user)) return false; + + DroppedInteraction(user, item); + return true; + + } + + /// + /// Calls Dropped on all components that implement the IDropped interface + /// on an entity that has been dropped. + /// + public void DroppedInteraction(IEntity user, IEntity item) + { + var dropMsg = new DroppedMessage(user, item); + RaiseEvent(dropMsg); + if (dropMsg.Handled) + { + return; + } + + var comps = item.GetAllComponents().ToList(); + + // Call Land on all components that implement the interface + foreach (var comp in comps) + { + comp.Dropped(new DroppedEventArgs(user)); + } + } + + /// + /// Calls HandSelected on all components that implement the IHandSelected interface + /// on an item entity on a hand that has just been selected. + /// + public void HandSelectedInteraction(IEntity user, IEntity item) + { + var dropMsg = new HandSelectedMessage(user, item); + RaiseEvent(dropMsg); + if (dropMsg.Handled) + { + return; + } + + var comps = item.GetAllComponents().ToList(); + + // Call Land on all components that implement the interface + foreach (var comp in comps) + { + comp.HandSelected(new HandSelectedEventArgs(user)); + } + } + + /// + /// Calls HandDeselected on all components that implement the IHandDeselected interface + /// on an item entity on a hand that has just been deselected. + /// + public void HandDeselectedInteraction(IEntity user, IEntity item) + { + var dropMsg = new HandDeselectedMessage(user, item); + RaiseEvent(dropMsg); + if (dropMsg.Handled) + { + return; + } + + var comps = item.GetAllComponents().ToList(); + + // Call Land on all components that implement the interface + foreach (var comp in comps) + { + comp.HandDeselected(new HandDeselectedEventArgs(user)); + } + } + + /// /// Will have two behaviors, either "uses" the weapon at range on the entity if it is capable of accepting that action /// Or it will use the weapon itself on the position clicked, regardless of what was there @@ -883,6 +1017,90 @@ namespace Content.Server.GameObjects.EntitySystems } } + /// + /// Raised when an entity that was thrown lands. + /// + [PublicAPI] + public class DroppedMessage : EntitySystemMessage + { + /// + /// If this message has already been "handled" by a previous system. + /// + public bool Handled { get; set; } + + /// + /// Entity that dropped the item. + /// + public IEntity User { get; } + + /// + /// Item that was dropped. + /// + public IEntity Dropped { get; } + + public DroppedMessage(IEntity user, IEntity dropped) + { + User = user; + Dropped = dropped; + } + } + + /// + /// Raised when an entity item in a hand is selected. + /// + [PublicAPI] + public class HandSelectedMessage : EntitySystemMessage + { + /// + /// If this message has already been "handled" by a previous system. + /// + public bool Handled { get; set; } + + /// + /// Entity that owns the selected hand. + /// + public IEntity User { get; } + + /// + /// The item in question. + /// + public IEntity Item { get; } + + public HandSelectedMessage(IEntity user, IEntity item) + { + User = user; + Item = item; + } + } + + /// + /// Raised when an entity item in a hand is deselected. + /// + [PublicAPI] + public class HandDeselectedMessage : EntitySystemMessage + { + /// + /// If this message has already been "handled" by a previous system. + /// + public bool Handled { get; set; } + + /// + /// Entity that owns the deselected hand. + /// + public IEntity User { get; } + + /// + /// The item in question. + /// + public IEntity Item { get; } + + public HandDeselectedMessage(IEntity user, IEntity item) + { + User = user; + Item = item; + } + } + /// /// Raised when an entity is activated in the world. /// diff --git a/Content.Server/GameObjects/EntitySystems/HandsSystem.cs b/Content.Server/GameObjects/EntitySystems/HandsSystem.cs index f80f47ab6b..36f7309a95 100644 --- a/Content.Server/GameObjects/EntitySystems/HandsSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/HandsSystem.cs @@ -12,6 +12,7 @@ using Robust.Server.Interfaces.Player; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Systems; using Robust.Shared.Input; +using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Map; using Robust.Shared.Interfaces.Timing; using Robust.Shared.IoC; @@ -27,6 +28,7 @@ namespace Content.Server.GameObjects.EntitySystems { #pragma warning disable 649 [Dependency] private readonly IMapManager _mapManager; + [Dependency] private readonly IEntitySystemManager _entitySystemManager; #pragma warning restore 649 private const float ThrowForce = 1.5f; // Throwing force of mobs in Newtons @@ -94,7 +96,19 @@ namespace Content.Server.GameObjects.EntitySystems if (!TryGetAttachedComponent(session as IPlayerSession, out HandsComponent handsComp)) return; + var interactionSystem = IoCManager.Resolve().GetEntitySystem(); + + var oldItem = handsComp.GetActiveHand; + handsComp.SwapHands(); + + var newItem = handsComp.GetActiveHand; + + if(oldItem != null) + interactionSystem.HandDeselectedInteraction(handsComp.Owner, oldItem.Owner); + + if(newItem != null) + interactionSystem.HandSelectedInteraction(handsComp.Owner, newItem.Owner); } private bool HandleDrop(ICommonSession session, GridCoordinates coords, EntityUid uid) @@ -102,14 +116,19 @@ namespace Content.Server.GameObjects.EntitySystems var ent = ((IPlayerSession) session).AttachedEntity; if (ent == null || !ent.IsValid()) - { return false; - } if (!ent.TryGetComponent(out HandsComponent handsComp)) - { return false; - } + + if (handsComp.GetActiveHand == null) + return false; + + if (!_entitySystemManager.GetEntitySystem().TryDroppedInteraction(ent, handsComp.GetActiveHand.Owner)) + return false; + + if(handsComp.GetActiveHand != null && !_entitySystemManager.GetEntitySystem().TryDroppedInteraction(ent, handsComp.GetActiveHand.Owner)) + return false; if (coords.InRange(_mapManager, ent.Transform.GridPosition, InteractionSystem.InteractionRange)) { diff --git a/Content.Shared/GameObjects/Components/Instruments/SharedInstrumentComponent.cs b/Content.Shared/GameObjects/Components/Instruments/SharedInstrumentComponent.cs new file mode 100644 index 0000000000..aef625a057 --- /dev/null +++ b/Content.Shared/GameObjects/Components/Instruments/SharedInstrumentComponent.cs @@ -0,0 +1,44 @@ +using System; +using Robust.Shared.Audio.Midi; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization; + +namespace Content.Shared.GameObjects.Components.Instruments +{ + public class SharedInstrumentComponent : Component + { + public override string Name => "Instrument"; + public override uint? NetID => ContentNetIDs.INSTRUMENTS; + } + + + /// + /// This message is sent to the client to completely stop midi input and midi playback. + /// + [Serializable, NetSerializable] + public class InstrumentStopMidiMessage : ComponentMessage + { + } + + /// + /// This message carries a MidiEvent to be played on clients. + /// + [Serializable, NetSerializable] + public class InstrumentMidiEventMessage : ComponentMessage + { + public MidiEvent MidiEvent; + public double Timestamp; + + public InstrumentMidiEventMessage(MidiEvent midiEvent, double timestamp) + { + MidiEvent = midiEvent; + Timestamp = timestamp; + } + } + + [NetSerializable, Serializable] + public enum InstrumentUiKey + { + Key, + } +} diff --git a/Content.Shared/GameObjects/ContentNetIDs.cs b/Content.Shared/GameObjects/ContentNetIDs.cs index b909a664c8..76bed8164f 100644 --- a/Content.Shared/GameObjects/ContentNetIDs.cs +++ b/Content.Shared/GameObjects/ContentNetIDs.cs @@ -35,5 +35,6 @@ public const uint CARGO_ORDER_DATABASE = 1030; public const uint GALACTIC_MARKET = 1031; public const uint HAIR = 1032; + public const uint INSTRUMENTS = 1033; } } diff --git a/Resources/Prototypes/Entities/buildings/instruments.yml b/Resources/Prototypes/Entities/buildings/instruments.yml new file mode 100644 index 0000000000..73f933dcc8 --- /dev/null +++ b/Resources/Prototypes/Entities/buildings/instruments.yml @@ -0,0 +1,69 @@ +- type: entity + name: BaseInstrument + id: BaseInstrument + components: + - type: Instrument + handheld: false + + - type: Clickable + + - type: Collidable + shapes: + - !type:PhysShapeAabb + mask: 19 + layer: 16 + + - 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/items/Instruments.yml b/Resources/Prototypes/Entities/items/Instruments.yml new file mode 100644 index 0000000000..7bf613566f --- /dev/null +++ b/Resources/Prototypes/Entities/items/Instruments.yml @@ -0,0 +1,131 @@ +- type: entity + name: BaseHandheldInstrument + parent: BaseItem + id: BaseHandheldInstrument + components: + - type: Instrument + handheld: true + - type: UserInterface + interfaces: + - key: enum.InstrumentUiKey.Key + type: InstrumentBoundUserInterface + +- type: entity + name: Synthesizer + parent: BaseHandheldInstrument + id: SynthesizerInstrument + components: + - type: Instrument + program: 2 + - type: Sprite + texture: Objects/Instruments/musician.rsi/h_synthesizer.png + - type: Icon + texture: Objects/Instruments/musician.rsi/h_synthesizer.png + +- type: entity + name: Violin + parent: BaseHandheldInstrument + id: ViolinInstrument + components: + - type: Instrument + program: 40 + - type: Sprite + texture: Objects/Instruments/musician.rsi/violin.png + - type: Icon + texture: Objects/Instruments/musician.rsi/violin.png + +- type: entity + name: Trumpet + parent: BaseHandheldInstrument + id: TrumpetInstrument + components: + - type: Instrument + program: 56 + - type: Sprite + texture: Objects/Instruments/musician.rsi/trumpet.png + - type: Icon + texture: Objects/Instruments/musician.rsi/trumpet.png + +- type: entity + name: Electric Guitar + parent: BaseHandheldInstrument + id: ElectricGuitarInstrument + components: + - type: Instrument + program: 27 + - type: Sprite + texture: Objects/Instruments/musician.rsi/eguitar.png + - type: Icon + texture: Objects/Instruments/musician.rsi/eguitar.png + +- type: entity + name: Accordion + parent: BaseHandheldInstrument + id: AccordionInstrument + components: + - type: Instrument + program: 21 + - type: Sprite + texture: Objects/Instruments/musician.rsi/accordion.png + - type: Icon + texture: Objects/Instruments/musician.rsi/accordion.png + +- type: entity + name: Harmonica + parent: BaseHandheldInstrument + id: HarmonicaInstrument + components: + - type: Instrument + program: 22 + - type: Sprite + texture: Objects/Instruments/musician.rsi/harmonica.png + - type: Icon + texture: Objects/Instruments/musician.rsi/harmonica.png + +- type: entity + name: Recorder + parent: BaseHandheldInstrument + id: RecorderInstrument + components: + - type: Instrument + program: 74 + - type: Sprite + texture: Objects/Instruments/musician.rsi/recorder.png + - type: Icon + texture: Objects/Instruments/musician.rsi/recorder.png + +- type: entity + name: Trombone + parent: BaseHandheldInstrument + id: TromboneInstrument + components: + - type: Instrument + program: 57 + - type: Sprite + texture: Objects/Instruments/musician.rsi/trombone.png + - type: Icon + texture: Objects/Instruments/musician.rsi/trombone.png + +- type: entity + name: Saxophone + parent: BaseHandheldInstrument + id: SaxophoneInstrument + components: + - type: Instrument + program: 67 + - type: Sprite + texture: Objects/Instruments/musician.rsi/saxophone.png + - type: Icon + texture: Objects/Instruments/musician.rsi/saxophone.png + +- type: entity + name: Glockenspiel + parent: BaseHandheldInstrument + id: GlockenspielInstrument + components: + - type: Instrument + program: 9 + - type: Sprite + texture: Objects/Instruments/musician.rsi/glockenspiel.png + - type: Icon + texture: Objects/Instruments/musician.rsi/glockenspiel.png diff --git a/Resources/Textures/Objects/Instruments/musician.rsi/accordion.png b/Resources/Textures/Objects/Instruments/musician.rsi/accordion.png new file mode 100644 index 0000000000..39465deb6d Binary files /dev/null and b/Resources/Textures/Objects/Instruments/musician.rsi/accordion.png differ diff --git a/Resources/Textures/Objects/Instruments/musician.rsi/bike_horn.png b/Resources/Textures/Objects/Instruments/musician.rsi/bike_horn.png new file mode 100644 index 0000000000..ea9fa956f2 Binary files /dev/null and b/Resources/Textures/Objects/Instruments/musician.rsi/bike_horn.png differ diff --git a/Resources/Textures/Objects/Instruments/musician.rsi/drum.png b/Resources/Textures/Objects/Instruments/musician.rsi/drum.png new file mode 100644 index 0000000000..721f6893c7 Binary files /dev/null and b/Resources/Textures/Objects/Instruments/musician.rsi/drum.png differ diff --git a/Resources/Textures/Objects/Instruments/musician.rsi/drum_bongo.png b/Resources/Textures/Objects/Instruments/musician.rsi/drum_bongo.png new file mode 100644 index 0000000000..b78bb308d8 Binary files /dev/null and b/Resources/Textures/Objects/Instruments/musician.rsi/drum_bongo.png differ diff --git a/Resources/Textures/Objects/Instruments/musician.rsi/drum_makeshift.png b/Resources/Textures/Objects/Instruments/musician.rsi/drum_makeshift.png new file mode 100644 index 0000000000..7eb0a3e6a8 Binary files /dev/null and b/Resources/Textures/Objects/Instruments/musician.rsi/drum_makeshift.png differ diff --git a/Resources/Textures/Objects/Instruments/musician.rsi/eguitar.png b/Resources/Textures/Objects/Instruments/musician.rsi/eguitar.png new file mode 100644 index 0000000000..f0046b5e2e Binary files /dev/null and b/Resources/Textures/Objects/Instruments/musician.rsi/eguitar.png differ diff --git a/Resources/Textures/Objects/Instruments/musician.rsi/glockenspiel.png b/Resources/Textures/Objects/Instruments/musician.rsi/glockenspiel.png new file mode 100644 index 0000000000..6c87407abc Binary files /dev/null and b/Resources/Textures/Objects/Instruments/musician.rsi/glockenspiel.png differ diff --git a/Resources/Textures/Objects/Instruments/musician.rsi/guitar.png b/Resources/Textures/Objects/Instruments/musician.rsi/guitar.png new file mode 100644 index 0000000000..7bbb2c0b45 Binary files /dev/null and b/Resources/Textures/Objects/Instruments/musician.rsi/guitar.png differ diff --git a/Resources/Textures/Objects/Instruments/musician.rsi/h_synthesizer.png b/Resources/Textures/Objects/Instruments/musician.rsi/h_synthesizer.png new file mode 100644 index 0000000000..9a8e0adb67 Binary files /dev/null and b/Resources/Textures/Objects/Instruments/musician.rsi/h_synthesizer.png differ diff --git a/Resources/Textures/Objects/Instruments/musician.rsi/harmonica.png b/Resources/Textures/Objects/Instruments/musician.rsi/harmonica.png new file mode 100644 index 0000000000..878fd846b1 Binary files /dev/null and b/Resources/Textures/Objects/Instruments/musician.rsi/harmonica.png differ diff --git a/Resources/Textures/Objects/Instruments/musician.rsi/meta.json b/Resources/Textures/Objects/Instruments/musician.rsi/meta.json new file mode 100644 index 0000000000..966ed218b5 --- /dev/null +++ b/Resources/Textures/Objects/Instruments/musician.rsi/meta.json @@ -0,0 +1 @@ +{"version": 1, "size": {"x": 32, "y": 32}, "license": "CC-BY-SA-3.0", "copyright": "https://github.com/vgstation-coders/vgstation13 at 8d9c91e19cb52713c7f7f1804c2b6f7203f8d331", "states": [{"name": "accordion", "directions": 1, "delays": [[1.0]]}, {"name": "bike_horn", "directions": 1, "delays": [[1.0]]}, {"name": "drum", "directions": 1, "delays": [[1.0]]}, {"name": "drum_bongo", "directions": 1, "delays": [[1.0]]}, {"name": "drum_makeshift", "directions": 1, "delays": [[1.0]]}, {"name": "glockenspiel", "directions": 1, "delays": [[1.0]]}, {"name": "guitar", "directions": 1, "delays": [[1.0]]}, {"name": "harmonica", "directions": 1, "delays": [[1.0]]}, {"name": "minimoog", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "minimoog-broken", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "piano", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "piano-broken", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "recorder", "directions": 1, "delays": [[1.0]]}, {"name": "saxophone", "directions": 1, "delays": [[1.0]]}, {"name": "trombone", "directions": 1, "delays": [[1.0]]}, {"name": "violin", "directions": 1, "delays": [[1.0]]}, {"name": "xylophone", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}, {"name": "xylophone-broken", "directions": 4, "delays": [[1.0], [1.0], [1.0], [1.0]]}]} diff --git a/Resources/Textures/Objects/Instruments/musician.rsi/minimoog-broken.png b/Resources/Textures/Objects/Instruments/musician.rsi/minimoog-broken.png new file mode 100644 index 0000000000..b2de7d185e Binary files /dev/null and b/Resources/Textures/Objects/Instruments/musician.rsi/minimoog-broken.png differ diff --git a/Resources/Textures/Objects/Instruments/musician.rsi/minimoog.png b/Resources/Textures/Objects/Instruments/musician.rsi/minimoog.png new file mode 100644 index 0000000000..e88067b295 Binary files /dev/null and b/Resources/Textures/Objects/Instruments/musician.rsi/minimoog.png differ diff --git a/Resources/Textures/Objects/Instruments/musician.rsi/minimoogbroken.png b/Resources/Textures/Objects/Instruments/musician.rsi/minimoogbroken.png new file mode 100644 index 0000000000..71ca879106 Binary files /dev/null and b/Resources/Textures/Objects/Instruments/musician.rsi/minimoogbroken.png differ diff --git a/Resources/Textures/Objects/Instruments/musician.rsi/piano-broken.png b/Resources/Textures/Objects/Instruments/musician.rsi/piano-broken.png new file mode 100644 index 0000000000..b28e2fcd52 Binary files /dev/null and b/Resources/Textures/Objects/Instruments/musician.rsi/piano-broken.png differ diff --git a/Resources/Textures/Objects/Instruments/musician.rsi/piano.png b/Resources/Textures/Objects/Instruments/musician.rsi/piano.png new file mode 100644 index 0000000000..545386ff33 Binary files /dev/null and b/Resources/Textures/Objects/Instruments/musician.rsi/piano.png differ diff --git a/Resources/Textures/Objects/Instruments/musician.rsi/pianobroken.png b/Resources/Textures/Objects/Instruments/musician.rsi/pianobroken.png new file mode 100644 index 0000000000..210211f3e7 Binary files /dev/null and b/Resources/Textures/Objects/Instruments/musician.rsi/pianobroken.png differ diff --git a/Resources/Textures/Objects/Instruments/musician.rsi/recorder.png b/Resources/Textures/Objects/Instruments/musician.rsi/recorder.png new file mode 100644 index 0000000000..5c8d85dc5d Binary files /dev/null and b/Resources/Textures/Objects/Instruments/musician.rsi/recorder.png differ diff --git a/Resources/Textures/Objects/Instruments/musician.rsi/saxophone.png b/Resources/Textures/Objects/Instruments/musician.rsi/saxophone.png new file mode 100644 index 0000000000..2ef8ec312d Binary files /dev/null and b/Resources/Textures/Objects/Instruments/musician.rsi/saxophone.png differ diff --git a/Resources/Textures/Objects/Instruments/musician.rsi/synthesizer.png b/Resources/Textures/Objects/Instruments/musician.rsi/synthesizer.png new file mode 100644 index 0000000000..91c1c253de Binary files /dev/null and b/Resources/Textures/Objects/Instruments/musician.rsi/synthesizer.png differ diff --git a/Resources/Textures/Objects/Instruments/musician.rsi/trombone.png b/Resources/Textures/Objects/Instruments/musician.rsi/trombone.png new file mode 100644 index 0000000000..31b08e78f0 Binary files /dev/null and b/Resources/Textures/Objects/Instruments/musician.rsi/trombone.png differ diff --git a/Resources/Textures/Objects/Instruments/musician.rsi/trumpet.png b/Resources/Textures/Objects/Instruments/musician.rsi/trumpet.png new file mode 100644 index 0000000000..769a11aa56 Binary files /dev/null and b/Resources/Textures/Objects/Instruments/musician.rsi/trumpet.png differ diff --git a/Resources/Textures/Objects/Instruments/musician.rsi/violin.png b/Resources/Textures/Objects/Instruments/musician.rsi/violin.png new file mode 100644 index 0000000000..0dc5a6a05d Binary files /dev/null and b/Resources/Textures/Objects/Instruments/musician.rsi/violin.png differ diff --git a/Resources/Textures/Objects/Instruments/musician.rsi/xylophone-broken.png b/Resources/Textures/Objects/Instruments/musician.rsi/xylophone-broken.png new file mode 100644 index 0000000000..1c26e577bc Binary files /dev/null and b/Resources/Textures/Objects/Instruments/musician.rsi/xylophone-broken.png differ diff --git a/Resources/Textures/Objects/Instruments/musician.rsi/xylophone.png b/Resources/Textures/Objects/Instruments/musician.rsi/xylophone.png new file mode 100644 index 0000000000..4206cfca19 Binary files /dev/null and b/Resources/Textures/Objects/Instruments/musician.rsi/xylophone.png differ diff --git a/SpaceStation14.sln.DotSettings b/SpaceStation14.sln.DotSettings index 4e299bbb28..8ca2ed5f1d 100644 --- a/SpaceStation14.sln.DotSettings +++ b/SpaceStation14.sln.DotSettings @@ -12,10 +12,14 @@ UV <data /> <data><IncludeFilters /><ExcludeFilters><Filter ModuleMask="*.UnitTesting" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /></ExcludeFilters></data> + True True <data /> <data><IncludeFilters /><ExcludeFilters><Filter ModuleMask="Lidgren.Network" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /></ExcludeFilters></data> + True + True True True True + True True