Adds playable instruments, IDropped, IHandSelected and IHandDese… (#368)

* 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
This commit is contained in:
Víctor Aguilera Puerto
2019-11-25 00:11:47 +01:00
committed by Pieter-Jan Briers
parent ce54c489eb
commit fedc0ad71c
41 changed files with 1062 additions and 7 deletions

View File

@@ -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<InstrumentComponent>(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();
}
}
}

View File

@@ -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
{
/// <summary>
/// Called when a midi song stops playing.
/// </summary>
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;
/// <summary>
/// A queue of MidiEvents to be sent to the server.
/// </summary>
private Queue<MidiEvent> _eventQueue = new Queue<MidiEvent>();
/// <summary>
/// Whether a midi song will loop or not.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public bool LoopMidi
{
get => _renderer.LoopMidi;
set => _renderer.LoopMidi = value;
}
/// <summary>
/// Changes the instrument the midi renderer will play.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public int InstrumentProgram
{
get => _instrumentProgram;
set {
_instrumentProgram = value;
_renderer.MidiProgram = _instrumentProgram;
}
}
/// <summary>
/// Whether there's a midi song being played or not.
/// </summary>
[ViewVariables]
public bool IsMidiOpen => _renderer.Status == MidiRendererStatus.File;
/// <summary>
/// Whether the midi renderer is listening for midi input or not.
/// </summary>
[ViewVariables]
public bool IsInputOpen => _renderer.Status == MidiRendererStatus.Input;
public 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;
}
}
/// <inheritdoc cref="MidiRenderer.OpenInput"/>
public bool OpenInput()
{
if (_renderer.OpenInput())
{
_renderer.OnMidiEvent += RendererOnMidiEvent;
return true;
}
return false;
}
/// <inheritdoc cref="MidiRenderer.CloseInput"/>
public bool CloseInput()
{
if (!_renderer.CloseInput()) return false;
_renderer.OnMidiEvent -= RendererOnMidiEvent;
return true;
}
/// <inheritdoc cref="MidiRenderer.OpenMidi(string)"/>
public bool OpenMidi(string filename)
{
if (!_renderer.OpenMidi(filename)) return false;
_renderer.OnMidiEvent += RendererOnMidiEvent;
return true;
}
/// <inheritdoc cref="MidiRenderer.CloseMidi"/>
public bool CloseMidi()
{
if (!_renderer.CloseMidi()) return false;
_renderer.OnMidiEvent -= RendererOnMidiEvent;
return true;
}
/// <summary>
/// Called whenever the renderer receives a midi event.
/// </summary>
/// <param name="midiEvent">The received midi event</param>
private void RendererOnMidiEvent(MidiEvent midiEvent)
{
SendNetworkMessage(new InstrumentMidiEventMessage(midiEvent, _timing.CurTime.TotalMilliseconds));
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -145,6 +145,8 @@ namespace Content.Server.GameObjects
item.Owner.Transform.LocalPosition = Vector2.Zero; item.Owner.Transform.LocalPosition = Vector2.Zero;
} }
_entitySystemManager.GetEntitySystem<InteractionSystem>().HandSelectedInteraction(Owner, item.Owner);
return success; return success;
} }

View File

@@ -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
{
/// <summary>
/// The client channel currently playing the instrument, or null if there's none.
/// </summary>
private INetChannel _instrumentPlayer;
private bool _handheld;
[ViewVariables]
private BoundUserInterface _userInterface;
/// <summary>
/// Whether the instrument is an item which can be held or not.
/// </summary>
[ViewVariables]
public bool Handheld => _handheld;
public override void Initialize()
{
base.Initialize();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>().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<BasicActorComponent>()?.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);
}
}
}

View File

