| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456 |
- using Content.Server.Administration;
- using Content.Server.Interaction;
- using Content.Server.Popups;
- using Content.Server.Stunnable;
- using Content.Shared.Administration;
- using Content.Shared.Examine;
- using Content.Shared.Instruments;
- using Content.Shared.Instruments.UI;
- using Content.Shared.Physics;
- using Content.Shared.Popups;
- using JetBrains.Annotations;
- using Robust.Server.GameObjects;
- using Robust.Shared.Audio.Midi;
- using Robust.Shared.Collections;
- using Robust.Shared.Configuration;
- using Robust.Shared.Console;
- using Robust.Shared.GameStates;
- using Robust.Shared.Player;
- using Robust.Shared.Timing;
- 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 _stuns = default!;
- [Dependency] private readonly UserInterfaceSystem _bui = default!;
- [Dependency] private readonly PopupSystem _popup = default!;
- [Dependency] private readonly TransformSystem _transform = default!;
- [Dependency] private readonly ExamineSystemShared _examineSystem = 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()
- {
- base.Initialize();
- InitializeCVars();
- SubscribeNetworkEvent<InstrumentMidiEventEvent>(OnMidiEventRx);
- SubscribeNetworkEvent<InstrumentStartMidiEvent>(OnMidiStart);
- SubscribeNetworkEvent<InstrumentStopMidiEvent>(OnMidiStop);
- SubscribeNetworkEvent<InstrumentSetMasterEvent>(OnMidiSetMaster);
- SubscribeNetworkEvent<InstrumentSetFilteredChannelEvent>(OnMidiSetFilteredChannel);
- Subs.BuiEvents<InstrumentComponent>(InstrumentUiKey.Key, subs =>
- {
- subs.Event<BoundUIClosedEvent>(OnBoundUIClosed);
- subs.Event<BoundUIOpenedEvent>(OnBoundUIOpened);
- subs.Event<InstrumentBandRequestBuiMessage>(OnBoundUIRequestBands);
- });
- SubscribeLocalEvent<InstrumentComponent, ComponentGetState>(OnStrumentGetState);
- _conHost.RegisterCommand("addtoband", AddToBandCommand);
- }
- private void OnStrumentGetState(EntityUid uid, InstrumentComponent component, ref ComponentGetState args)
- {
- args.State = new InstrumentComponentState()
- {
- Playing = component.Playing,
- InstrumentProgram = component.InstrumentProgram,
- InstrumentBank = component.InstrumentBank,
- AllowPercussion = component.AllowPercussion,
- AllowProgramChange = component.AllowProgramChange,
- RespectMidiLimits = component.RespectMidiLimits,
- Master = GetNetEntity(component.Master),
- FilteredChannels = component.FilteredChannels
- };
- }
- [AdminCommand(AdminFlags.Fun)]
- private void AddToBandCommand(IConsoleShell shell, string _, string[] args)
- {
- if (!NetEntity.TryParse(args[0], out var firstUidNet) || !TryGetEntity(firstUidNet, out var firstUid))
- {
- shell.WriteError($"Cannot parse first Uid");
- return;
- }
- if (!NetEntity.TryParse(args[1], out var secondUidNet) || !TryGetEntity(secondUidNet, 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.Value);
- otherInstrument.Playing = true;
- otherInstrument.Master = firstUid;
- Dirty(secondUid.Value, otherInstrument);
- }
- private void OnMidiStart(InstrumentStartMidiEvent msg, EntitySessionEventArgs args)
- {
- var uid = GetEntity(msg.Uid);
- if (!TryComp(uid, out InstrumentComponent? instrument))
- return;
- if (args.SenderSession.AttachedEntity != instrument.InstrumentPlayer)
- return;
- instrument.Playing = true;
- Dirty(uid, instrument);
- }
- private void OnMidiStop(InstrumentStopMidiEvent msg, EntitySessionEventArgs args)
- {
- var uid = GetEntity(msg.Uid);
- if (!TryComp(uid, out InstrumentComponent? instrument))
- return;
- if (args.SenderSession.AttachedEntity != instrument.InstrumentPlayer)
- return;
- Clean(uid, instrument);
- }
- private void OnMidiSetMaster(InstrumentSetMasterEvent msg, EntitySessionEventArgs args)
- {
- var uid = GetEntity(msg.Uid);
- var master = GetEntity(msg.Master);
- if (!HasComp<ActiveInstrumentComponent>(uid))
- return;
- if (!TryComp(uid, out InstrumentComponent? instrument))
- return;
- if (args.SenderSession.AttachedEntity != 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 = GetEntity(msg.Uid);
- if (!TryComp(uid, out InstrumentComponent? instrument))
- return;
- if (args.SenderSession.AttachedEntity != 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(msg.Uid, new []{RobustMidiEvent.AllNotesOff((byte)msg.Channel, 0)}));
- }
- Dirty(uid, instrument);
- }
- private void OnBoundUIClosed(EntityUid uid, InstrumentComponent component, BoundUIClosedEvent args)
- {
- if (HasComp<ActiveInstrumentComponent>(uid)
- && !_bui.IsUiOpen(uid, args.UiKey))
- {
- RemComp<ActiveInstrumentComponent>(uid);
- }
- Clean(uid, component);
- }
- private void OnBoundUIOpened(EntityUid uid, InstrumentComponent component, BoundUIOpenedEvent args)
- {
- EnsureComp<ActiveInstrumentComponent>(uid);
- 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 (NetEntity, string)[] GetBands(EntityUid uid)
- {
- var metadataQuery = EntityManager.GetEntityQuery<MetaDataComponent>();
- if (Deleted(uid, metadataQuery))
- return Array.Empty<(NetEntity, string)>();
- var list = new ValueList<(NetEntity, string)>();
- var instrumentQuery = EntityManager.GetEntityQuery<InstrumentComponent>();
- if (!TryComp(uid, out InstrumentComponent? originInstrument)
- || originInstrument.InstrumentPlayer is not {} originPlayer)
- return Array.Empty<(NetEntity, 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 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
- if (!_examineSystem.InRangeUnOccluded(uid, entity, MaxInstrumentBandRange, e => e == playerUid || e == originPlayer))
- continue;
- if (!metadataQuery.TryGetComponent(playerUid, out var playerMetadata)
- || !metadataQuery.TryGetComponent(entity, out var metadata))
- continue;
- list.Add((GetNetEntity(entity), $"{playerMetadata.EntityName} - {metadata.EntityName}"));
- }
- return list.ToArray();
- }
- public void Clean(EntityUid uid, InstrumentComponent? instrument = null)
- {
- if (!Resolve(uid, ref instrument))
- return;
- if (instrument.Playing)
- {
- var netUid = GetNetEntity(uid);
- // Reset puppet instruments too.
- RaiseNetworkEvent(new InstrumentMidiEventEvent(netUid, new[]{RobustMidiEvent.SystemReset(0)}));
- RaiseNetworkEvent(new InstrumentStopMidiEvent(netUid));
- }
- instrument.Playing = false;
- instrument.Master = null;
- instrument.FilteredChannels.SetAll(false);
- instrument.LastSequencerTick = 0;
- instrument.BatchesDropped = 0;
- instrument.LaggedBatches = 0;
- Dirty(uid, instrument);
- }
- private void OnMidiEventRx(InstrumentMidiEventEvent msg, EntitySessionEventArgs args)
- {
- var uid = GetEntity(msg.Uid);
- if (!TryComp(uid, out InstrumentComponent? instrument))
- return;
- if (!instrument.Playing
- || args.SenderSession.AttachedEntity != instrument.InstrumentPlayer
- || instrument.InstrumentPlayer == null
- || args.SenderSession.AttachedEntity is not { } attached)
- {
- return;
- }
- var send = true;
- var minTick = uint.MaxValue;
- var maxTick = uint.MinValue;
- for (var i = 0; i < msg.MidiEvent.Length; i++)
- {
- var tick = msg.MidiEvent[i].Tick;
- if (tick < minTick)
- minTick = tick;
- if (tick > maxTick)
- maxTick = tick;
- }
- if (instrument.LastSequencerTick > minTick)
- {
- instrument.LaggedBatches++;
- if (instrument.RespectMidiLimits)
- {
- if (instrument.LaggedBatches == (int) (MaxMidiLaggedBatches * (1 / 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))
- {
- _popup.PopupEntity(Loc.GetString("instrument-component-finger-cramps-serious-message"),
- uid, attached, PopupType.MediumCaution);
- }
- }
- if (instrument.LaggedBatches > MaxMidiLaggedBatches)
- {
- send = false;
- }
- }
- if (++instrument.MidiEventCount > MaxMidiEventsPerSecond
- || msg.MidiEvent.Length > MaxMidiEventsPerBatch)
- {
- instrument.BatchesDropped++;
- send = false;
- }
- instrument.LastSequencerTick = Math.Max(maxTick, minTick);
- if (send || !instrument.RespectMidiLimits)
- {
- RaiseNetworkEvent(msg);
- }
- }
- public override void Update(float frameTime)
- {
- base.Update(frameTime);
- if (_bandRequestQueue.Count > 0 && _bandRequestTimer < _timing.RealTime)
- {
- _bandRequestTimer = _timing.RealTime.Add(TimeSpan.FromSeconds(BandRequestDelay));
- foreach (var request in _bandRequestQueue)
- {
- var entity = GetEntity(request.Entity);
- var nearby = GetBands(entity);
- _bui.ServerSendUiMessage(entity, request.UiKey, new InstrumentBandResponseBuiMessage(nearby), request.Actor);
- }
- _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 (!_transform.InRange(masterTrans.Coordinates, trans.Coordinates, 10f)
- )
- {
- Clean(uid, instrument);
- }
- }
- if (instrument.RespectMidiLimits &&
- (instrument.BatchesDropped >= MaxMidiBatchesDropped
- || instrument.LaggedBatches >= MaxMidiLaggedBatches))
- {
- if (instrument.InstrumentPlayer is {Valid: true} mob)
- {
- _stuns.TryParalyze(mob, TimeSpan.FromSeconds(1), true);
- _popup.PopupEntity(Loc.GetString("instrument-component-finger-cramps-max-message"),
- uid, mob, PopupType.LargeCaution);
- }
- // Just in case
- Clean(uid);
- _bui.CloseUi(uid, InstrumentUiKey.Key);
- }
- instrument.Timer += frameTime;
- if (instrument.Timer < 1)
- continue;
- instrument.Timer = 0f;
- instrument.MidiEventCount = 0;
- instrument.LaggedBatches = 0;
- instrument.BatchesDropped = 0;
- }
- }
- public void ToggleInstrumentUi(EntityUid uid, EntityUid actor, InstrumentComponent? component = null)
- {
- if (!Resolve(uid, ref component))
- return;
- _bui.TryToggleUi(uid, InstrumentUiKey.Key, actor);
- }
- public override bool ResolveInstrument(EntityUid uid, ref SharedInstrumentComponent? component)
- {
- if (component is not null)
- return true;
- TryComp<InstrumentComponent>(uid, out var localComp);
- component = localComp;
- return component != null;
- }
- }
|