Instrument band support, submodule update to 138.0.0 (#17995)
This commit is contained in:
committed by
GitHub
parent
69ff0ae2e6
commit
a2893dd6c3
@@ -52,13 +52,44 @@ public sealed class InstrumentSystem : SharedInstrumentSystem
|
||||
EndRenderer(uid, false, component);
|
||||
}
|
||||
|
||||
public void SetMaster(EntityUid uid, EntityUid? masterUid)
|
||||
{
|
||||
if (!TryComp(uid, out InstrumentComponent? instrument))
|
||||
return;
|
||||
|
||||
RaiseNetworkEvent(new InstrumentSetMasterEvent(uid, masterUid));
|
||||
}
|
||||
|
||||
public void SetFilteredChannel(EntityUid uid, int channel, bool value)
|
||||
{
|
||||
if (!TryComp(uid, out InstrumentComponent? instrument))
|
||||
return;
|
||||
|
||||
if(value)
|
||||
instrument.Renderer?.SendMidiEvent(RobustMidiEvent.AllNotesOff((byte)channel, 0), false);
|
||||
|
||||
RaiseNetworkEvent(new InstrumentSetFilteredChannelEvent(uid, channel, value));
|
||||
}
|
||||
|
||||
public override void SetupRenderer(EntityUid uid, bool fromStateChange, SharedInstrumentComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
if (component is not InstrumentComponent instrument || instrument.IsRendererAlive)
|
||||
if (component is not InstrumentComponent instrument)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (instrument.IsRendererAlive)
|
||||
{
|
||||
if (fromStateChange)
|
||||
{
|
||||
UpdateRenderer(uid, instrument);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
instrument.SequenceDelay = 0;
|
||||
instrument.SequenceStartTick = 0;
|
||||
@@ -88,17 +119,39 @@ public sealed class InstrumentSystem : SharedInstrumentSystem
|
||||
return;
|
||||
|
||||
instrument.Renderer.TrackingEntity = uid;
|
||||
|
||||
instrument.Renderer.FilteredChannels.SetAll(false);
|
||||
instrument.Renderer.FilteredChannels.Or(instrument.FilteredChannels);
|
||||
|
||||
instrument.Renderer.DisablePercussionChannel = !instrument.AllowPercussion;
|
||||
instrument.Renderer.DisableProgramChangeEvent = !instrument.AllowProgramChange;
|
||||
|
||||
for (int i = 0; i < RobustMidiEvent.MaxChannels; i++)
|
||||
{
|
||||
if(instrument.FilteredChannels[i])
|
||||
instrument.Renderer.SendMidiEvent(RobustMidiEvent.AllNotesOff((byte)i, 0));
|
||||
}
|
||||
|
||||
if (!instrument.AllowProgramChange)
|
||||
{
|
||||
instrument.Renderer.MidiBank = instrument.InstrumentBank;
|
||||
instrument.Renderer.MidiProgram = instrument.InstrumentProgram;
|
||||
}
|
||||
|
||||
UpdateRendererMaster(instrument);
|
||||
|
||||
instrument.Renderer.LoopMidi = instrument.LoopMidi;
|
||||
instrument.DirtyRenderer = false;
|
||||
}
|
||||
|
||||
private void UpdateRendererMaster(InstrumentComponent instrument)
|
||||
{
|
||||
if (instrument.Renderer == null || instrument.Master == null)
|
||||
return;
|
||||
|
||||
if (!TryComp(instrument.Master, out InstrumentComponent? masterInstrument) || masterInstrument.Renderer == null)
|
||||
return;
|
||||
|
||||
instrument.Renderer.Master = masterInstrument.Renderer;
|
||||
}
|
||||
|
||||
public override void EndRenderer(EntityUid uid, bool fromStateChange, SharedInstrumentComponent? component = null)
|
||||
@@ -121,7 +174,8 @@ public sealed class InstrumentSystem : SharedInstrumentSystem
|
||||
return;
|
||||
}
|
||||
|
||||
instrument.Renderer?.StopAllNotes();
|
||||
instrument.Renderer?.SystemReset();
|
||||
instrument.Renderer?.ClearAllEvents();
|
||||
|
||||
var renderer = instrument.Renderer;
|
||||
|
||||
@@ -162,13 +216,14 @@ public sealed class InstrumentSystem : SharedInstrumentSystem
|
||||
|
||||
SetupRenderer(uid, false, instrument);
|
||||
|
||||
if (instrument.Renderer != null && instrument.Renderer.OpenInput())
|
||||
{
|
||||
if (instrument.Renderer == null || !instrument.Renderer.OpenInput())
|
||||
return false;
|
||||
|
||||
SetMaster(uid, null);
|
||||
instrument.MidiEventBuffer.Clear();
|
||||
instrument.Renderer.OnMidiEvent += instrument.MidiEventBuffer.Add;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool OpenMidi(EntityUid uid, ReadOnlySpan<byte> data, InstrumentComponent? instrument = null)
|
||||
@@ -179,12 +234,10 @@ public sealed class InstrumentSystem : SharedInstrumentSystem
|
||||
SetupRenderer(uid, false, instrument);
|
||||
|
||||
if (instrument.Renderer == null || !instrument.Renderer.OpenMidi(data))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
SetMaster(uid, null);
|
||||
instrument.MidiEventBuffer.Clear();
|
||||
|
||||
instrument.Renderer.OnMidiEvent += instrument.MidiEventBuffer.Add;
|
||||
return true;
|
||||
}
|
||||
@@ -266,12 +319,24 @@ public sealed class InstrumentSystem : SharedInstrumentSystem
|
||||
|
||||
instrument.SequenceDelay = Math.Max(instrument.SequenceDelay, delta);
|
||||
|
||||
var currentTick = renderer.SequencerTick;
|
||||
SendMidiEvents(midiEv.MidiEvent, instrument);
|
||||
}
|
||||
|
||||
private void SendMidiEvents(IReadOnlyList<RobustMidiEvent> midiEvents, InstrumentComponent instrument)
|
||||
{
|
||||
if (instrument.Renderer == null)
|
||||
{
|
||||
Log.Warning($"Tried to send Midi events to an instrument without a renderer.");
|
||||
return;
|
||||
}
|
||||
|
||||
var currentTick = instrument.Renderer.SequencerTick;
|
||||
|
||||
// ReSharper disable once ForCanBeConvertedToForeach
|
||||
for (uint i = 0; i < midiEv.MidiEvent.Length; i++)
|
||||
for (uint i = 0; i < midiEvents.Count; i++)
|
||||
{
|
||||
var ev = midiEv.MidiEvent[i];
|
||||
// I am surprised this doesn't take uint...
|
||||
var ev = midiEvents[(int)i];
|
||||
|
||||
var scheduled = ev.Tick + instrument.SequenceDelay;
|
||||
|
||||
@@ -306,12 +371,14 @@ public sealed class InstrumentSystem : SharedInstrumentSystem
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var instrument in EntityManager.EntityQuery<InstrumentComponent>(true))
|
||||
var query = EntityQueryEnumerator<InstrumentComponent>();
|
||||
while (query.MoveNext(out var uid, out var instrument))
|
||||
{
|
||||
if (instrument.DirtyRenderer && instrument.Renderer != null)
|
||||
UpdateRenderer(instrument.Owner, instrument);
|
||||
// For cases where the master renderer was not created yet.
|
||||
if (instrument is { Renderer.Master: null, Master: not null })
|
||||
UpdateRendererMaster(instrument);
|
||||
|
||||
if (!instrument.IsMidiOpen && !instrument.IsInputOpen)
|
||||
if (instrument is { IsMidiOpen: false, IsInputOpen: false })
|
||||
continue;
|
||||
|
||||
var now = _gameTiming.RealTime;
|
||||
@@ -323,10 +390,11 @@ public sealed class InstrumentSystem : SharedInstrumentSystem
|
||||
instrument.SentWithinASec = 0;
|
||||
}
|
||||
|
||||
if (instrument.MidiEventBuffer.Count == 0) continue;
|
||||
if (instrument.MidiEventBuffer.Count == 0)
|
||||
continue;
|
||||
|
||||
var max = instrument.RespectMidiLimits ?
|
||||
Math.Min(MaxMidiEventsPerBatch, MaxMidiEventsPerSecond - instrument.SentWithinASec)
|
||||
var max = instrument.RespectMidiLimits
|
||||
? Math.Min(MaxMidiEventsPerBatch, MaxMidiEventsPerSecond - instrument.SentWithinASec)
|
||||
: instrument.MidiEventBuffer.Count;
|
||||
|
||||
if (max <= 0)
|
||||
@@ -357,7 +425,7 @@ public sealed class InstrumentSystem : SharedInstrumentSystem
|
||||
if (eventCount == 0)
|
||||
continue;
|
||||
|
||||
RaiseNetworkEvent(new InstrumentMidiEventEvent(instrument.Owner, events));
|
||||
RaiseNetworkEvent(new InstrumentMidiEventEvent(uid, events));
|
||||
|
||||
instrument.SentWithinASec += eventCount;
|
||||
|
||||
|
||||
9
Content.Client/Instruments/UI/BandMenu.xaml
Normal file
9
Content.Client/Instruments/UI/BandMenu.xaml
Normal file
@@ -0,0 +1,9 @@
|
||||
<DefaultWindow Title="{Loc 'instruments-component-band-menu'}" MinSize="250 350" xmlns="https://spacestation14.io"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client">
|
||||
<BoxContainer Orientation="Vertical" Align="Center" HorizontalExpand="true" VerticalExpand="true">
|
||||
<ItemList Name="BandList" SelectMode="Button" Margin="3 3 3 3"
|
||||
HorizontalExpand="true" VerticalExpand="true" SizeFlagsStretchRatio="8"/>
|
||||
<Button Name="RefreshButton" Text="{Loc 'instrument-component-band-refresh'}" TextAlign="Center"
|
||||
HorizontalExpand="true" Margin="5 5 5 5" SizeFlagsStretchRatio="1"/>
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
45
Content.Client/Instruments/UI/BandMenu.xaml.cs
Normal file
45
Content.Client/Instruments/UI/BandMenu.xaml.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Instruments.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class BandMenu : DefaultWindow
|
||||
{
|
||||
private readonly InstrumentBoundUserInterface _owner;
|
||||
|
||||
public BandMenu(InstrumentBoundUserInterface owner) : base()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
_owner = owner;
|
||||
BandList.OnItemSelected += OnItemSelected;
|
||||
RefreshButton.OnPressed += OnRefreshPressed;
|
||||
}
|
||||
|
||||
private void OnRefreshPressed(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
_owner.RefreshBands();
|
||||
}
|
||||
|
||||
private void OnItemSelected(ItemList.ItemListSelectedEventArgs args)
|
||||
{
|
||||
_owner.Instruments.SetMaster(_owner.Owner, (EntityUid)args.ItemList[args.ItemIndex].Metadata!);
|
||||
BandList.Clear();
|
||||
Timer.Spawn(0, Close);
|
||||
}
|
||||
|
||||
public void Populate((EntityUid, string)[] nearby)
|
||||
{
|
||||
BandList.Clear();
|
||||
|
||||
foreach (var (uid, name) in nearby)
|
||||
{
|
||||
var item = BandList.AddItem(name, null, true, uid);
|
||||
item.Selected = _owner.Instrument?.Master == uid;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Content.Client/Instruments/UI/ChannelsMenu.xaml
Normal file
11
Content.Client/Instruments/UI/ChannelsMenu.xaml
Normal file
@@ -0,0 +1,11 @@
|
||||
<DefaultWindow Title="{Loc 'instruments-component-channels-menu'}" MinSize="250 350" xmlns="https://spacestation14.io"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client">
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="true" VerticalExpand="true" Align="Center">
|
||||
<ItemList Name="ChannelList" SelectMode="Multiple" Margin="3 3 3 3" HorizontalExpand="true" VerticalExpand="true" SizeFlagsStretchRatio="8"/>
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="true" VerticalExpand="true" Align="Center"
|
||||
SizeFlagsStretchRatio="1">
|
||||
<Button Name="AllButton" Text="{Loc 'instruments-component-channels-all-button'}" HorizontalExpand="true" VerticalExpand="true" SizeFlagsStretchRatio="1"/>
|
||||
<Button Name="ClearButton" Text="{Loc 'instruments-component-channels-clear-button'}" HorizontalExpand="true" VerticalExpand="true" SizeFlagsStretchRatio="1"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
66
Content.Client/Instruments/UI/ChannelsMenu.xaml.cs
Normal file
66
Content.Client/Instruments/UI/ChannelsMenu.xaml.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Audio.Midi;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Instruments.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class ChannelsMenu : DefaultWindow
|
||||
{
|
||||
private readonly InstrumentBoundUserInterface _owner;
|
||||
|
||||
public ChannelsMenu(InstrumentBoundUserInterface owner) : base()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
_owner = owner;
|
||||
|
||||
ChannelList.OnItemSelected += OnItemSelected;
|
||||
ChannelList.OnItemDeselected += OnItemDeselected;
|
||||
AllButton.OnPressed += OnAllPressed;
|
||||
ClearButton.OnPressed += OnClearPressed;
|
||||
}
|
||||
|
||||
private void OnItemSelected(ItemList.ItemListSelectedEventArgs args)
|
||||
{
|
||||
_owner.Instruments.SetFilteredChannel(_owner.Owner, (int)ChannelList[args.ItemIndex].Metadata!, false);
|
||||
}
|
||||
|
||||
private void OnItemDeselected(ItemList.ItemListDeselectedEventArgs args)
|
||||
{
|
||||
_owner.Instruments.SetFilteredChannel(_owner.Owner, (int)ChannelList[args.ItemIndex].Metadata!, true);
|
||||
}
|
||||
|
||||
private void OnAllPressed(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
foreach (var item in ChannelList)
|
||||
{
|
||||
// TODO: Make this efficient jfc
|
||||
item.Selected = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnClearPressed(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
foreach (var item in ChannelList)
|
||||
{
|
||||
// TODO: Make this efficient jfc
|
||||
item.Selected = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Populate()
|
||||
{
|
||||
ChannelList.Clear();
|
||||
|
||||
for (int i = 0; i < RobustMidiEvent.MaxChannels; i++)
|
||||
{
|
||||
var item = ChannelList.AddItem(_owner.Loc.GetString("instrument-component-channel-name",
|
||||
("number", i)), null, true, i);
|
||||
|
||||
item.Selected = !_owner.Instrument?.FilteredChannels[i] ?? false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,56 @@
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Instruments.UI;
|
||||
using Content.Shared.Interaction;
|
||||
using Robust.Client.Audio.Midi;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.Instruments.UI
|
||||
{
|
||||
public sealed class InstrumentBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[ViewVariables]
|
||||
private InstrumentMenu? _instrumentMenu;
|
||||
[Dependency] public readonly IEntityManager Entities = default!;
|
||||
[Dependency] public readonly IMidiManager MidiManager = default!;
|
||||
[Dependency] public readonly IFileDialogManager FileDialogManager = default!;
|
||||
[Dependency] public readonly IPlayerManager PlayerManager = default!;
|
||||
[Dependency] public readonly ILocalizationManager Loc = default!;
|
||||
|
||||
[ViewVariables]
|
||||
public InstrumentComponent? Instrument { get; set; }
|
||||
public readonly InstrumentSystem Instruments = default!;
|
||||
public readonly ActionBlockerSystem ActionBlocker = default!;
|
||||
public readonly SharedInteractionSystem Interactions = default!;
|
||||
|
||||
[ViewVariables] private InstrumentMenu? _instrumentMenu;
|
||||
[ViewVariables] private BandMenu? _bandMenu;
|
||||
[ViewVariables] private ChannelsMenu? _channelsMenu;
|
||||
|
||||
[ViewVariables] public InstrumentComponent? Instrument { get; private set; }
|
||||
|
||||
public InstrumentBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
Instruments = Entities.System<InstrumentSystem>();
|
||||
ActionBlocker = Entities.System<ActionBlockerSystem>();
|
||||
Interactions = Entities.System<SharedInteractionSystem>();
|
||||
}
|
||||
|
||||
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
|
||||
{
|
||||
switch (message)
|
||||
{
|
||||
case InstrumentBandResponseBuiMessage bandRx:
|
||||
_bandMenu?.Populate(bandRx.Nearby);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
if (!EntMan.TryGetComponent<InstrumentComponent?>(Owner, out var instrument)) return;
|
||||
if (!EntMan.TryGetComponent<InstrumentComponent?>(Owner, out var instrument))
|
||||
return;
|
||||
|
||||
Instrument = instrument;
|
||||
_instrumentMenu = new InstrumentMenu(this);
|
||||
@@ -28,8 +62,45 @@ namespace Content.Client.Instruments.UI
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
if (!disposing) return;
|
||||
if (!disposing)
|
||||
return;
|
||||
_instrumentMenu?.Dispose();
|
||||
_bandMenu?.Dispose();
|
||||
_channelsMenu?.Dispose();
|
||||
}
|
||||
|
||||
public void RefreshBands()
|
||||
{
|
||||
SendMessage(new InstrumentBandRequestBuiMessage());
|
||||
}
|
||||
|
||||
public void OpenBandMenu()
|
||||
{
|
||||
_bandMenu ??= new BandMenu(this);
|
||||
|
||||
// Refresh cache...
|
||||
RefreshBands();
|
||||
|
||||
_bandMenu.OpenCenteredLeft();
|
||||
}
|
||||
|
||||
public void CloseBandMenu()
|
||||
{
|
||||
if(_bandMenu?.IsOpen ?? false)
|
||||
_bandMenu.Close();
|
||||
}
|
||||
|
||||
public void OpenChannelsMenu()
|
||||
{
|
||||
_channelsMenu ??= new ChannelsMenu(this);
|
||||
_channelsMenu.Populate();
|
||||
_channelsMenu.OpenCenteredRight();
|
||||
}
|
||||
|
||||
public void CloseChannelsMenu()
|
||||
{
|
||||
if(_channelsMenu?.IsOpen ?? false)
|
||||
_channelsMenu.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,14 +4,20 @@
|
||||
<BoxContainer Orientation="Horizontal" VerticalExpand="True">
|
||||
<Button Name="InputButton" ToggleMode="True" Text="{Loc 'instruments-component-menu-input-button'}" TextAlign="Center"
|
||||
HorizontalExpand="True" SizeFlagsStretchRatio="1" />
|
||||
<Control HorizontalExpand="True" SizeFlagsStretchRatio="2" />
|
||||
<Control HorizontalExpand="True" SizeFlagsStretchRatio="0.5" />
|
||||
<Button Name="BandButton" Text="{Loc 'instruments-component-menu-band-button'}" TextAlign="Center" HorizontalExpand="True"
|
||||
SizeFlagsStretchRatio="1"/>
|
||||
<Control HorizontalExpand="True" SizeFlagsStretchRatio="0.5" />
|
||||
<Button Name="FileButton" Text="{Loc 'instruments-component-menu-play-button'}" TextAlign="Center" HorizontalExpand="True"
|
||||
SizeFlagsStretchRatio="1" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal" VerticalExpand="True">
|
||||
<Button Name="LoopButton" ToggleMode="True" Text="{Loc 'instruments-component-menu-loop-button'}" TextAlign="Center" HorizontalExpand="True"
|
||||
SizeFlagsStretchRatio="1" />
|
||||
<Control HorizontalExpand="True" SizeFlagsStretchRatio="2" />
|
||||
<Control HorizontalExpand="True" SizeFlagsStretchRatio="0.5" />
|
||||
<Button Name="ChannelsButton" Text="{Loc 'instruments-component-menu-channels-button'}" TextAlign="Center" HorizontalExpand="True"
|
||||
SizeFlagsStretchRatio="1"/>
|
||||
<Control HorizontalExpand="True" SizeFlagsStretchRatio="0.5" />
|
||||
<Button Name="StopButton" Text="{Loc 'instruments-component-menu-stop-button'}" TextAlign="Center" HorizontalExpand="True"
|
||||
SizeFlagsStretchRatio="1" />
|
||||
</BoxContainer>
|
||||
|
||||
@@ -1,20 +1,12 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Client.Interactable;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Interaction;
|
||||
using Robust.Client.Audio.Midi;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Timing;
|
||||
using static Robust.Client.UserInterface.Controls.BaseButton;
|
||||
using Range = Robust.Client.UserInterface.Controls.Range;
|
||||
@@ -24,29 +16,26 @@ namespace Content.Client.Instruments.UI
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class InstrumentMenu : DefaultWindow
|
||||
{
|
||||
[Dependency] private readonly IMidiManager _midiManager = default!;
|
||||
[Dependency] private readonly IFileDialogManager _fileDialogManager = default!;
|
||||
|
||||
private readonly InstrumentBoundUserInterface _owner;
|
||||
|
||||
public InstrumentMenu(InstrumentBoundUserInterface owner)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_owner = owner;
|
||||
|
||||
if (_owner.Instrument != null)
|
||||
{
|
||||
_owner.Instrument.OnMidiPlaybackEnded += InstrumentOnMidiPlaybackEnded;
|
||||
Title = IoCManager.Resolve<IEntityManager>().GetComponent<MetaDataComponent>(_owner.Instrument.Owner).EntityName;
|
||||
Title = _owner.Entities.GetComponent<MetaDataComponent>(_owner.Owner).EntityName;
|
||||
LoopButton.Disabled = !_owner.Instrument.IsMidiOpen;
|
||||
LoopButton.Pressed = _owner.Instrument.LoopMidi;
|
||||
ChannelsButton.Disabled = !_owner.Instrument.IsRendererAlive;
|
||||
StopButton.Disabled = !_owner.Instrument.IsMidiOpen;
|
||||
PlaybackSlider.MouseFilter = _owner.Instrument.IsMidiOpen ? MouseFilterMode.Pass : MouseFilterMode.Ignore;
|
||||
}
|
||||
|
||||
if (!_midiManager.IsAvailable)
|
||||
if (!_owner.MidiManager.IsAvailable)
|
||||
{
|
||||
UnavailableOverlay.Visible = true;
|
||||
// We return early as to not give the buttons behavior.
|
||||
@@ -54,8 +43,11 @@ namespace Content.Client.Instruments.UI
|
||||
}
|
||||
|
||||
InputButton.OnToggled += MidiInputButtonOnOnToggled;
|
||||
BandButton.OnPressed += BandButtonOnPressed;
|
||||
BandButton.OnToggled += BandButtonOnToggled;
|
||||
FileButton.OnPressed += MidiFileButtonOnOnPressed;
|
||||
LoopButton.OnToggled += MidiLoopButtonOnOnToggled;
|
||||
ChannelsButton.OnPressed += ChannelsButtonOnPressed;
|
||||
StopButton.OnPressed += MidiStopButtonOnPressed;
|
||||
PlaybackSlider.OnValueChanged += PlaybackSliderSeek;
|
||||
PlaybackSlider.OnKeyBindUp += PlaybackSliderKeyUp;
|
||||
@@ -63,6 +55,27 @@ namespace Content.Client.Instruments.UI
|
||||
MinSize = SetSize = new Vector2(400, 150);
|
||||
}
|
||||
|
||||
private void BandButtonOnPressed(ButtonEventArgs obj)
|
||||
{
|
||||
if (!PlayCheck())
|
||||
return;
|
||||
|
||||
_owner.OpenBandMenu();
|
||||
}
|
||||
|
||||
private void BandButtonOnToggled(ButtonToggledEventArgs obj)
|
||||
{
|
||||
if (obj.Pressed)
|
||||
return;
|
||||
|
||||
_owner.Instruments.SetMaster(_owner.Owner, null);
|
||||
}
|
||||
|
||||
private void ChannelsButtonOnPressed(ButtonEventArgs obj)
|
||||
{
|
||||
_owner.OpenChannelsMenu();
|
||||
}
|
||||
|
||||
private void InstrumentOnMidiPlaybackEnded()
|
||||
{
|
||||
MidiPlaybackSetButtonsDisabled(true);
|
||||
@@ -70,6 +83,9 @@ namespace Content.Client.Instruments.UI
|
||||
|
||||
public void MidiPlaybackSetButtonsDisabled(bool disabled)
|
||||
{
|
||||
if(disabled)
|
||||
_owner.CloseChannelsMenu();
|
||||
|
||||
LoopButton.Disabled = disabled;
|
||||
StopButton.Disabled = disabled;
|
||||
|
||||
@@ -79,8 +95,10 @@ namespace Content.Client.Instruments.UI
|
||||
|
||||
private async void MidiFileButtonOnOnPressed(ButtonEventArgs obj)
|
||||
{
|
||||
_owner.CloseBandMenu();
|
||||
|
||||
var filters = new FileDialogFilters(new FileDialogFilters.Group("mid", "midi"));
|
||||
await using var file = await _fileDialogManager.OpenFile(filters);
|
||||
await using var file = await _owner.FileDialogManager.OpenFile(filters);
|
||||
|
||||
// did the instrument menu get closed while waiting for the user to select a file?
|
||||
if (Disposed)
|
||||
@@ -89,7 +107,8 @@ namespace Content.Client.Instruments.UI
|
||||
// The following checks are only in place to prevent players from playing MIDI songs locally.
|
||||
// There are equivalents for these checks on the server.
|
||||
|
||||
if (file == null) return;
|
||||
if (file == null)
|
||||
return;
|
||||
|
||||
/*if (!_midiManager.IsMidiFile(filename))
|
||||
{
|
||||
@@ -107,7 +126,7 @@ namespace Content.Client.Instruments.UI
|
||||
await Task.WhenAll(Timer.Delay(100), file.CopyToAsync(memStream));
|
||||
|
||||
if (_owner.Instrument is not {} instrument
|
||||
|| !EntitySystem.Get<InstrumentSystem>().OpenMidi(instrument.Owner, memStream.GetBuffer().AsSpan(0, (int) memStream.Length), instrument))
|
||||
|| !_owner.Instruments.OpenMidi(_owner.Owner, memStream.GetBuffer().AsSpan(0, (int) memStream.Length), instrument))
|
||||
return;
|
||||
|
||||
MidiPlaybackSetButtonsDisabled(false);
|
||||
@@ -117,7 +136,7 @@ namespace Content.Client.Instruments.UI
|
||||
|
||||
private void MidiInputButtonOnOnToggled(ButtonToggledEventArgs obj)
|
||||
{
|
||||
var instrumentSystem = EntitySystem.Get<InstrumentSystem>();
|
||||
_owner.CloseBandMenu();
|
||||
|
||||
if (obj.Pressed)
|
||||
{
|
||||
@@ -126,45 +145,47 @@ namespace Content.Client.Instruments.UI
|
||||
|
||||
MidiStopButtonOnPressed(null);
|
||||
if(_owner.Instrument is {} instrument)
|
||||
instrumentSystem.OpenInput(instrument.Owner, instrument);
|
||||
_owner.Instruments.OpenInput(_owner.Owner, instrument);
|
||||
}
|
||||
else if (_owner.Instrument is { } instrument)
|
||||
instrumentSystem.CloseInput(instrument.Owner, false, instrument);
|
||||
{
|
||||
_owner.Instruments.CloseInput(_owner.Owner, false, instrument);
|
||||
_owner.CloseChannelsMenu();
|
||||
}
|
||||
}
|
||||
|
||||
private bool PlayCheck()
|
||||
{
|
||||
// TODO all of these checks should also be done server-side.
|
||||
|
||||
var instrumentEnt = _owner.Instrument?.Owner;
|
||||
var instrumentEnt = _owner.Owner;
|
||||
var instrument = _owner.Instrument;
|
||||
|
||||
// If either the entity or component are null, return.
|
||||
if (instrumentEnt == null || instrument == null)
|
||||
if (instrument == null)
|
||||
return false;
|
||||
|
||||
var localPlayer = IoCManager.Resolve<IPlayerManager>().LocalPlayer;
|
||||
var localPlayer = _owner.PlayerManager.LocalPlayer;
|
||||
|
||||
// If we don't have a player or controlled entity, we return.
|
||||
if (localPlayer?.ControlledEntity == null) return false;
|
||||
if (localPlayer?.ControlledEntity == null)
|
||||
return false;
|
||||
|
||||
// By default, allow an instrument to play itself and skip all other checks
|
||||
if (localPlayer.ControlledEntity == instrumentEnt)
|
||||
return true;
|
||||
|
||||
// If we're a handheld instrument, we might be in a container. Get it just in case.
|
||||
instrumentEnt.Value.TryGetContainerMan(out var conMan);
|
||||
instrumentEnt.TryGetContainerMan(out var conMan);
|
||||
|
||||
// If the instrument is handheld and we're not holding it, we return.
|
||||
if ((instrument.Handheld && (conMan == null
|
||||
|| conMan.Owner != localPlayer.ControlledEntity))) return false;
|
||||
if ((instrument.Handheld && (conMan == null || conMan.Owner != localPlayer.ControlledEntity)))
|
||||
return false;
|
||||
|
||||
var entSysMan = IoCManager.Resolve<IEntitySystemManager>();
|
||||
if (!entSysMan.GetEntitySystem<ActionBlockerSystem>().CanInteract(localPlayer.ControlledEntity.Value, instrumentEnt))
|
||||
if (!_owner.ActionBlocker.CanInteract(localPlayer.ControlledEntity.Value, instrumentEnt))
|
||||
return false;
|
||||
|
||||
// We check that we're in range unobstructed just in case.
|
||||
return entSysMan.GetEntitySystem<SharedInteractionSystem>().InRangeUnobstructed(localPlayer.ControlledEntity.Value, instrumentEnt.Value);
|
||||
return _owner.Interactions.InRangeUnobstructed(localPlayer.ControlledEntity.Value, instrumentEnt);
|
||||
}
|
||||
|
||||
private void MidiStopButtonOnPressed(ButtonEventArgs? obj)
|
||||
@@ -174,7 +195,8 @@ namespace Content.Client.Instruments.UI
|
||||
if (_owner.Instrument is not {} instrument)
|
||||
return;
|
||||
|
||||
EntitySystem.Get<InstrumentSystem>().CloseMidi(instrument.Owner, false, instrument);
|
||||
_owner.Instruments.CloseMidi(_owner.Owner, false, instrument);
|
||||
_owner.CloseChannelsMenu();
|
||||
}
|
||||
|
||||
private void MidiLoopButtonOnOnToggled(ButtonToggledEventArgs obj)
|
||||
@@ -183,29 +205,45 @@ namespace Content.Client.Instruments.UI
|
||||
return;
|
||||
|
||||
_owner.Instrument.LoopMidi = obj.Pressed;
|
||||
_owner.Instrument.DirtyRenderer = true;
|
||||
_owner.Instruments.UpdateRenderer(_owner.Owner, _owner.Instrument);
|
||||
}
|
||||
|
||||
private void PlaybackSliderSeek(Range _)
|
||||
{
|
||||
// Do not seek while still grabbing.
|
||||
if (PlaybackSlider.Grabbed || _owner.Instrument is not {} instrument) return;
|
||||
if (PlaybackSlider.Grabbed || _owner.Instrument is not {} instrument)
|
||||
return;
|
||||
|
||||
EntitySystem.Get<InstrumentSystem>().SetPlayerTick(instrument.Owner, (int)Math.Ceiling(PlaybackSlider.Value), instrument);
|
||||
_owner.Instruments.SetPlayerTick(_owner.Owner, (int)Math.Ceiling(PlaybackSlider.Value), instrument);
|
||||
}
|
||||
|
||||
private void PlaybackSliderKeyUp(GUIBoundKeyEventArgs args)
|
||||
{
|
||||
if (args.Function != EngineKeyFunctions.UIClick || _owner.Instrument is not {} instrument) return;
|
||||
if (args.Function != EngineKeyFunctions.UIClick || _owner.Instrument is not {} instrument)
|
||||
return;
|
||||
|
||||
EntitySystem.Get<InstrumentSystem>().SetPlayerTick(instrument.Owner, (int)Math.Ceiling(PlaybackSlider.Value), instrument);
|
||||
_owner.Instruments.SetPlayerTick(_owner.Owner, (int)Math.Ceiling(PlaybackSlider.Value), instrument);
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
base.Close();
|
||||
_owner.CloseBandMenu();
|
||||
_owner.CloseChannelsMenu();
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
if (_owner.Instrument == null) return;
|
||||
if (_owner.Instrument == null)
|
||||
return;
|
||||
|
||||
var hasMaster = _owner.Instrument.Master != null;
|
||||
BandButton.ToggleMode = hasMaster;
|
||||
BandButton.Pressed = hasMaster;
|
||||
BandButton.Disabled = _owner.Instrument.IsMidiOpen || _owner.Instrument.IsInputOpen;
|
||||
ChannelsButton.Disabled = !_owner.Instrument.IsRendererAlive;
|
||||
|
||||
if (!_owner.Instrument.IsMidiOpen)
|
||||
{
|
||||
@@ -214,7 +252,8 @@ namespace Content.Client.Instruments.UI
|
||||
return;
|
||||
}
|
||||
|
||||
if (PlaybackSlider.Grabbed) return;
|
||||
if (PlaybackSlider.Grabbed)
|
||||
return;
|
||||
|
||||
PlaybackSlider.MaxValue = _owner.Instrument.PlayerTotalTick;
|
||||
PlaybackSlider.SetValueWithoutEvent(_owner.Instrument.PlayerTick);
|
||||
|
||||
@@ -10,20 +10,11 @@ public sealed class InstrumentComponent : SharedInstrumentComponent
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
|
||||
[ViewVariables]
|
||||
public float Timer = 0f;
|
||||
|
||||
[ViewVariables]
|
||||
public int BatchesDropped = 0;
|
||||
|
||||
[ViewVariables]
|
||||
public int LaggedBatches = 0;
|
||||
|
||||
[ViewVariables]
|
||||
public int MidiEventCount = 0;
|
||||
|
||||
[ViewVariables]
|
||||
public uint LastSequencerTick = 0;
|
||||
[ViewVariables] public float Timer = 0f;
|
||||
[ViewVariables] public int BatchesDropped = 0;
|
||||
[ViewVariables] public int LaggedBatches = 0;
|
||||
[ViewVariables] public int MidiEventCount = 0;
|
||||
[ViewVariables] public uint LastSequencerTick = 0;
|
||||
|
||||
// TODO Instruments: Make this ECS
|
||||
public IPlayerSession? InstrumentPlayer =>
|
||||
@@ -34,4 +25,6 @@ public sealed class InstrumentComponent : SharedInstrumentComponent
|
||||
}
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class ActiveInstrumentComponent : Component {}
|
||||
public sealed class ActiveInstrumentComponent : Component
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,20 +1,44 @@
|
||||
using Content.Server.Administration;
|
||||
using Content.Server.Interaction;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Stunnable;
|
||||
using Content.Server.UserInterface;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Instruments;
|
||||
using Content.Shared.Instruments.UI;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Physics;
|
||||
using Content.Shared.Popups;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Audio.Midi;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Instruments;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed partial class InstrumentSystem : SharedInstrumentSystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IConsoleHost _conHost = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly StunSystem _stunSystem = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _userInterfaceSystem = default!;
|
||||
[Dependency] private readonly StunSystem _stuns = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _bui = default!;
|
||||
[Dependency] private readonly PopupSystem _popup = default!;
|
||||
[Dependency] private readonly TransformSystem _transform = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly InteractionSystem _interactions = default!;
|
||||
|
||||
private const float MaxInstrumentBandRange = 10f;
|
||||
|
||||
// Band Requests are queued and delayed both to avoid metagaming and to prevent spamming it, since it's expensive.
|
||||
private const float BandRequestDelay = 1.0f;
|
||||
private TimeSpan _bandRequestTimer = TimeSpan.Zero;
|
||||
private readonly List<InstrumentBandRequestBuiMessage> _bandRequestQueue = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -25,30 +49,62 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem
|
||||
SubscribeNetworkEvent<InstrumentMidiEventEvent>(OnMidiEventRx);
|
||||
SubscribeNetworkEvent<InstrumentStartMidiEvent>(OnMidiStart);
|
||||
SubscribeNetworkEvent<InstrumentStopMidiEvent>(OnMidiStop);
|
||||
SubscribeNetworkEvent<InstrumentSetMasterEvent>(OnMidiSetMaster);
|
||||
SubscribeNetworkEvent<InstrumentSetFilteredChannelEvent>(OnMidiSetFilteredChannel);
|
||||
|
||||
SubscribeLocalEvent<InstrumentComponent, BoundUIClosedEvent>(OnBoundUIClosed);
|
||||
SubscribeLocalEvent<InstrumentComponent, BoundUIOpenedEvent>(OnBoundUIOpened);
|
||||
SubscribeLocalEvent<InstrumentComponent, InstrumentBandRequestBuiMessage>(OnBoundUIRequestBands);
|
||||
|
||||
_conHost.RegisterCommand("addtoband", AddToBandCommand);
|
||||
}
|
||||
|
||||
[AdminCommand(AdminFlags.Fun)]
|
||||
private void AddToBandCommand(IConsoleShell shell, string _, string[] args)
|
||||
{
|
||||
if (!EntityUid.TryParse(args[0], out var firstUid))
|
||||
{
|
||||
shell.WriteError($"Cannot parse first Uid");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EntityUid.TryParse(args[1], out var secondUid))
|
||||
{
|
||||
shell.WriteError($"Cannot parse second Uid");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!HasComp<ActiveInstrumentComponent>(secondUid))
|
||||
{
|
||||
shell.WriteError($"Puppet instrument is not active!");
|
||||
return;
|
||||
}
|
||||
|
||||
var otherInstrument = Comp<InstrumentComponent>(secondUid);
|
||||
otherInstrument.Playing = true;
|
||||
otherInstrument.Master = firstUid;
|
||||
Dirty(secondUid, otherInstrument);
|
||||
}
|
||||
|
||||
private void OnMidiStart(InstrumentStartMidiEvent msg, EntitySessionEventArgs args)
|
||||
{
|
||||
var uid = msg.Uid;
|
||||
|
||||
if (!EntityManager.TryGetComponent(uid, out InstrumentComponent? instrument))
|
||||
if (!TryComp(uid, out InstrumentComponent? instrument))
|
||||
return;
|
||||
|
||||
if (args.SenderSession != instrument.InstrumentPlayer)
|
||||
return;
|
||||
|
||||
instrument.Playing = true;
|
||||
instrument.Dirty();
|
||||
Dirty(uid, instrument);
|
||||
}
|
||||
|
||||
private void OnMidiStop(InstrumentStopMidiEvent msg, EntitySessionEventArgs args)
|
||||
{
|
||||
var uid = msg.Uid;
|
||||
|
||||
if (!EntityManager.TryGetComponent(uid, out InstrumentComponent? instrument))
|
||||
if (!TryComp(uid, out InstrumentComponent? instrument))
|
||||
return;
|
||||
|
||||
if (args.SenderSession != instrument.InstrumentPlayer)
|
||||
@@ -57,6 +113,66 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem
|
||||
Clean(uid, instrument);
|
||||
}
|
||||
|
||||
private void OnMidiSetMaster(InstrumentSetMasterEvent msg, EntitySessionEventArgs args)
|
||||
{
|
||||
var uid = msg.Uid;
|
||||
var master = msg.Master;
|
||||
|
||||
if (!HasComp<ActiveInstrumentComponent>(uid))
|
||||
return;
|
||||
|
||||
if (!TryComp(uid, out InstrumentComponent? instrument))
|
||||
return;
|
||||
|
||||
if (args.SenderSession != instrument.InstrumentPlayer)
|
||||
return;
|
||||
|
||||
if (master != null)
|
||||
{
|
||||
if (!HasComp<ActiveInstrumentComponent>(master))
|
||||
return;
|
||||
|
||||
if (!TryComp<InstrumentComponent>(master, out var masterInstrument) || masterInstrument.Master != null)
|
||||
return;
|
||||
|
||||
instrument.Master = master;
|
||||
instrument.FilteredChannels.SetAll(false);
|
||||
instrument.Playing = true;
|
||||
Dirty(uid, instrument);
|
||||
return;
|
||||
}
|
||||
|
||||
// Cleanup when disabling master...
|
||||
if (master == null && instrument.Master != null)
|
||||
{
|
||||
Clean(uid, instrument);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMidiSetFilteredChannel(InstrumentSetFilteredChannelEvent msg, EntitySessionEventArgs args)
|
||||
{
|
||||
var uid = msg.Uid;
|
||||
|
||||
if (!TryComp(uid, out InstrumentComponent? instrument))
|
||||
return;
|
||||
|
||||
if (args.SenderSession != instrument.InstrumentPlayer)
|
||||
return;
|
||||
|
||||
if (msg.Channel == RobustMidiEvent.PercussionChannel && !instrument.AllowPercussion)
|
||||
return;
|
||||
|
||||
instrument.FilteredChannels[msg.Channel] = msg.Value;
|
||||
|
||||
if (msg.Value)
|
||||
{
|
||||
// Prevent stuck notes when turning off a channel... Shrimple.
|
||||
RaiseNetworkEvent(new InstrumentMidiEventEvent(uid, new []{RobustMidiEvent.AllNotesOff((byte)msg.Channel, 0)}));
|
||||
}
|
||||
|
||||
Dirty(uid, instrument);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
@@ -70,7 +186,7 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem
|
||||
return;
|
||||
|
||||
if (HasComp<ActiveInstrumentComponent>(uid)
|
||||
&& _userInterfaceSystem.TryGetUi(uid, args.UiKey, out var bui)
|
||||
&& _bui.TryGetUi(uid, args.UiKey, out var bui)
|
||||
&& bui.SubscribedSessions.Count == 0)
|
||||
{
|
||||
RemComp<ActiveInstrumentComponent>(uid);
|
||||
@@ -88,6 +204,63 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem
|
||||
Clean(uid, component);
|
||||
}
|
||||
|
||||
private void OnBoundUIRequestBands(EntityUid uid, InstrumentComponent component, InstrumentBandRequestBuiMessage args)
|
||||
{
|
||||
foreach (var request in _bandRequestQueue)
|
||||
{
|
||||
// Prevent spamming requests for the same entity.
|
||||
if (request.Entity == args.Entity)
|
||||
return;
|
||||
}
|
||||
|
||||
_bandRequestQueue.Add(args);
|
||||
}
|
||||
|
||||
public (EntityUid, string)[] GetBands(EntityUid uid)
|
||||
{
|
||||
var metadataQuery = EntityManager.GetEntityQuery<MetaDataComponent>();
|
||||
|
||||
if (Deleted(uid, metadataQuery))
|
||||
return Array.Empty<(EntityUid, string)>();
|
||||
|
||||
var list = new ValueList<(EntityUid, string)>();
|
||||
var instrumentQuery = EntityManager.GetEntityQuery<InstrumentComponent>();
|
||||
|
||||
if (!TryComp(uid, out InstrumentComponent? originInstrument)
|
||||
|| originInstrument.InstrumentPlayer?.AttachedEntity is not {} originPlayer)
|
||||
return Array.Empty<(EntityUid, string)>();
|
||||
|
||||
// It's probably faster to get all possible active instruments than all entities in range
|
||||
var activeEnumerator = EntityManager.EntityQueryEnumerator<ActiveInstrumentComponent>();
|
||||
while (activeEnumerator.MoveNext(out var entity, out _))
|
||||
{
|
||||
if (entity == uid)
|
||||
continue;
|
||||
|
||||
// Don't grab puppet instruments.
|
||||
if (!instrumentQuery.TryGetComponent(entity, out var instrument) || instrument.Master != null)
|
||||
continue;
|
||||
|
||||
// We want to use the instrument player's name.
|
||||
if (instrument.InstrumentPlayer?.AttachedEntity is not {} playerUid)
|
||||
continue;
|
||||
|
||||
// Maybe a bit expensive but oh well GetBands is queued and has a timer anyway.
|
||||
// Make sure the instrument is visible, uses the Opaque collision group so this works across windows etc.
|
||||
if (!_interactions.InRangeUnobstructed(uid, entity, MaxInstrumentBandRange,
|
||||
CollisionGroup.Opaque, e => e == playerUid || e == originPlayer))
|
||||
continue;
|
||||
|
||||
if (!metadataQuery.TryGetComponent(playerUid, out var playerMetadata)
|
||||
|| !metadataQuery.TryGetComponent(entity, out var metadata))
|
||||
continue;
|
||||
|
||||
list.Add((entity, $"{playerMetadata.EntityName} - {metadata.EntityName}"));
|
||||
}
|
||||
|
||||
return list.ToArray();
|
||||
}
|
||||
|
||||
public void Clean(EntityUid uid, InstrumentComponent? instrument = null)
|
||||
{
|
||||
if (!Resolve(uid, ref instrument))
|
||||
@@ -95,14 +268,19 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem
|
||||
|
||||
if (instrument.Playing)
|
||||
{
|
||||
// Reset puppet instruments too.
|
||||
RaiseNetworkEvent(new InstrumentMidiEventEvent(uid, new[]{RobustMidiEvent.SystemReset(0)}));
|
||||
|
||||
RaiseNetworkEvent(new InstrumentStopMidiEvent(uid));
|
||||
}
|
||||
|
||||
instrument.Playing = false;
|
||||
instrument.Master = null;
|
||||
instrument.FilteredChannels.SetAll(false);
|
||||
instrument.LastSequencerTick = 0;
|
||||
instrument.BatchesDropped = 0;
|
||||
instrument.LaggedBatches = 0;
|
||||
instrument.Dirty();
|
||||
Dirty(uid, instrument);
|
||||
}
|
||||
|
||||
private void OnMidiEventRx(InstrumentMidiEventEvent msg, EntitySessionEventArgs args)
|
||||
@@ -142,12 +320,13 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem
|
||||
{
|
||||
if (instrument.LaggedBatches == (int) (MaxMidiLaggedBatches * (1 / 3d) + 1))
|
||||
{
|
||||
attached.PopupMessage(
|
||||
Loc.GetString("instrument-component-finger-cramps-light-message"));
|
||||
} else if (instrument.LaggedBatches == (int) (MaxMidiLaggedBatches * (2 / 3d) + 1))
|
||||
_popup.PopupEntity(Loc.GetString("instrument-component-finger-cramps-light-message"),
|
||||
uid, attached, PopupType.SmallCaution);
|
||||
}
|
||||
else if (instrument.LaggedBatches == (int) (MaxMidiLaggedBatches * (2 / 3d) + 1))
|
||||
{
|
||||
attached.PopupMessage(
|
||||
Loc.GetString("instrument-component-finger-cramps-serious-message"));
|
||||
_popup.PopupEntity(Loc.GetString("instrument-component-finger-cramps-serious-message"),
|
||||
uid, attached, PopupType.MediumCaution);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,24 +344,58 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem
|
||||
send = false;
|
||||
}
|
||||
|
||||
instrument.LastSequencerTick = Math.Max(maxTick, minTick);
|
||||
|
||||
if (send || !instrument.RespectMidiLimits)
|
||||
{
|
||||
RaiseNetworkEvent(msg);
|
||||
}
|
||||
|
||||
instrument.LastSequencerTick = Math.Max(maxTick, minTick);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
foreach (var (_, instrument) in EntityManager.EntityQuery<ActiveInstrumentComponent, InstrumentComponent>(true))
|
||||
if (_bandRequestQueue.Count > 0 && _bandRequestTimer < _timing.RealTime)
|
||||
{
|
||||
if (instrument.DirtyRenderer)
|
||||
_bandRequestTimer = _timing.RealTime.Add(TimeSpan.FromSeconds(BandRequestDelay));
|
||||
|
||||
foreach (var request in _bandRequestQueue)
|
||||
{
|
||||
Dirty(instrument);
|
||||
instrument.DirtyRenderer = false;
|
||||
var nearby = GetBands(request.Entity);
|
||||
_bui.TrySendUiMessage(request.Entity, request.UiKey, new InstrumentBandResponseBuiMessage(nearby),
|
||||
(IPlayerSession)request.Session);
|
||||
}
|
||||
|
||||
_bandRequestQueue.Clear();
|
||||
}
|
||||
|
||||
var activeQuery = EntityManager.GetEntityQuery<ActiveInstrumentComponent>();
|
||||
var metadataQuery = EntityManager.GetEntityQuery<MetaDataComponent>();
|
||||
var transformQuery = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
|
||||
var query = AllEntityQuery<ActiveInstrumentComponent, InstrumentComponent>();
|
||||
while (query.MoveNext(out var uid, out _, out var instrument))
|
||||
{
|
||||
if (instrument.Master is {} master)
|
||||
{
|
||||
if (Deleted(master, metadataQuery))
|
||||
{
|
||||
Clean(uid, instrument);
|
||||
}
|
||||
|
||||
var masterActive = activeQuery.CompOrNull(master);
|
||||
if (masterActive == null)
|
||||
{
|
||||
Clean(uid, instrument);
|
||||
}
|
||||
|
||||
var trans = transformQuery.GetComponent(uid);
|
||||
var masterTrans = transformQuery.GetComponent(master);
|
||||
if (!masterTrans.Coordinates.InRange(EntityManager, _transform, trans.Coordinates, 10f))
|
||||
{
|
||||
Clean(uid, instrument);
|
||||
}
|
||||
}
|
||||
|
||||
if (instrument.RespectMidiLimits &&
|
||||
@@ -191,16 +404,17 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem
|
||||
{
|
||||
if (instrument.InstrumentPlayer?.AttachedEntity is {Valid: true} mob)
|
||||
{
|
||||
_stunSystem.TryParalyze(mob, TimeSpan.FromSeconds(1), true);
|
||||
_stuns.TryParalyze(mob, TimeSpan.FromSeconds(1), true);
|
||||
|
||||
instrument.Owner.PopupMessage(mob, Loc.GetString("instrument-component-finger-cramps-max-message"));
|
||||
_popup.PopupEntity(Loc.GetString("instrument-component-finger-cramps-max-message"),
|
||||
uid, mob, PopupType.LargeCaution);
|
||||
}
|
||||
|
||||
// Just in case
|
||||
Clean((instrument).Owner);
|
||||
Clean(uid);
|
||||
|
||||
if (instrument.UserInterface is not null)
|
||||
_userInterfaceSystem.CloseAll(instrument.UserInterface);
|
||||
_bui.CloseAll(instrument.UserInterface);
|
||||
}
|
||||
|
||||
instrument.Timer += frameTime;
|
||||
@@ -219,7 +433,7 @@ public sealed partial class InstrumentSystem : SharedInstrumentSystem
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
if (_userInterfaceSystem.TryGetUi(uid, InstrumentUiKey.Key, out var bui))
|
||||
_userInterfaceSystem.ToggleUi(bui, session);
|
||||
if (_bui.TryGetUi(uid, InstrumentUiKey.Key, out var bui))
|
||||
_bui.ToggleUi(bui, session);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,38 @@
|
||||
using System.Collections;
|
||||
using Robust.Shared.Audio.Midi;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Instruments;
|
||||
|
||||
[NetworkedComponent, Access(typeof(SharedInstrumentSystem))]
|
||||
public abstract class SharedInstrumentComponent : Component
|
||||
[NetworkedComponent]
|
||||
[AutoGenerateComponentState(true)]
|
||||
[Access(typeof(SharedInstrumentSystem))]
|
||||
public abstract partial class SharedInstrumentComponent : Component
|
||||
{
|
||||
[ViewVariables]
|
||||
[ViewVariables, AutoNetworkedField]
|
||||
public bool Playing { get; set; }
|
||||
|
||||
[DataField("program"), ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("program"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
|
||||
public byte InstrumentProgram { get; set; }
|
||||
|
||||
[DataField("bank"), ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("bank"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
|
||||
public byte InstrumentBank { get; set; }
|
||||
|
||||
[DataField("allowPercussion"), ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("allowPercussion"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
|
||||
public bool AllowPercussion { get; set; }
|
||||
|
||||
[DataField("allowProgramChange"), ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("allowProgramChange"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
|
||||
public bool AllowProgramChange { get ; set; }
|
||||
|
||||
[DataField("respectMidiLimits"), ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("respectMidiLimits"), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
|
||||
public bool RespectMidiLimits { get; set; } = true;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[Access(typeof(SharedInstrumentSystem), Other = AccessPermissions.ReadWrite)] // FIXME Friends
|
||||
public bool DirtyRenderer { get; set; }
|
||||
[ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
|
||||
public EntityUid? Master { get; set; } = null;
|
||||
|
||||
[ViewVariables, AutoNetworkedField]
|
||||
public BitArray FilteredChannels { get; set; } = new(RobustMidiEvent.MaxChannels, true);
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +50,40 @@ public sealed class InstrumentStopMidiEvent : EntityEventArgs
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send from the client to the server to set a master instrument.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class InstrumentSetMasterEvent : EntityEventArgs
|
||||
{
|
||||
public EntityUid Uid { get; }
|
||||
public EntityUid? Master { get; }
|
||||
|
||||
public InstrumentSetMasterEvent(EntityUid uid, EntityUid? master)
|
||||
{
|
||||
Uid = uid;
|
||||
Master = master;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send from the client to the server to set a master instrument channel.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class InstrumentSetFilteredChannelEvent : EntityEventArgs
|
||||
{
|
||||
public EntityUid Uid { get; }
|
||||
public int Channel { get; }
|
||||
public bool Value { get; }
|
||||
|
||||
public InstrumentSetFilteredChannelEvent(EntityUid uid, int channel, bool value)
|
||||
{
|
||||
Uid = uid;
|
||||
Channel = channel;
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This message is sent to the client to start the synth.
|
||||
/// </summary>
|
||||
@@ -75,27 +114,6 @@ public sealed class InstrumentMidiEventEvent : EntityEventArgs
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class InstrumentState : ComponentState
|
||||
{
|
||||
public bool Playing { get; }
|
||||
public byte InstrumentProgram { get; }
|
||||
public byte InstrumentBank { get; }
|
||||
public bool AllowPercussion { get; }
|
||||
public bool AllowProgramChange { get; }
|
||||
public bool RespectMidiLimits { get; }
|
||||
|
||||
public InstrumentState(bool playing, byte instrumentProgram, byte instrumentBank, bool allowPercussion, bool allowProgramChange, bool respectMidiLimits)
|
||||
{
|
||||
Playing = playing;
|
||||
InstrumentProgram = instrumentProgram;
|
||||
InstrumentBank = instrumentBank;
|
||||
AllowPercussion = allowPercussion;
|
||||
AllowProgramChange = allowProgramChange;
|
||||
RespectMidiLimits = respectMidiLimits;
|
||||
}
|
||||
}
|
||||
|
||||
[NetSerializable, Serializable]
|
||||
public enum InstrumentUiKey
|
||||
{
|
||||
|
||||
@@ -1,57 +1,32 @@
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Instruments;
|
||||
|
||||
public abstract class SharedInstrumentSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<SharedInstrumentComponent, ComponentGetState>(OnGetState);
|
||||
SubscribeLocalEvent<SharedInstrumentComponent, ComponentHandleState>(OnHandleState);
|
||||
SubscribeLocalEvent<SharedInstrumentComponent, AfterAutoHandleStateEvent>(AfterHandleInstrumentState);
|
||||
}
|
||||
|
||||
public virtual void SetupRenderer(EntityUid uid, bool fromStateChange, SharedInstrumentComponent? instrument = null)
|
||||
{ }
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void EndRenderer(EntityUid uid, bool fromStateChange, SharedInstrumentComponent? instrument = null)
|
||||
{ }
|
||||
{
|
||||
}
|
||||
|
||||
public void SetInstrumentProgram(SharedInstrumentComponent component, byte program, byte bank)
|
||||
{
|
||||
component.InstrumentBank = bank;
|
||||
component.InstrumentProgram = program;
|
||||
component.DirtyRenderer = true;
|
||||
Dirty(component);
|
||||
}
|
||||
|
||||
private void OnGetState(EntityUid uid, SharedInstrumentComponent instrument, ref ComponentGetState args)
|
||||
{
|
||||
args.State =
|
||||
new InstrumentState(instrument.Playing, instrument.InstrumentProgram, instrument.InstrumentBank,
|
||||
instrument.AllowPercussion, instrument.AllowProgramChange, instrument.RespectMidiLimits);
|
||||
}
|
||||
|
||||
private void OnHandleState(EntityUid uid, SharedInstrumentComponent instrument, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not InstrumentState state)
|
||||
return;
|
||||
|
||||
if (state.Playing)
|
||||
private void AfterHandleInstrumentState(EntityUid uid, SharedInstrumentComponent instrument, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
if(instrument.Playing)
|
||||
SetupRenderer(uid, true, instrument);
|
||||
}
|
||||
else
|
||||
{
|
||||
EndRenderer(uid, true, instrument);
|
||||
}
|
||||
|
||||
instrument.Playing = state.Playing;
|
||||
instrument.AllowPercussion = state.AllowPercussion;
|
||||
instrument.AllowProgramChange = state.AllowProgramChange;
|
||||
instrument.InstrumentBank = state.InstrumentBank;
|
||||
instrument.InstrumentProgram = state.InstrumentProgram;
|
||||
instrument.DirtyRenderer = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Instruments.UI;
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class InstrumentBandRequestBuiMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class InstrumentBandResponseBuiMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public (EntityUid, string)[] Nearby { get; set; }
|
||||
|
||||
public InstrumentBandResponseBuiMessage((EntityUid, string)[] nearby)
|
||||
{
|
||||
Nearby = nearby;
|
||||
}
|
||||
}
|
||||
@@ -8,9 +8,17 @@ instruments-component-menu-no-midi-support = MIDI support is currently not
|
||||
FluidSynth or a development package
|
||||
for FluidSynth.
|
||||
instruments-component-menu-input-button = MIDI Input
|
||||
instruments-component-menu-play-button = Play MIDI File
|
||||
instruments-component-menu-band-button = Join Band
|
||||
instruments-component-menu-play-button = Play MIDI
|
||||
instruments-component-menu-loop-button = Loop
|
||||
instruments-component-menu-channels-button = Channels
|
||||
instruments-component-menu-stop-button = Stop
|
||||
instruments-component-band-menu = Choose band leader
|
||||
instrument-component-band-refresh = Refresh
|
||||
instruments-component-channels-menu = MIDI Channel Selection
|
||||
instrument-component-channel-name = MIDI Channel {$number}
|
||||
instruments-component-channels-all-button = All
|
||||
instruments-component-channels-clear-button = Clear
|
||||
|
||||
# SwappableInstrumentComponent
|
||||
swappable-instrument-component-style-set = Style set to "{$style}"
|
||||
|
||||
Submodule RobustToolbox updated: 8eae802fb6...aa52e8c2ef
@@ -550,6 +550,7 @@ public sealed class $CLASS$ : Shared$CLASS$ {
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=LOOC/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Magboots/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=metabolizable/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=metagaming/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=mommi/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Monstermos/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Nar_0027/@EntryIndexedValue">True</s:Boolean>
|
||||
@@ -578,6 +579,7 @@ public sealed class $CLASS$ : Shared$CLASS$ {
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=saltern/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=sandboxing/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Serilog/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Shrimple/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=singulo/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Smokable/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=smokables/@EntryIndexedValue">True</s:Boolean>
|
||||
|
||||
Reference in New Issue
Block a user