@@ -57,6 +57,11 @@ namespace Content.Server.GameObjects
return true; return true;
} }
bool IActionBlocker.CanDrop()
{
return true;
}
bool IActionBlocker.CanEmote() bool IActionBlocker.CanEmote()
{ {
return true; return true;
@@ -103,6 +108,11 @@ namespace Content.Server.GameObjects
return false; return false;
} }
bool IActionBlocker.CanDrop()
{
return false;
}
bool IActionBlocker.CanEmote() bool IActionBlocker.CanEmote()
{ {
return false; return false;
@@ -169,6 +179,11 @@ namespace Content.Server.GameObjects
return false; return false;
} }
bool IActionBlocker.CanDrop()
{
return false;
}
bool IActionBlocker.CanEmote() bool IActionBlocker.CanEmote()
{ {
return false; return false;

View File

@@ -101,6 +101,11 @@ namespace Content.Server.GameObjects
bool IActionBlocker.CanSpeak() bool IActionBlocker.CanSpeak()
{ {
return CurrentDamageState.CanSpeak(); return CurrentDamageState.CanSpeak();
}
bool IActionBlocker.CanDrop()
{
return CurrentDamageState.CanDrop();
} }
bool IActionBlocker.CanEmote() bool IActionBlocker.CanEmote()

View File

@@ -5,7 +5,7 @@ using Robust.Shared.GameObjects;
namespace Content.Server.GameObjects.Components.Research namespace Content.Server.GameObjects.Components.Research
{ {
[RegisterComponent] [RegisterComponent]
public class TechnologyDatabaseComponent : SharedTechnologyDatabaseComponent public class TechnologyDatabaseComponent : SharedTechnologyDatabaseComponent
{ {
public override ComponentState GetComponentState() public override ComponentState GetComponentState()
{ {

View File

@@ -15,6 +15,8 @@ namespace Content.Server.GameObjects.EntitySystems
bool CanSpeak(); bool CanSpeak();
bool CanDrop();
bool CanEmote(); bool CanEmote();
} }
@@ -70,6 +72,16 @@ namespace Content.Server.GameObjects.EntitySystems
return canspeak; return canspeak;
} }
public static bool CanDrop(IEntity entity)
{
bool candrop = true;
foreach (var actionblockercomponents in entity.GetAllComponents<IActionBlocker>())
{
candrop &= actionblockercomponents.CanDrop();
}
return candrop;
}
public static bool CanEmote(IEntity entity) public static bool CanEmote(IEntity entity)
{ {
bool canemote = true; bool canemote = true;

View File

@@ -183,6 +183,60 @@ namespace Content.Server.GameObjects.EntitySystems
public GridCoordinates ClickLocation { get; } public GridCoordinates ClickLocation { get; }
} }
/// <summary>
/// This interface gives components behavior when they're held on the selected hand.
/// </summary>
public interface IHandSelected
{
void HandSelected(HandSelectedEventArgs eventArgs);
}
public class HandSelectedEventArgs : EventArgs
{
public HandSelectedEventArgs(IEntity user)
{
User = user;
}
public IEntity User { get; }
}
/// <summary>
/// This interface gives components behavior when they're held on a deselected hand.
/// </summary>
public interface IHandDeselected
{
void HandDeselected(HandDeselectedEventArgs eventArgs);
}
public class HandDeselectedEventArgs : EventArgs
{
public HandDeselectedEventArgs(IEntity user)
{
User = user;
}
public IEntity User { get; }
}
/// <summary>
/// This interface gives components behavior when they're dropped by a mob.
/// </summary>
public interface IDropped
{
void Dropped(DroppedEventArgs eventArgs);
}
public class DroppedEventArgs : EventArgs
{
public DroppedEventArgs(IEntity user)
{
User = user;
}
public IEntity User { get; }
}
/// <summary> /// <summary>
/// Governs interactions during clicking on entities /// Governs interactions during clicking on entities
/// </summary> /// </summary>
@@ -512,8 +566,8 @@ namespace Content.Server.GameObjects.EntitySystems
} }
/// <summary> /// <summary>
/// Activates the Use behavior of an object /// Activates the Throw behavior of an object
/// Verifies that the user is capable of doing the use interaction first /// Verifies that the user is capable of doing the throw interaction first
/// </summary> /// </summary>
public bool TryThrowInteraction(IEntity user, IEntity item) public bool TryThrowInteraction(IEntity user, IEntity item)
{ {
@@ -568,6 +622,86 @@ namespace Content.Server.GameObjects.EntitySystems
} }
} }
/// <summary>
/// Activates the Dropped behavior of an object
/// Verifies that the user is capable of doing the drop interaction first
/// </summary>
public bool TryDroppedInteraction(IEntity user, IEntity item)
{
if (user == null || item == null || !ActionBlockerSystem.CanDrop(user)) return false;
DroppedInteraction(user, item);
return true;
}
/// <summary>
/// Calls Dropped on all components that implement the IDropped interface
/// on an entity that has been dropped.
/// </summary>
public void DroppedInteraction(IEntity user, IEntity item)
{
var dropMsg = new DroppedMessage(user, item);
RaiseEvent(dropMsg);
if (dropMsg.Handled)
{
return;
}
var comps = item.GetAllComponents<IDropped>().ToList();
// Call Land on all components that implement the interface
foreach (var comp in comps)
{
comp.Dropped(new DroppedEventArgs(user));
}
}
/// <summary>
/// Calls HandSelected on all components that implement the IHandSelected interface
/// on an item entity on a hand that has just been selected.
/// </summary>
public void HandSelectedInteraction(IEntity user, IEntity item)
{
var dropMsg = new HandSelectedMessage(user, item);
RaiseEvent(dropMsg);
if (dropMsg.Handled)
{
return;
}
var comps = item.GetAllComponents<IHandSelected>().ToList();
// Call Land on all components that implement the interface
foreach (var comp in comps)
{
comp.HandSelected(new HandSelectedEventArgs(user));
}
}
/// <summary>
/// Calls HandDeselected on all components that implement the IHandDeselected interface
/// on an item entity on a hand that has just been deselected.
/// </summary>
public void HandDeselectedInteraction(IEntity user, IEntity item)
{
var dropMsg = new HandDeselectedMessage(user, item);
RaiseEvent(dropMsg);
if (dropMsg.Handled)
{
return;
}
var comps = item.GetAllComponents<IHandDeselected>().ToList();
// Call Land on all components that implement the interface
foreach (var comp in comps)
{
comp.HandDeselected(new HandDeselectedEventArgs(user));
}
}
/// <summary> /// <summary>
/// Will have two behaviors, either "uses" the weapon at range on the entity if it is capable of accepting that action /// 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 /// 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
} }
} }
/// <summary>
/// Raised when an entity that was thrown lands.
/// </summary>
[PublicAPI]
public class DroppedMessage : EntitySystemMessage
{
/// <summary>
/// If this message has already been "handled" by a previous system.
/// </summary>
public bool Handled { get; set; }
/// <summary>
/// Entity that dropped the item.
/// </summary>
public IEntity User { get; }
/// <summary>
/// Item that was dropped.
/// </summary>
public IEntity Dropped { get; }
public DroppedMessage(IEntity user, IEntity dropped)
{
User = user;
Dropped = dropped;
}
}
/// <summary>
/// Raised when an entity item in a hand is selected.
/// </summary>
[PublicAPI]
public class HandSelectedMessage : EntitySystemMessage
{
/// <summary>
/// If this message has already been "handled" by a previous system.
/// </summary>
public bool Handled { get; set; }
/// <summary>
/// Entity that owns the selected hand.
/// </summary>
public IEntity User { get; }
/// <summary>
/// The item in question.
/// </summary>
public IEntity Item { get; }
public HandSelectedMessage(IEntity user, IEntity item)
{
User = user;
Item = item;
}
}
/// <summary>
/// Raised when an entity item in a hand is deselected.
/// </summary>
[PublicAPI]
public class HandDeselectedMessage : EntitySystemMessage
{
/// <summary>
/// If this message has already been "handled" by a previous system.
/// </summary>
public bool Handled { get; set; }
/// <summary>
/// Entity that owns the deselected hand.
/// </summary>
public IEntity User { get; }
/// <summary>
/// The item in question.
/// </summary>
public IEntity Item { get; }
public HandDeselectedMessage(IEntity user, IEntity item)
{
User = user;
Item = item;
}
}
/// <summary> /// <summary>
/// Raised when an entity is activated in the world. /// Raised when an entity is activated in the world.
/// </summary> /// </summary>

