From fedc0ad71c1ed89411ed37a568d899e6b48ea35e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Aguilera=20Puerto?= <6766154+Zumorica@users.noreply.github.com> Date: Mon, 25 Nov 2019 00:11:47 +0100 Subject: [PATCH] =?UTF-8?q?Adds=20playable=20instruments,=20IDropped,=20IH?= =?UTF-8?q?andSelected=20and=20IHandDese=E2=80=A6=20(#368)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Instrument test. * Midi stuff * Some more work * This actually works now! * update * Midi Audio works! * Lots of stuff, and cool interfaces for items * Update * Fix a few things * It just works * Move textures to another folder, remove placeholder description from instruments * Fix warning * Use renderer enum * Instruments now use DisposeRenderer method, and send MidiEvents as they receive them. Deletes InstrumentSystem whoo. * Fix incorrect sprite paths * Instruments take midi file size check into account when enabling/disabling midi playback buttons * Fix crash when pressing drop on empty hand. * Use new renderer return values for midi/input * Xylophones are no longer handheld instruments, fix their sprites. * Use new API * Remove nfluidsynth from solution * Timing information * Use IGameTiming.CurTime for timestamps instead --- .../InstrumentBoundUserInterface.cs | 36 +++ .../Instruments/InstrumentComponent.cs | 175 ++++++++++++++ Content.Client/Instruments/InstrumentMenu.cs | 197 ++++++++++++++++ .../Components/GUI/ServerHandsComponent.cs | 2 + .../Instruments/InstrumentComponent.cs | 126 ++++++++++ .../Components/Mobs/DamageStates.cs | 15 ++ .../Components/Mobs/SpeciesComponent.cs | 5 + .../Research/TechnologyDatabaseComponent.cs | 2 +- .../EntitySystems/ActionBlockerSystem.cs | 12 + .../EntitySystems/Click/InteractionSystem.cs | 222 +++++++++++++++++- .../GameObjects/EntitySystems/HandsSystem.cs | 27 ++- .../Instruments/SharedInstrumentComponent.cs | 44 ++++ Content.Shared/GameObjects/ContentNetIDs.cs | 1 + .../Entities/buildings/instruments.yml | 69 ++++++ .../Prototypes/Entities/items/Instruments.yml | 131 +++++++++++ .../Instruments/musician.rsi/accordion.png | Bin 0 -> 772 bytes .../Instruments/musician.rsi/bike_horn.png | Bin 0 -> 411 bytes .../Objects/Instruments/musician.rsi/drum.png | Bin 0 -> 550 bytes .../Instruments/musician.rsi/drum_bongo.png | Bin 0 -> 518 bytes .../musician.rsi/drum_makeshift.png | Bin 0 -> 520 bytes .../Instruments/musician.rsi/eguitar.png | Bin 0 -> 808 bytes .../Instruments/musician.rsi/glockenspiel.png | Bin 0 -> 551 bytes .../Instruments/musician.rsi/guitar.png | Bin 0 -> 926 bytes .../musician.rsi/h_synthesizer.png | Bin 0 -> 565 bytes .../Instruments/musician.rsi/harmonica.png | Bin 0 -> 780 bytes .../Instruments/musician.rsi/meta.json | 1 + .../musician.rsi/minimoog-broken.png | Bin 0 -> 3170 bytes .../Instruments/musician.rsi/minimoog.png | Bin 0 -> 2255 bytes .../musician.rsi/minimoogbroken.png | Bin 0 -> 1155 bytes .../Instruments/musician.rsi/piano-broken.png | Bin 0 -> 4493 bytes .../Instruments/musician.rsi/piano.png | Bin 0 -> 1973 bytes .../Instruments/musician.rsi/pianobroken.png | Bin 0 -> 1521 bytes .../Instruments/musician.rsi/recorder.png | Bin 0 -> 414 bytes .../Instruments/musician.rsi/saxophone.png | Bin 0 -> 707 bytes .../Instruments/musician.rsi/synthesizer.png | Bin 0 -> 779 bytes .../Instruments/musician.rsi/trombone.png | Bin 0 -> 373 bytes .../Instruments/musician.rsi/trumpet.png | Bin 0 -> 965 bytes .../Instruments/musician.rsi/violin.png | Bin 0 -> 389 bytes .../musician.rsi/xylophone-broken.png | Bin 0 -> 3703 bytes .../Instruments/musician.rsi/xylophone.png | Bin 0 -> 3333 bytes SpaceStation14.sln.DotSettings | 4 + 41 files changed, 1062 insertions(+), 7 deletions(-) create mode 100644 Content.Client/GameObjects/Components/Instruments/InstrumentBoundUserInterface.cs create mode 100644 Content.Client/GameObjects/Components/Instruments/InstrumentComponent.cs create mode 100644 Content.Client/Instruments/InstrumentMenu.cs create mode 100644 Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs create mode 100644 Content.Shared/GameObjects/Components/Instruments/SharedInstrumentComponent.cs create mode 100644 Resources/Prototypes/Entities/buildings/instruments.yml create mode 100644 Resources/Prototypes/Entities/items/Instruments.yml create mode 100644 Resources/Textures/Objects/Instruments/musician.rsi/accordion.png create mode 100644 Resources/Textures/Objects/Instruments/musician.rsi/bike_horn.png create mode 100644 Resources/Textures/Objects/Instruments/musician.rsi/drum.png create mode 100644 Resources/Textures/Objects/Instruments/musician.rsi/drum_bongo.png create mode 100644 Resources/Textures/Objects/Instruments/musician.rsi/drum_makeshift.png create mode 100644 Resources/Textures/Objects/Instruments/musician.rsi/eguitar.png create mode 100644 Resources/Textures/Objects/Instruments/musician.rsi/glockenspiel.png create mode 100644 Resources/Textures/Objects/Instruments/musician.rsi/guitar.png create mode 100644 Resources/Textures/Objects/Instruments/musician.rsi/h_synthesizer.png create mode 100644 Resources/Textures/Objects/Instruments/musician.rsi/harmonica.png create mode 100644 Resources/Textures/Objects/Instruments/musician.rsi/meta.json create mode 100644 Resources/Textures/Objects/Instruments/musician.rsi/minimoog-broken.png create mode 100644 Resources/Textures/Objects/Instruments/musician.rsi/minimoog.png create mode 100644 Resources/Textures/Objects/Instruments/musician.rsi/minimoogbroken.png create mode 100644 Resources/Textures/Objects/Instruments/musician.rsi/piano-broken.png create mode 100644 Resources/Textures/Objects/Instruments/musician.rsi/piano.png create mode 100644 Resources/Textures/Objects/Instruments/musician.rsi/pianobroken.png create mode 100644 Resources/Textures/Objects/Instruments/musician.rsi/recorder.png create mode 100644 Resources/Textures/Objects/Instruments/musician.rsi/saxophone.png create mode 100644 Resources/Textures/Objects/Instruments/musician.rsi/synthesizer.png create mode 100644 Resources/Textures/Objects/Instruments/musician.rsi/trombone.png create mode 100644 Resources/Textures/Objects/Instruments/musician.rsi/trumpet.png create mode 100644 Resources/Textures/Objects/Instruments/musician.rsi/violin.png create mode 100644 Resources/Textures/Objects/Instruments/musician.rsi/xylophone-broken.png create mode 100644 Resources/Textures/Objects/Instruments/musician.rsi/xylophone.png 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 0000000000000000000000000000000000000000..39465deb6d2b7caa8619f3bde78eaf86ff4e87d7 GIT binary patch literal 772 zcmV+f1N;1mP)2iGhWX zAt0*EDE<+fNGKp>bdI69%ia0xq=f;AJ}J8M-FM&j{O{M7l* zLV!*p9{{j!SHb%1`LwxngPN{Ib>ojd_N2l^{ibW>7H$kBivuw$m2#Grcw z0NC8#$?b=giZ%x$vBrwu?|qem+TK+x)wTSswJT-x-7<<8GNox7dtNzh?6l!qX+=->E0c9GlR?Zrx1&F%#?ee zPyv-W0507!Fhh!Fw1WVV<4AE*_Ba_MPR8he{sI8l@I!3)A2LO-|iZUfgi1vJ133d>mUzN`VLk&`WFilfT)07t%eK6=AY4=_{C&U+s97kF& z5m?0sj6|Lf)WYI(%@2_fy4(Xy1)UF={OUGD{xJsKBW!N(WO6lK3*OA&()X(sNp~%Q zJRdMjmiXXX@I4d1P6=v$xZDFkz7HmZ@?>fuwWvBaAr$>;1wCWNPr+OQz`_RrP%=%F z%*D4ODsXtv(n>Gl*6?IIYkruSlL&}LWD4AP`wq{YivWPzVZvjtPy+wS(@es{gBI>S z*p9IY46IFTuU`dkGSS`z}xbI_0000h&@h34hr#8u^PU!#u002ovPDHLk FV1n^^vC;ql literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..721f6893c74b391b237aa0a83bfb998f536c6edb GIT binary patch literal 550 zcmV+>0@?kEP)F{~){d}Jv@9RONNRcA_2kmSu*v8VT{pa9H(X>_Y z*f<-Wu4J5zy4jp7W;ANfbD?(;s}xPU)#`EiFiZFGHjtxbK!Cg19P3+ip=)32#|SVG z3I~|hfs1U{x8_Kv5`k;0<+1}WGagTK#%vIQy$c&2njW(2O5%T(*(;9oHy z!v%Q%pa(Fj#u4+QHpsIuv4Li1KsuEmzqd!;#|%J5bFaW5G?rFvE1q;6J$V+Ioq=n; zUrY?4^>O#J7FZv5SdAJtxG$Y_D&aerG8k~+VbW@WxaGV?ypqAX3T=USw&F>Q4Pv*ajP`L%4oXtByt3^4=8@gxnBKG}Y3-`3@(F+h&X84yVc z!vK*CiF$-#kTc>Yh@u2ze2npkqGXJV@#>A4I8_ZsI{>h`-R0w}$9VOIU6}TxCf)lz zbkQ_4!g^x-qvCcXG3EZubQPYU0|B z(ITBXdBVoOegDC)&p#)Q(KT`G?C}Bz@cLz3C_d4ECOmWT9IGo!Hnw+g$d~t@3dP4w zlyQ@oQUj`ow=WM@SC-g2I0WFqvv*VPD+8e1EKH?JmHw%I1L=-0@_Ziz)&Kwi07*qo IM6N<$f)sA?v;Y7A literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..7eb0a3e6a84fc94953cc86db03ae7d4def61bb2d GIT binary patch literal 520 zcmV+j0{8uiP)R`_s-Zex%X~0pCvrrFTd|Mzx>|&K}}6fP0jz#n&}HiyN~j+ zy{B(K8RuI!747yO(6XI{m^6tvf_a>-naZF+r;CK03X6e%YhoLp3DlvZHS zY38nL)7WYoi37B3hi(sqXaM9@Ca#rj2w@qCu9~N^2c*mp#H6#eZY9zrA{)m@nJtYi zH6Y~#*R_!{Lk1zPYgZaclL&xp91{cyLRbjVAPAC5152Cm&HF>nkN0V8wdaks_qw+b zd-x(mHLNoE{K?+ZCR}O&rvI_;d8A21%Xa?i^5Q8``$OMH$_c>gr$8ZuHBILG0Q5bF zY#h_~Ji0T%^W%N69Y*0P@W+@kv2q1e75>%9#fl7P=8EXt0ibyk0$jgygIhN@=j);G zW8JeDh0~|v+xH**`ubBht==pcbej3&o%VcPG*T2+eEbk=eLZMa;Naw9?e_i4eEaUk zg4k8gzFcaHgW)uM_3U8|pp0A<26*}Ivr$gt@mVxd#8kMZM)4c-IKb6~0+sgw0000< KMNUMnLSTZ?clzuA literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..f0046b5e2e3ec622712334fb41937dbefab5f0eb GIT binary patch literal 808 zcmV+@1K0eCP)GX492|p%sE|0Qiz0$s2ZuU&IOry{ zbP`*1C@l_GtcruJgQ?lX#Q1z2%)4sUdP$RfAzTjb?(_S7-}n82GAkm4iU^_fePy}f zA;Y-15kkG)-E~!z8}6T);%g>D$S{Zqp~_YOz^m0&d_Er?v$I&IrvSLh0gecvo>Wz_ zm(3DiUuQHJ6aEtvxm zA(ZfXc^Hoa+z5vfIiPJ%N=Zsej&FfO2^`S2R}T-9yaNtJc0k*HdVY>liXT$`6Andm zAR>f%Y8W((jF6Jj5gdx-fRnV}rh@l^fj~gPMY@I8OT)+r-zO&Wfj zxmQuaiBdGh<9rz(=R_&q?Cj+2=#4Mj1g@>ErKY9^0Mj&SX=&kIG>RK|Tvf$J zI$cl!S3>wV`1#V(^@?3C!r?HcY2pT&6A3?j3#BejRUf;$3ho|qA=Kfz32t_Da5yw{rE?9pEQ^hFy5Q~sDs|^tsZ0Z=zN$>+hrqKv(kBM0`gSpW`m!7Enx;P5=4 m7b^`f#@K5)&Oj;P0KWm{k#k<a8hic;Arbq>aRb+K}F7}2@aCQ zNKkO7y^J?lgia1!ba^|}_xgR`yIdl=dCzk1`+e`;_qor#??FOB!gD7TQB&_eya&Md zeX`lC(Y9-c)%~bB9w%_rIHv7+01W4H0JJ@i;ao1b>e|mAm$9PjE32`BefHu2=dA_+ z{po>VA$gUQrz;^TDeC*al0tTBhQLwdnA4Lc3v-_USXy4>y!G2yuoo%GL}}6xYI`1s z)qTZ%`cgo(Jr5`21R|R|yP*kZ$`f6s9)giT{lE>{PR3z*=?kYPO+J78mxvbAeT_`2 zKRwWMI<`Qn>eh5yNs)Z9V2EjvZFd)dg}FsTMk0l}2?SK#n%*P|;G)?A%r|l}PEeQ0 zNEs<)i9~P07!d&K2dfIkjaaHcTL&Q^m;L zjrXCh)~(nO_{#$cF|xA(KX}_F!(V!!UF37Bc>MMTcvIo|oGM0lA@*uNf%Di)Cnw<#+ z$MZSzrpo)1Srof^GugK zKE6;2RfR|^_zQ>br|t#By?_PFAnwCw7ccYUH4D4$!zU*Qk_wsOd$1}7pa5PNHqfL6 znRH5w?%#v#`nYBl%`#~7!HF~y^L#-NG}dOHc!+k#r6dH?C5M9Mh8DQ40-1D5939$? zRMpTU3Z0I?iwJB*1_VzH_5zmyIJ~zXr)r?tb=rz100lj~z_JQdD;1Kl7+tQ!dl#-y z>_3Jl2gE&)h0NIAK8_6b;m^%eEiKSdvH8aGDQGS;v)0;AfudXJzRyBLaJ6VKb$SA; z_ZgfBBzy^71o%ardB^74Uo`S%n}X)9D*L7armSG-23vfXIyS)8WQ?iP6BxZuQxQJN zvPG#@<-4D?26z#^n(UhkXeRY}6VK4mg~Is09xA587gHxmNfL8Wh{}Jm)&hsW4PilZ zxdc3%3UEz{I~AzOF(wc0A`+GP)vz!Z47OFQb%8Gz&|SJaqxeE%i|J65Wsq!=zDyZt zbLu2?o3Bl8)kAkfftguL>h6e&ON-wR1|FTr{H7>iJ2(-A9~R-O9l9F|!1ZT&df9S+ z%|mxnftguL;;ux?OJzObaFgr!-X AZvX%Q literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..9a8e0adb67cac0832d4cbee6fd90f3e0c00719e9 GIT binary patch literal 565 zcmV-50?Pe~P)EnUW@DTvK`hRH4IyRF>Qz zDyPUB#LOELV-N3E!_1)b-S5s^m@s0jR;#NR9}b6!ZjW}-6x{dy_PH!et%wir0SEAX zUvgwL0Xle}2%`eJ@b!9)=Xuh4mSu0=F(yFGJWW%AAduj;ZS(8apDB8V)JE5An zT6bN|Rd}A~Eq9QBz9u8!B6HVu0eayupaes0zXuL?I$+xNK?26Gy&JtH7y=(+`&|Nt z!iU)24ebUUTpT*lXaKg`?Ne~AfLrk6@rY$vEEWq%#BR5H7!JzEs0+`sjOB9Ku347F z<#Lf;{`%t+FP~pHolXzIq2qx1RqJLc=72Zz@b7=GuItAI^ucvvOu<2O3*Hmn3aIOv zvMi;AEXyRg`afp^4zDGc&*#_KY{qmtMR)gACrrWN)qy)#(=|(R@`+$#2E0K0=gnZi~;WzAVi4q7;u2UOiBWK={kk!00000NkvXXu0mjf Dqdx^| literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..878fd846b17302e6f5dca4cb6a50a5b39e9b44be GIT binary patch literal 780 zcmV+n1M~ceP)7H6vH5D9a~O3Uaf%DbRAi6(FYCL?Gb_K=c&$ML;wp zEOte43ILyPLISu~epUMw2<4Sv^BBgf4>aQbzFvBIpE3Dd1@PLZdYQ=0eGd!U3{lpP0MiUmJ345}Qnw0clFC;07Ga!UO zQJeq)J!dzZV@H7lv)LSW&hUVWtFoGE!*MJ|6WKRw$p+DI$0Nx;-$aru%aoVb8FoXi zIqvW4Wq3f1NDknttOj7MG>^5?JSwU#@pEMv*xK{)@psabN1Xz?MPuVFDPkr5-earP z8daos%!_x-OW)%zUcY(E?fM30=lrOu%AvzY{|L~FlHwB4)7=@9SaYlqr)B0aJLiY` z22@qW>3 z_gd!`7ZnE`hC0@wzFg(vT=sb`mbm~>Rh5ljNReFc_~@UR5<{;(qV;R06jRe>BaN1@II!?>X(d~FU zb~IsXMy}+anG&$s3$iS;Iye**0I;y|1z<;WZ4v9?4I-w>E44R7R(3ucgF~zqWdTrk z{o1C0T}1uQ0zyT&To-`Nn1MYJYtVHMJb&?20>t|IQrw6Spy4-``C9bf1{+@h0000< KMNUMnLSTYDv1K9v literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..b2de7d185ede48811b589ccf829a2a7f144d0d7d GIT binary patch literal 3170 zcmV-o44w0dP)8$8py*~d)32OR1E7=n^3M*BOeoX?m7EV zn6#p3Y19%&lR2*h{3H)gp+x+XSW9;9IZJl#IZJ;2%!dz_8Rv!`F87mI%dP)D)(`;L zw56jUdr}o*5;?RqYRS>l@kPzwN#^J0 zN$}!V0Dx!epOqZ4rBO?)Pi?}{)8`jGcVHxn(CkfYuU;=`KhR_ZCK3|*sd!qJo{ypE zeO+H)A?#Z*30MjoO=e=QVgzmfNngWi^+v({k@L5)y?Q;wv`T5lCKBlTY)mQvU5*Aq zMP#KxMNo{uNL18P1RyH7H<^7&}f{TU*KQ zS_5fqZ6)*b^JI6eflN1ndN)*!gISS)0EdYZeZ*Vfin zlI>l0G?|ITVj<<_<-}sK5SC>HSBu3$IyyS?_R)6JlSQ(0e5;ey5z>`RAr^}X+9Go$ z0D$)PcI-d=1!`-BN@-vH<8V5i+mTPEQUlP!CjNdBqDPquy+n17KFneK*Oh{ee~~%s;%&Py@EEaRx4Of@E%efz{gZPjdHCzFDFgwGVfaGmX!9|9XD`*q*M)sZ@139cE@`0D!wn*1M%f=$0C$b!o|;80A{EM2GPey+jF$-&|2g|as+wC|!IEcM__W}TVdV0{-)&`bkVYAtA z@V!r=(P#wp?1wCO-`w1ca5xO3(TGSA^YP(yI?>(T4FH0x&1M4t^!E0`YPG^pe1A+Z7{t4;?m&0<006*HoD$5b z*XuEpDwkbPVg>^vQ7o)8XWgJu{A;=%Pd+h(mQMpXy35A3SCWW835nNV_QH9fC>|gd zlcnJIoptbf1JM2LBc$({PXIu1_AzvW4-&ExKtFm`0`(OOStWq(VZ|1|2mac!5|B(L zS8nx+$8vUOW6VlHmI1eK-(JBbb2C7UE=L2oc=Zb8;)^s`TWj5lM<0E3NsN>A1D5Oe z=L%ph_8rf5ctAcLxmZXT5EzLn`uwH1{>TwbP5vGKyyV3I7I3=Bgab`RyxkYf1TvS2 z#YUEajASTfj)178QA>)8$}kmAE0%&IUp3+7t)JmfV{SaI$sBm#_|BXm><9gxFA80b z21Ti=T=-F^v6h>u{jnXs;G*bgGII=&3w!;q%t=wu^f-+t<)Ph)H%fkgVgyPZgvU~D3Rsd!p8Qp86xnZ)>9X~CGFI62Dq(Wx^-&E-EP ze+G}I4F?Y%1dx&)LvgV0ylQu?f$$G{A{gP??ao*r~L7{d2k_^+N) zQ>#b`h$y_Abs`QJxGqb0v;T-`?fM^XH_Drb!OqejL$mw+jL)N}!kl48x$ew-+qS z3JwosvM_uJpm05J&6&5)hwatt;dMdaa}gE|*Kt#-Gz@GzuhO zKtt4g(5KPd+zbFXa$Xou@caF!FdhIPEO-)w_Ei`UNF;#HN!Jid008XWySH!>K%EnM zZ?oAD3SC@4k!C;V!AOny%U1DTqz|qrj)y1ny@GiCYV}4*V&r|SA?&^1 zCyxt*7Vl#X5hugMbI^i^NF;)4(MH{#`PRmvA?)_eR&?D?z#!mSwT|kr1p_tL#`gC}x16IF-q_^WHBC zs*tjoK|VHZ=^z)*wMq_p;asaqN%bdPLrw|WY&HZ&qLR!|E+}Te2cHb%gHMKW?AS4w z)};lhX}(*U4_7X|ne{2>>Jx*%Ce0KdJ9doQr&27+4`3Ju8jVKMN8M6tq9_6*(Z$v2 zi7*vUO9~w58hlrnf+kA~<_GYLq^(vf=LyPznn56=xoHXOVrmD(pyQ|FY0ettGK16; z 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 dbP8oz~5wpg5T5J zJLjDH-S3`r?u-iu7U8_$#9CtOZ^oWfl^IDOQkmoxOYh-6jGLT1+poIdK&Ljyf} zP4xjzba^W-a69APRT6H1yta?TN}jB$adj?TQR4OWb(SC3?HUPlu@hZh{pwuW6&mQV z1x~Ew@wbS?7BZwC?;{in5e|nr-u#HzLWV|<02GeN!26T89OcR$$#gzND{X0`KjsW^i!OGFQlf z0(?Fnkx0ZGAKd#JN18Udbm0)6eRBy+1wPfyd`-CZ`|+}s?2K)@V_272^L9oBv9 z9j4_@bEArBO{g^sI)i~<-qUjDc;&%IJmY%t@cC`~yP-g-Bn$)s#1=AmYc`&p0Kn4S3y$yl z#l=PQIr&haZNJ9M%nVbP<4j$S?~K4!WL3>}FBoN5+=>m<*4845;?DcPRv33dR@KaE zs8ohVCoIl^o40RNJl1XR0)QWueLE@C=eX|)|~6=e7R!$iVAEcl2tV>d@#tzpG^}Ehb`}u4+WxMMs0po zRaMUVfK!~mIF6zymOfx07z`2!1UUOf`Bwd}2`Gw!s;VXXKr|XvwC8g`_BEXxcH4H0;$2`KanfK)2Q+P!P6-MePZp{Dy002ovPDHLkV1m*^CG7wJ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..b28e2fcd52a23fcddd239ade9980069d25fc23a0 GIT binary patch literal 4493 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 Hu0mjfpC_P; literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..210211f3e757b537c640cbb9866afe5860542954 GIT binary patch literal 1521 zcmVMmd-k?P-eZEIJR93McBn|yyR0_`rdj?|fh0 z=Y8Hk-}k|5K&UIIXb-^fV3%_0Xp^#=wZi`~c>I`8dGMqG0I47$6(qy=pwG;$82J2N zC7sHcw)-#he=&UIkXr#*THYX^%ahOLk!9KN96#n$_K5WWgj!^FQexK%gt~&t(sB}j zSX{?~)mf71`hRg3sQwFg`xc^z<~>u3f|D_bR(0pc$8C+2E-LZLAXK&J6Ny zr-N8rW@&i?fPsMl0Q&m+^m)4qLAmi-uvzUCH`cLP?R4*J+|nhKKu%%x;{+XTAzH3F&v!^6V_ zgF$_bu0W_OsF*jB%*NMDT0K`v5751@k%{S2mG^#^%*1pFL2&8c5}OvHu_6bXRl?V= zU$1h;tQpsm48XjR)Jv?~xXo&3V!A~C0dviebhd69-nCwl*tD>mFLLtaNlu?WO*Wg| zQlK))yuM}y3a^a0+fe`-4c=>*OIQ%fvMQ5FlgYs6JvJ`>E&)>jjNKSBDp2WZDksp> zVa!i0^Oq&3p4B_SZmYv?tK-`n5+A%-;^rSWi9{nrqLD2EQbDTeZ7eS1cga;m0Gi!a zgZDih0;$~Ayywot0IV0+bRq!f&!6YQg$sB*o-G1Tlg~^xs~x{fu4*Q!oIort^Uk4q zeXO{#&e4{w_TJN>&M8MzVUlxa2KDzwQ4Lu*9A;=}$dCi(P+6Ni(e0_J#>8~VkYKlx ziaxK>xQ5GDuT+f<3=C8$AOL*%@mn~YKEvek#YIv%L04riVWG2ilV*24GmlmPp{DoS zVe0-Ak!ZwN1=H8w1vEq)#{VtiqdrRC+ z!f*eWi9{oKJRaKLXg3mYITh+G4k8Z{IGjGDLK@C%^kL}%+Jx02>< zjjKs`pHxm@^0CB`y+Df^f4RLs#9Ugz z>qztc?Wpc;`58bRQ%RJUU?tXzYm~$mAg6m>4l_VLm*G-Fv;pWUbY-DzTDZq1A0A5{_ons~gsg19-hYq=Hn{ zFXj^9cdfB3spG5pOcjY{r(Ms1p`jt80-!b+?VOh`co1D8qDw>&1pNW%>n`c4;_ll?a)hYsR(MC8NX6(k8Ar!*H*?Ci_E2!jidA+&hb9sG&=n@To^x#PY z4z(&t7aW*L>fhoiJ01RY=F=r=A)C$W`+!EGX7I|pVL6%FaN>64C|O%{QeXX?1x(m& zb^3k)>Ru2C1Q;0^(Z>W(PpFy-l>{nVbFK5>vgD+=k*=aV!m7&RN2^peSNYf%p)#<*`60?{SzB1ErV z`*3#NRCR6I(-KggCZFl!p&FM8*b3WDhP32%)H{(@3%VlO#bn!i+v|YW*Q`_$$fTEa zyW1YiPaSPiw6N?3YsFRN_MLI%_MLHkO(wl$vRUoOvRq|+SL|z%de!x8JM3u*)Lio~ Xxg^d5P|m!h00000NkvXXu0mjfQi<`@ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..5c8d85dc5da5c384a6dcd5e033e9a9b231a3d27b GIT binary patch literal 414 zcmV;P0b%}$P)P#6|chRolEzb9{1orpZ9%m9QgS7{C70fFI-P0#l=-!lk35KxhFAI2c%Y$ z0HSYAX1AyToR;^+Tc;!HH+S6#p z%86(=M6>;h6|^x-ji%EkdwBBgL6uBE-mz)h#3CUKQ{(pjf!O>s8yWYhl?6y~A|B!R z?40GLMS!8ie+n2Br`J}1ukToCfJ*VN0=y6p72u`#$AEq@^gbXe#a{-DQtS$-mU5zc zc@02uXNyL&<*8Vr%f;BZ-F-MJxrnFm;$Hz-P0J#i@S0PUOwg3PC07*qo IM6N<$f}n1@nE(I) literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..2ef8ec312d53b1990b043e70ea58bb129dae6f78 GIT binary patch literal 707 zcmV;!0zCbRP)J&=%@YmsmK*P{fu%7OPMvw+zLpNZo2Z7soE*5GgGb zshfdP5v5m)C$y1t3DPErOaDN@Dn5tuLhf?8q-hEc`M_J=zu))1@ArG}piP?wn3_n5 z^jKV+#okK2zyPVl0P}N8{5<%J{cY`09=ZO8iZ6T{g zRU-h5CKGk{YDxfr3^tlf;5h3z&bp^xia0X?Ade!-4f6oq&^(nPMT{mBp?6240)%B* z_U1hE=I=fhLbyVVqagM0B~K`Gf#@hX0r+$mnOjKqRs7xPs^22nUuBO zlE5mc2plL(29`p03(E}ln{+1=eDqaDJ*pgt#bP3t%hep0Se7NE5I8QuPICwV-OxNP z^hR}nKo`R9fxzH?`z0gewz4pb80s2-i9b0IY44JpI2_4f{o={4uAx32Px;@N?yQoXP2o p$H|udVEU0iwGw~uHf{b3{sG)kD<+Nc&xim3002ovPDHLkV1hILNj(4n literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..91c1c253deb89e8aae91e909c420abef14035265 GIT binary patch literal 779 zcmV+m1N8ifP)T|g6Gt5$7BAdVnvusFUL=d`|MK(=A z23jgtkj$dF@g}x5kutOKfPCkCx!&`gKVIR>zU)7n1#o?RogW_`Z#{f zIlyExVKf>snM?pkl7!J{M3N-s;hUSAeAfajEkN5~FaRJ3f(mzfdfMvZrrqkeu3H9a znsR@C&&kONuIrMfDf+wa#NFbKDAK&iYz+N=cPzo^`j^v=_TCW-ZB^r2!fy-EmX(b znNFvT1vrj_l#(cVtGdri>1&fxmVOWfoSmJO7{_r1Eg?kic^;3Ck7xv_bDrn%^ZB{B zwmy3RiIm_!`<3Lr@AL5RP;xrf*eMb$JkRTC1lDrj4iAfKz8QTn{NFHTN*u>{o(I6| z>uaTpI@Y)sh9LkZhGDp;Vgt*w&pocW`h} z{t17C9e~Tr%btnAVhyJ|v2b0NFbvVCFVh^ra=F~NPbOz6rKI`jq-k1SXmYOW{z%ib zSA1KfY1-4MMVXxIx{W%vVFK@MI2`6msaz>Fzqq)VE2VOiD}0T)5Mr*Bnk%K|+F!>t z79fO>N-3$7lG+DyAUC-JwSat^fL1SIx7&>XY}?L{j*d)z3|QT6SI5?HILs*)x!5DU zR~4@H|99KA%lAFYvUA(E$61yEob&c7A%u{d_Q~X0CDuQzS}#o=$W3ugZLewa8dX-C zWtml+(cdIHGD%UDQRT+74bH5!O*KP)sB>c%8F1jF(L zb>9CO7#J8PPH0C-MCg26U_qVt|D8+g{#%+Dk(US)nzk}#W+wfA_4*A%W@ZvmmZIx_ ze)B9tx|bwgb$HW&kdPn>O^zc7sln@jckkXa2nh)?ynFW^rb=D0|NuU><66u`SX_&7X_nW6pVsVFroketooUl T2@t*(00000NkvXXu0mjfsot70 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..769a11aa56570a8f63df6a7bbedb3343e7c1a367 GIT binary patch literal 965 zcmV;$13LVPP)EX>4Tx04R}tkv&MmKpe$iQ%hB<4t9{@kfAzR5EXIMDionYs1;guFuC*#nlvOW zE{=k0!NHHks)LKOt`4q(Aou~|;_9U6A|?JWDYS_3;J6>}?mh0_0Yan9G^=YI(DbUA zO2oxXc2x|%q6ZQ5BZh#?EMrcRlJFc~_we!cF2=LG&;2?2)SSftpGZ8*46{nSK|H-# zH8}4RhgnfpiO-40Ou8WPBi9v|-#8Z>7IPWeK{ zWtH<5XRTagt$XqphI0DKGS_JiBY{OML4pVcHIz_BmHH|{C&faF_TwJ@h~t;YrI4!x zMvetkph0&0;D7MDTPr^~;U_~o^LOu_?pV2pEfx%m#cg^jswU5&WAWdB*Z-9eC zV6;Hl>mKj!?(FT~Gp+u90AFQtp9VLsQ~&?~24YJ`L;(K){{a7>y{D4^000SaNLh0L z04^f{04^f|c%?sf00007bV*G`2jdAE4lWe}^w!G&000?uMObu0Z*6U5Zgc=ca%Ew3 zWn>_CX>@2HM@dakSAh-}0004;Nkl1uKgfte5G(!_j;EJ7G0hB}Ez6Xh6gPqp0Zx&|?vC%L)j zz2`gUd+&FCK%>!U{u}aPQ~qVXvSM+Z@JfLFXt#7Ssh(o3#;dl{Kjt{$EllKOVyvf8 zy3uykEw@WhTYt)av|IKJ3|jW5lG8w=T;857(V=iQACWES_Kx|Ym>(KYCQ`{$%f8Ww z4}i{KEdRxa5Q6wf4uIX&9HDTQt!NcsCN|p+28vxCB|Y4pycKjPoF$Wa5CVXqnKS^`9<#42Wa`Q(A2)7~ zVIX*K0N*ADIP3eU+}w>>>J3WYYfV=it~(nr+#XZaw%Sm3G_;l3{*>YN=31QcxxOi# nD*7vS7Fwhmt83=-ps%YPbK?>q%1LMr zcOwFN-?AdRxPk&QQALn5(Fi#g3zzk8>j0_&6fKcbs)cW2P7%-}{aFS=h!jkZ4EiRz z3zR5T{W`q|*QweGP&RG|c_v{d5@S9hX>57@pT+M5un#cD=7tFvjlU9bnWbL)79yoF zo!1LCI-hH9lztDyhD?nB$JvBiDsV z*D2lSUwhfPrc>_1m4O`9{ZaRT7VDIrvF{8KK=f&|mY^j=EE(DbHo`6RGIU6Qh<^$8 z+%hB}JDevvGy@*jsw&wMvZ4#Dhyl@>Gelun$A?G86u;N=Gu()2iK?=5k;&-5fyP45L^~^7Zi|v`^R$5{)Pj{y_vaw zXU^<--*evgJkN8U^Stl#W8pcT<2nAAq54A;5D04RpNJMlH-0t2ze$Hj}UsD*6YoMhHKpCiSr57PggdP zsh)(QU%D9YXFg%~fdPle0`Mh5LP=497)Z5-3+Z{8>8YY@?{?;86^N7sqlk`5_Is8p zQmbsznkt&st@7$>TDOYQn!2Dv!T=WK%@R8|6!F=f{}e5)R+?=cTzud%JS+B<$!e3A zHCY&vHJBQoQ2xVhKXsZoBS(afpA79L9#ogBs0daBsDp$LGTA+dxOE(YLV zj(sIO2N!2$h+m(a3S(wNSUMkkbWoi5-)hvliFCGENzPqJMc#H6r9gH(9QfxC#p=sY zwQZePeK`_<7aU8-&DSw?dNlO?54xxbdb_*0cKRTb^q&Cmp!p1Ok&)Ouh}1+z@+3Y@ zgv7+-bXagU*Q%lul7u>X98Vk$tc}eofDccc9`b4trq{XD>)PpqB%|Sy^G9+*USwvr zhuT#lx2u2CfN?G4cebGV_(L+2CX*5uPwS4gQ~(<;eZY>KU2N1{>>q5D&;Q%8go3a= zl-*o|NalF8iI@fO^4Y^&nfnI6t2RL{%$u-_ zrMSm?&O;1rD_cQsKkdHE`$A$KAbi$x*O<$t#HCInEiW5@y7CI9%`}LfPCI&C7#3>} zwRLvInlR#3;LFskkxmg1%jU!d><^!{oYs`O!HflK8k;bW(7_aL;oC|EIlp;J@>3=x3(;Q%|LNh zhL8?$X~I9HOk>UXBwp)w?Jsw3C?YdGRfIHKtN>)pUkO0N;Ts%jY2mBX?Qj_qPc<@rGfqfq)?jLb7&j4ihdm%uo`!5NNWw|7{g&;U z&LJY=8Q0mu^h6uAY?Xz3yxPQ{y)&6A^L@GPHqbtXw;xN!&LHcYm8=Pg9P?OY!vMZ3 zzpN^#-6WbVRzK@moRuLwg;4s9Yx3IKUJ~8*h90}kcj%Cio3Hb-4X;k<06L)Gd@N0I z+m?VBGkZYW6gqcKK-(rSgMpX_Zw)Bkas!<KjdGqsXuER@QeEpbKt%sto|~CK2#<)8&o>v8 zu&r!`Y*437F>>o(H2}wP*?@TycCpxKAU-ahD|6of4+C8NMIy%c8d+>Kuwq}Cs-&pE zYZ0AKF^JMNm=X|=UsGF7PnG7q_YiOkKrRgJfPCx|r={^&j@ zYA$&__g3{yE}y$942fegHP|SxyzghgPqtWvUK7KFcr6=&^*i=aReMJj@Vh^>VzB^N zJ|iula7t_H2ozlKMC4mv3cB1@Qe!?z=gs}-G_g!cjAQTSmAqBFm8#l1D!on{&;XAF z#aS6*X13PLo0Z`Cyr3Ged`21xyAOEz*9p51(6sMU7{I@lAiAu60Y`ghgUa67Kz$Pw zynjt5;#o5f{cpcA@HNlp4QK;o=4WKO+WRuWkfMlyzUG^#oIUQ)R*&~GIT}n42SJG# zPJq!9p$RXGu%tq{t+rgna_{S(1k;`vRf0m)$$~L4g!FVUHe7?l`hPz;0X)ZZJjWk5 z6d9$=^i&~5a0iRyfqRQ<>FlPe_RgpozRdJgAq90k(QL6Qe(s<*>2+FBws$+=4kRdn zwBIgxf77U}`BE+;7}oFD$9LtI2Ys$0!v1368{*y7pL6@BJLoZ3T<<~i8ODU1ai zR&=Bo?4=Av_=>#2yQX$--?UQ@w#O@^CAqJ`aIdjV$@D`;v&E|Vc-wD;6yIAmA<_GW z+M}#@wky&+MPcz)TN^)|v-mL^jCsl9v)I~|Og}7`&CLLEoKHe!zLwCrb7*O`1{&rE zMH*l-RjKag9TOXM7ro+bX3a0;V5b+`?b?5ZCldPP{e&Gk}F)Bvt<{q+i~oZKA@iZes|XLT`cicxNh zh2~g&g4F*L5kTSedd=B7AXA4IzwE4yR z0>%IO8c(}UdX=fA1m9f01kZ%WK;wO5f`Opu6Uf}WTa{b*E3e7^JkiF@h$C_W#22=% zF2EHS5g~W!dr&#FVy79IUL`J`80gJ=Mu0qHg2A9j0F#V)V%0xx<#;R5Z}@}7=zA`g z^7!iyakkd|L0sRGq5^Tf!pfG)Ybqt5h%zG>YoMalmHU^nGvE$W`ywPJ9%t)t>O8#R z8xbg?awLn-prp70V zo~4-3=@~|`=j*SiZ)=tNq~xS0>dGtl{=bf3Z!(XH4VX65AZ8jTlAf+1*${`e!s7Q* z&P7e&^G}t<9~EzeqT;mFx3%)}*~4D>xIpl*Qy7Y%?(8YFeHwJr?svcLCNakPDe}7>%zEq*LA*i z;e3}vGb0lWDHPY4{T!O1AVi-ooC`nV5n%JEK`2Vy2)XY2j`sgfJa<++^hu%yoL9-i zO$|2az39a(xR)TQYQ$pgk*`T5s|iKF^se9@CPLJ%y?}24hEzA?*@yhb4-0-6;|iKcX?0Kd23s`e7qM(k9Al#<$bHkM7}-Co z3(qqBfiU7);ep_4Hh_GBG zq`TeguQPFxZlNE6nz2{;&*ov7{}15) V7y3c{m8$>%002ovPDHLkV1f@sGyMPn literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4206cfca19499f732561e3592108f7ee5b601860 GIT binary patch literal 3333 zcmV+g4f^tlP)FQ<>xFj73K($G*Ean19j5;DJ1n;x0-)1%hWN!fjF}!JCNRkHuJ?r)A4HRE#_x6GceO;#AsUOx2tfVm zcL2EP{}O;ztNe0#TOBwZtQ_?M(C|hFjJHrS)C0gn=RUyE*UR@cH2@sAxS#4?C^wx4 z;Gpz67n{BOz*Y}HY3!R^ZSqim`W?nbhw+ULDF(FMvI&iTB{6B6iAmcGK+N(27VUYN z_-~c7=sSlntj-sPW$9vBc9t-vS?=nQ(L@#$RQ3b#QG*kJyb%;U_wTzE{MVTU3ce}z zn1UC7{x=H#>e;^x^%ZG+5HZ@M$?s{KiLtCjyRHoFx-tM#GS~2@f4z$bcWk5RugX~S zfK{wql`M)1O(HwXa7PY_y_3rK3R0pm^j*8IjKw2%vWoHmXth`KK<-lZeA61LbM&Xz zgxfuaNxKO5@CYsLe#IkWF&Tv`P&Q=+08E^#QWiAUDf!rW1+D99SMZY#W%c7#eF}b| z(mh?bs-ZPZu8^#vLb9`yDBEeJY^Usm!d0mpDlMkG_(2YBDPrSNvskS+h=(o9#GZl& z#o3=7W?RuZ0TB*3-qTHOiR|s|xpHIm2`G5O&(jqAd)I0e{7lb&1+RMew+jAj>ASNU zn39;N;7JkOpf@OZhBYPhy=LL9>FgYprS-J~`^eAE z6dIRXe&+Kw?**XagDV{G>E?2EKNXFwAq^0VWM2g}puf-rOlrW}OLhR08nE9dzgdDB z@Y0o?fKme@5p>QA_niPHop((87BK1DCvScTOgi6KEw8BHCzl5V-(oVNscX3=J#$p<;6(fcZ{FrwE*;b8^ui{R5C=$x4ZG*VzA1vpZmBL%#mvY%b4X^K8$0Y0|ZNTn?YL&5-X z)$8H;Pwc{!n1I`N8Eeib&9m=xNut2jlW9go_S z+LE8XLa9A*87OpC0- z00UjU_-ZObuPQOz&%ABlqKIkV0yCkd)g#%f-O}~8TT=67r)2++3cmR8V?O)$I^SsdLP55v1BE=>Vo4zXK1i$w>ovt%1w`>aiLmRInExHs}$GaOxi3>@$)|2m#aF>~pW$Dpg7N?j_uW3$Qq5ir)x^>{s4 zZR7zo*3s6c0Sm0x{0S3Og#_Q_?vL(9$nR>A{9#;pZVKX((fDpLs!f8$xpcbD0Fd{@ zUg9ns!96qzz^~tanl|SEy@P!?opn-EYo!qJW)=Tdp2u-J@EFG?L3 zP6=I#5wH6O%?(XbP30HDnwEgGV-S07yL94@t-@;1;k?m{-O(YP__9@4l9EX^8z~2# zIrXnOCLB0oD*z`Z+)-&6db7{me=uz&?DU;+QXp=u|T zpPea!5!{*Lc;N_caot0s^Say0&(0LVpl&!^?taz%&RWT0GK#Z5I}A()64W5L-!A>( z!D&yLcZ*;yVto`3PrlC8rpchkY;nC#*BRn8pKzx3Y}AdP zZBp*U`Kk^>ZO#F%HhCzGeNz$Adbaf4uMBZr@9vxNx4VJM-7o#qt8a*4d@nM}L(55? z@ein4d8*<8y@P#RsB}*;b|&M4CqDP!fSTs#gsv_ZKq$@^L~V(Ym`94~>Frn5j;a;l zbk<3|_rE88V5<+s*FX+5ymV!!BHlOQ8^rIu&geiR-l6%+>GgQMuw*ik6Z1$h0P6$4 zy9RHmYDbj=tXTzO$CJ--ve^qZx$JmVAMbYq#=s{G6Wyno=sC}P!lX;!%lI*3vj^o# zIn@ZrlhW3v;e4k$=c{snh&OYw*~>xc^-!c1Q4aQ%HHK8*zemH$^nSseuSotiO$-2&FQ;+w?p59v43*% zWe#Aq;q<6meySX>Jhw=E_l1}Fs22zb|49d&4E!D+R@YZk!T9wvsyd%5Or|(K8)|1_ zUWYhwLGQq)I zKoAW-PlLFPSTmBQ{C7I2S^+m2FG^eO2gUa_HB&U;?Ik<#cs=+$gA8wJ!U!dsKuAetLy7eZAU;C0`i zuB&~DvEe+JBpva3)igOLB}Yxhj^?*R4Lo`CLq2M7@@b&T_FOBk_}9@?-w-_w zu%;!zcnb}0bWEx)@+&8tTTHQBZ@UA7?f@D~w(xI#o}0j6;0^-7zRYsiFj+6SIa^wJ zPTXj`7#$68b_~MUd4;hs`3ef&8gcjg)HF9IX!P=(ByE`SFnMPnoIh#AS%X5+x7_Kn)JV6TC|J zQQ-Z+Q);rE5^l$(C~3frUT9Pr53=zUjq;(#J*;l)IVZ#=1B5P5ha-58D4g#qP9Z^; zs;X&q8sO-VUitjL#i~{QDa-A0Yd@bjyQkd8jV3g{nS(3Apv-u(rd(E-Ix@|46xUbyds zJSlS zHk8O4rL*Htv3jk6Ro2CP(KvhissqyM19&|KP(R0Ckr{@0`bp3q8n?f>p P00000NkvXXu0mjfN2-4G literal 0 HcmV?d00001 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