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.Wieldable; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Network; namespace Content.Shared.Item.ItemToggle; /// /// Handles generic item toggles, like a welder turning on and off, or an e-sword. /// /// /// If you need extended functionality (e.g. requiring power) then add a new component and use events. /// public abstract class SharedItemToggleSystem : EntitySystem { [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedPointLightSystem _light = default!; [Dependency] private readonly INetManager _netManager = default!; [Dependency] private readonly SharedPopupSystem _popup = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnStartup); SubscribeLocalEvent(TurnOffonUnwielded); SubscribeLocalEvent(TurnOnonWielded); SubscribeLocalEvent(OnUseInHand); SubscribeLocalEvent(OnIsHotEvent); SubscribeLocalEvent(UpdateActiveSound); } private void OnStartup(Entity ent, ref ComponentStartup args) { UpdateVisuals(ent); } private void OnUseInHand(EntityUid uid, ItemToggleComponent itemToggle, UseInHandEvent args) { if (args.Handled) return; args.Handled = true; Toggle(uid, args.User, predicted: itemToggle.Predictable, itemToggle: itemToggle); } /// /// Used when an item is attempted to be toggled. /// public void Toggle(EntityUid uid, EntityUid? user = null, bool predicted = true, ItemToggleComponent? itemToggle = null) { if (!Resolve(uid, ref itemToggle)) return; if (itemToggle.Activated) { TryDeactivate(uid, user, itemToggle: itemToggle, predicted: predicted); } else { TryActivate(uid, user, itemToggle: itemToggle, predicted: predicted); } } /// /// Used when an item is attempting to be activated. It returns false if the attempt fails any reason, interrupting the activation. /// public bool TryActivate(EntityUid uid, EntityUid? user = null, bool predicted = true, ItemToggleComponent? itemToggle = null) { if (!Resolve(uid, ref itemToggle)) return false; if (itemToggle.Activated) return true; if (!itemToggle.Predictable && _netManager.IsClient) return true; var attempt = new ItemToggleActivateAttemptEvent(user); RaiseLocalEvent(uid, ref attempt); if (attempt.Cancelled) { if (predicted) _audio.PlayPredicted(itemToggle.SoundFailToActivate, uid, user); else _audio.PlayPvs(itemToggle.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, itemToggle, predicted, user); return true; } /// /// Used when an item is attempting to be deactivated. It returns false if the attempt fails any reason, interrupting the deactivation. /// public bool TryDeactivate(EntityUid uid, EntityUid? user = null, bool predicted = true, ItemToggleComponent? itemToggle = null) { if (!Resolve(uid, ref itemToggle)) return false; if (!itemToggle.Predictable && _netManager.IsClient) return true; if (!itemToggle.Activated) return true; var attempt = new ItemToggleDeactivateAttemptEvent(user); RaiseLocalEvent(uid, ref attempt); if (attempt.Cancelled) { return false; } Deactivate(uid, itemToggle, predicted, user); return true; } private void Activate(EntityUid uid, ItemToggleComponent itemToggle, bool predicted, EntityUid? user = null) { // TODO: Fix this hardcoding TryComp(uid, out AppearanceComponent? appearance); _appearance.SetData(uid, ToggleableLightVisuals.Enabled, true, appearance); _appearance.SetData(uid, ToggleVisuals.Toggled, true, appearance); if (_light.TryGetLight(uid, out var light)) { _light.SetEnabled(uid, true, light); } var soundToPlay = itemToggle.SoundActivate; if (predicted) _audio.PlayPredicted(soundToPlay, uid, user); else _audio.PlayPvs(soundToPlay, uid); // END FIX HARDCODING var toggleUsed = new ItemToggledEvent(predicted, Activated: true, user); RaiseLocalEvent(uid, ref toggleUsed); itemToggle.Activated = true; UpdateVisuals((uid, itemToggle)); Dirty(uid, itemToggle); } /// /// Used to make the actual changes to the item's components on deactivation. /// private void Deactivate(EntityUid uid, ItemToggleComponent itemToggle, bool predicted, EntityUid? user = null) { var soundToPlay = itemToggle.SoundDeactivate; if (predicted) _audio.PlayPredicted(soundToPlay, uid, user); else _audio.PlayPvs(soundToPlay, uid); // END FIX HARDCODING var toggleUsed = new ItemToggledEvent(predicted, Activated: false, user); RaiseLocalEvent(uid, ref toggleUsed); itemToggle.Activated = false; UpdateVisuals((uid, itemToggle)); Dirty(uid, itemToggle); } private void UpdateVisuals(Entity ent) { if (TryComp(ent, out AppearanceComponent? appearance)) { _appearance.SetData(ent, ToggleVisuals.Toggled, ent.Comp.Activated, appearance); if (ent.Comp.ToggleLight) _appearance.SetData(ent, ToggleableLightVisuals.Enabled, ent.Comp.Activated, appearance); } if (!ent.Comp.ToggleLight) return; if (_light.TryGetLight(ent, out var light)) _light.SetEnabled(ent, ent.Comp.Activated, light); } /// /// 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. /// private void TurnOffonUnwielded(EntityUid uid, ItemToggleComponent itemToggle, ItemUnwieldedEvent args) { if (itemToggle.Activated) TryDeactivate(uid, args.User, itemToggle: itemToggle); } /// /// Wieldable items will automatically turn on when wielded. /// private void TurnOnonWielded(EntityUid uid, ItemToggleComponent itemToggle, ref ItemWieldedEvent args) { if (!itemToggle.Activated) TryActivate(uid, itemToggle: itemToggle); } public bool IsActivated(EntityUid uid, ItemToggleComponent? comp = null) { if (!Resolve(uid, ref comp, false)) return true; // assume always activated if no component return comp.Activated; } /// /// Used to make the item hot when activated. /// private void OnIsHotEvent(EntityUid uid, ItemToggleHotComponent itemToggleHot, IsHotEvent args) { args.IsHot |= IsActivated(uid); } /// /// Used to update the looping active sound linked to the entity. /// private void UpdateActiveSound(EntityUid uid, ItemToggleActiveSoundComponent activeSound, ref ItemToggledEvent args) { if (args.Activated) { if (activeSound.ActiveSound != null && activeSound.PlayingStream == null) { if (args.Predicted) activeSound.PlayingStream = _audio.PlayPredicted(activeSound.ActiveSound, uid, args.User, AudioParams.Default.WithLoop(true)).Value.Entity; else activeSound.PlayingStream = _audio.PlayPvs(activeSound.ActiveSound, uid, AudioParams.Default.WithLoop(true)).Value.Entity; } } else { activeSound.PlayingStream = _audio.Stop(activeSound.PlayingStream); } } }