View File

@@ -12,6 +12,7 @@ using Robust.Server.Interfaces.Player;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems; using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Input; using Robust.Shared.Input;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Map; using Robust.Shared.Interfaces.Map;
using Robust.Shared.Interfaces.Timing; using Robust.Shared.Interfaces.Timing;
using Robust.Shared.IoC; using Robust.Shared.IoC;
@@ -27,6 +28,7 @@ namespace Content.Server.GameObjects.EntitySystems
{ {
#pragma warning disable 649 #pragma warning disable 649
[Dependency] private readonly IMapManager _mapManager; [Dependency] private readonly IMapManager _mapManager;
[Dependency] private readonly IEntitySystemManager _entitySystemManager;
#pragma warning restore 649 #pragma warning restore 649
private const float ThrowForce = 1.5f; // Throwing force of mobs in Newtons 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)) if (!TryGetAttachedComponent(session as IPlayerSession, out HandsComponent handsComp))
return; return;
var interactionSystem = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<InteractionSystem>();
var oldItem = handsComp.GetActiveHand;
handsComp.SwapHands(); 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) private bool HandleDrop(ICommonSession session, GridCoordinates coords, EntityUid uid)
@@ -102,14 +116,19 @@ namespace Content.Server.GameObjects.EntitySystems
var ent = ((IPlayerSession) session).AttachedEntity; var ent = ((IPlayerSession) session).AttachedEntity;
if (ent == null || !ent.IsValid()) if (ent == null || !ent.IsValid())
{
return false; return false;
}
if (!ent.TryGetComponent(out HandsComponent handsComp)) if (!ent.TryGetComponent(out HandsComponent handsComp))
{
return false; return false;
}
if (handsComp.GetActiveHand == null)
return false;
if (!_entitySystemManager.GetEntitySystem<InteractionSystem>().TryDroppedInteraction(ent, handsComp.GetActiveHand.Owner))
return false;
if(handsComp.GetActiveHand != null && !_entitySystemManager.GetEntitySystem<InteractionSystem>().TryDroppedInteraction(ent, handsComp.GetActiveHand.Owner))
return false;
if (coords.InRange(_mapManager, ent.Transform.GridPosition, InteractionSystem.InteractionRange)) if (coords.InRange(_mapManager, ent.Transform.GridPosition, InteractionSystem.InteractionRange))
{ {

View File

@@ -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;
}
/// <summary>
/// This message is sent to the client to completely stop midi input and midi playback.
/// </summary>
[Serializable, NetSerializable]
public class InstrumentStopMidiMessage : ComponentMessage
{
}
/// <summary>
/// This message carries a MidiEvent to be played on clients.
/// </summary>
[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,
}
}

View File

@@ -35,5 +35,6 @@
public const uint CARGO_ORDER_DATABASE = 1030; public const uint CARGO_ORDER_DATABASE = 1030;
public const uint GALACTIC_MARKET = 1031; public const uint GALACTIC_MARKET = 1031;
public const uint HAIR = 1032; public const uint HAIR = 1032;
public const uint INSTRUMENTS = 1033;
} }
} }

