| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293 |
- using Content.Shared.Interaction;
- using Content.Shared.Interaction.Events;
- using Content.Shared.Item.ItemToggle.Components;
- using Content.Shared.Popups;
- using Content.Shared.Temperature;
- using Content.Shared.Toggleable;
- using Content.Shared.Verbs;
- using Content.Shared.Wieldable;
- using Robust.Shared.Audio;
- using Robust.Shared.Audio.Systems;
- using Robust.Shared.Network;
- namespace Content.Shared.Item.ItemToggle;
- /// <summary>
- /// Handles generic item toggles, like a welder turning on and off, or an e-sword.
- /// </summary>
- /// <remarks>
- /// If you need extended functionality (e.g. requiring power) then add a new component and use events.
- /// </remarks>
- public sealed class ItemToggleSystem : EntitySystem
- {
- [Dependency] private readonly INetManager _netManager = default!;
- [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
- [Dependency] private readonly SharedAudioSystem _audio = default!;
- [Dependency] private readonly SharedPopupSystem _popup = default!;
- private EntityQuery<ItemToggleComponent> _query;
- public override void Initialize()
- {
- base.Initialize();
- _query = GetEntityQuery<ItemToggleComponent>();
- SubscribeLocalEvent<ItemToggleComponent, ComponentStartup>(OnStartup);
- SubscribeLocalEvent<ItemToggleComponent, MapInitEvent>(OnMapInit);
- SubscribeLocalEvent<ItemToggleComponent, ItemUnwieldedEvent>(TurnOffOnUnwielded);
- SubscribeLocalEvent<ItemToggleComponent, ItemWieldedEvent>(TurnOnOnWielded);
- SubscribeLocalEvent<ItemToggleComponent, UseInHandEvent>(OnUseInHand);
- SubscribeLocalEvent<ItemToggleComponent, GetVerbsEvent<ActivationVerb>>(OnActivateVerb);
- SubscribeLocalEvent<ItemToggleComponent, ActivateInWorldEvent>(OnActivate);
- SubscribeLocalEvent<ItemToggleHotComponent, IsHotEvent>(OnIsHotEvent);
- SubscribeLocalEvent<ItemToggleActiveSoundComponent, ItemToggledEvent>(UpdateActiveSound);
- }
- private void OnStartup(Entity<ItemToggleComponent> ent, ref ComponentStartup args)
- {
- UpdateVisuals(ent);
- }
- private void OnMapInit(Entity<ItemToggleComponent> ent, ref MapInitEvent args)
- {
- if (!ent.Comp.Activated)
- return;
- var ev = new ItemToggledEvent(Predicted: ent.Comp.Predictable, Activated: ent.Comp.Activated, User: null);
- RaiseLocalEvent(ent, ref ev);
- }
- private void OnUseInHand(Entity<ItemToggleComponent> ent, ref UseInHandEvent args)
- {
- if (args.Handled || !ent.Comp.OnUse)
- return;
- args.Handled = true;
- Toggle((ent, ent.Comp), args.User, predicted: ent.Comp.Predictable);
- }
- private void OnActivateVerb(Entity<ItemToggleComponent> ent, ref GetVerbsEvent<ActivationVerb> args)
- {
- if (!args.CanAccess || !args.CanInteract || !ent.Comp.OnActivate)
- return;
- var user = args.User;
- args.Verbs.Add(new ActivationVerb()
- {
- Text = !ent.Comp.Activated ? Loc.GetString(ent.Comp.VerbToggleOn) : Loc.GetString(ent.Comp.VerbToggleOff),
- Act = () =>
- {
- Toggle((ent.Owner, ent.Comp), user, predicted: ent.Comp.Predictable);
- }
- });
- }
- private void OnActivate(Entity<ItemToggleComponent> ent, ref ActivateInWorldEvent args)
- {
- if (args.Handled || !ent.Comp.OnActivate)
- return;
- args.Handled = true;
- Toggle((ent.Owner, ent.Comp), args.User, predicted: ent.Comp.Predictable);
- }
- /// <summary>
- /// Used when an item is attempted to be toggled.
- /// Sets its state to the opposite of what it is.
- /// </summary>
- /// <returns>Same as <see cref="TrySetActive"/></returns>
- public bool Toggle(Entity<ItemToggleComponent?> ent, EntityUid? user = null, bool predicted = true)
- {
- if (!_query.Resolve(ent, ref ent.Comp, false))
- return false;
- return TrySetActive(ent, !ent.Comp.Activated, user, predicted);
- }
- /// <summary>
- /// Tries to set the activated bool from a value.
- /// </summary>
- /// <returns>false if the attempt fails for any reason</returns>
- public bool TrySetActive(Entity<ItemToggleComponent?> ent, bool active, EntityUid? user = null, bool predicted = true)
- {
- if (active)
- return TryActivate(ent, user, predicted: predicted);
- else
- return TryDeactivate(ent, user, predicted: predicted);
- }
- /// <summary>
- /// Used when an item is attempting to be activated. It returns false if the attempt fails any reason, interrupting the activation.
- /// </summary>
- public bool TryActivate(Entity<ItemToggleComponent?> ent, EntityUid? user = null, bool predicted = true)
- {
- if (!_query.Resolve(ent, ref ent.Comp, false))
- return false;
- var uid = ent.Owner;
- var comp = ent.Comp;
- if (comp.Activated)
- return true;
- if (!comp.Predictable && _netManager.IsClient)
- return true;
- var attempt = new ItemToggleActivateAttemptEvent(user);
- RaiseLocalEvent(uid, ref attempt);
- if (!comp.Predictable) predicted = false;
- if (attempt.Cancelled)
- {
- if (predicted)
- _audio.PlayPredicted(comp.SoundFailToActivate, uid, user);
- else
- _audio.PlayPvs(comp.SoundFailToActivate, uid);
- if (attempt.Popup != null && user != null)
- {
- if (predicted)
- _popup.PopupClient(attempt.Popup, uid, user.Value);
- else
- _popup.PopupEntity(attempt.Popup, uid, user.Value);
- }
- return false;
- }
- Activate((uid, comp), predicted, user);
- return true;
- }
- /// <summary>
- /// Used when an item is attempting to be deactivated. It returns false if the attempt fails any reason, interrupting the deactivation.
- /// </summary>
- public bool TryDeactivate(Entity<ItemToggleComponent?> ent, EntityUid? user = null, bool predicted = true)
- {
- if (!_query.Resolve(ent, ref ent.Comp, false))
- return false;
- var uid = ent.Owner;
- var comp = ent.Comp;
- if (!comp.Activated)
- return true;
- if (!comp.Predictable && _netManager.IsClient)
- return true;
- var attempt = new ItemToggleDeactivateAttemptEvent(user);
- RaiseLocalEvent(uid, ref attempt);
- if (attempt.Cancelled)
- return false;
- if (!comp.Predictable) predicted = false;
- Deactivate((uid, comp), predicted, user);
- return true;
- }
- private void Activate(Entity<ItemToggleComponent> ent, bool predicted, EntityUid? user = null)
- {
- var (uid, comp) = ent;
- var soundToPlay = comp.SoundActivate;
- if (predicted)
- _audio.PlayPredicted(soundToPlay, uid, user);
- else
- _audio.PlayPvs(soundToPlay, uid);
- comp.Activated = true;
- UpdateVisuals((uid, comp));
- Dirty(uid, comp);
- var toggleUsed = new ItemToggledEvent(predicted, Activated: true, user);
- RaiseLocalEvent(uid, ref toggleUsed);
- }
- /// <summary>
- /// Used to make the actual changes to the item's components on deactivation.
- /// </summary>
- private void Deactivate(Entity<ItemToggleComponent> ent, bool predicted, EntityUid? user = null)
- {
- var (uid, comp) = ent;
- var soundToPlay = comp.SoundDeactivate;
- if (predicted)
- _audio.PlayPredicted(soundToPlay, uid, user);
- else
- _audio.PlayPvs(soundToPlay, uid);
- comp.Activated = false;
- UpdateVisuals((uid, comp));
- Dirty(uid, comp);
- var toggleUsed = new ItemToggledEvent(predicted, Activated: false, user);
- RaiseLocalEvent(uid, ref toggleUsed);
- }
- private void UpdateVisuals(Entity<ItemToggleComponent> ent)
- {
- if (TryComp(ent, out AppearanceComponent? appearance))
- {
- _appearance.SetData(ent, ToggleVisuals.Toggled, ent.Comp.Activated, appearance);
- }
- }
- /// <summary>
- /// Used for items that require to be wielded in both hands to activate. For instance the dual energy sword will turn off if not wielded.
- /// </summary>
- private void TurnOffOnUnwielded(Entity<ItemToggleComponent> ent, ref ItemUnwieldedEvent args)
- {
- TryDeactivate((ent, ent.Comp), args.User);
- }
- /// <summary>
- /// Wieldable items will automatically turn on when wielded.
- /// </summary>
- private void TurnOnOnWielded(Entity<ItemToggleComponent> ent, ref ItemWieldedEvent args)
- {
- // FIXME: for some reason both client and server play sound
- TryActivate((ent, ent.Comp));
- }
- public bool IsActivated(Entity<ItemToggleComponent?> ent)
- {
- if (!_query.Resolve(ent, ref ent.Comp, false))
- return true; // assume always activated if no component
- return ent.Comp.Activated;
- }
- /// <summary>
- /// Used to make the item hot when activated.
- /// </summary>
- private void OnIsHotEvent(Entity<ItemToggleHotComponent> ent, ref IsHotEvent args)
- {
- args.IsHot |= IsActivated(ent.Owner);
- }
- /// <summary>
- /// Used to update the looping active sound linked to the entity.
- /// </summary>
- private void UpdateActiveSound(Entity<ItemToggleActiveSoundComponent> ent, ref ItemToggledEvent args)
- {
- var (uid, comp) = ent;
- if (!args.Activated)
- {
- comp.PlayingStream = _audio.Stop(comp.PlayingStream);
- return;
- }
- if (comp.ActiveSound != null && comp.PlayingStream == null)
- {
- var loop = comp.ActiveSound.Params.WithLoop(true);
- var stream = args.Predicted
- ? _audio.PlayPredicted(comp.ActiveSound, uid, args.User, loop)
- : _audio.PlayPvs(comp.ActiveSound, uid, loop);
- if (stream?.Entity is {} entity)
- comp.PlayingStream = entity;
- }
- }
- }
|