View File

@@ -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

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 772 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 411 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 520 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 808 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 551 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 926 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 565 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 780 B

View File

@@ -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]]}]}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 707 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 779 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 373 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 965 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -12,10 +12,14 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=UV/@EntryIndexedValue">UV</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=UV/@EntryIndexedValue">UV</s:String>
<s:String x:Key="/Default/FilterSettingsManager/AttributeFilterXml/@EntryValue">&lt;data /&gt;</s:String> <s:String x:Key="/Default/FilterSettingsManager/AttributeFilterXml/@EntryValue">&lt;data /&gt;</s:String>
<s:String x:Key="/Default/FilterSettingsManager/CoverageFilterXml/@EntryValue">&lt;data&gt;&lt;IncludeFilters /&gt;&lt;ExcludeFilters&gt;&lt;Filter ModuleMask="*.UnitTesting" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /&gt;&lt;/ExcludeFilters&gt;&lt;/data&gt;</s:String> <s:String x:Key="/Default/FilterSettingsManager/CoverageFilterXml/@EntryValue">&lt;data&gt;&lt;IncludeFilters /&gt;&lt;ExcludeFilters&gt;&lt;Filter ModuleMask="*.UnitTesting" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /&gt;&lt;/ExcludeFilters&gt;&lt;/data&gt;</s:String>
<s:Boolean x:Key="/Default/UserDictionary/Words/=autoconnect/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=BYOND/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=BYOND/@EntryIndexedValue">True</s:Boolean>
<s:String x:Key="/Default/FilterSettingsManager/AttributeFilterXml/@EntryValue">&lt;data /&gt;</s:String> <s:String x:Key="/Default/FilterSettingsManager/AttributeFilterXml/@EntryValue">&lt;data /&gt;</s:String>
<s:String x:Key="/Default/FilterSettingsManager/CoverageFilterXml/@EntryValue">&lt;data&gt;&lt;IncludeFilters /&gt;&lt;ExcludeFilters&gt;&lt;Filter ModuleMask="Lidgren.Network" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /&gt;&lt;/ExcludeFilters&gt;&lt;/data&gt;</s:String> <s:String x:Key="/Default/FilterSettingsManager/CoverageFilterXml/@EntryValue">&lt;data&gt;&lt;IncludeFilters /&gt;&lt;ExcludeFilters&gt;&lt;Filter ModuleMask="Lidgren.Network" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /&gt;&lt;/ExcludeFilters&gt;&lt;/data&gt;</s:String>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Fluidsynth/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=freepats/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Lerp/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=Lerp/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Noto/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=Noto/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=preemptively/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=preemptively/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=soundfonts/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=swsl/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> <s:Boolean x:Key="/Default/UserDictionary/Words/=swsl/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>