+++ /dev/null
-using Content.Shared.Explosion.EntitySystems;
-
-namespace Content.Client.Explosion;
-
-public sealed class SmokeOnTriggerSystem : SharedSmokeOnTriggerSystem
-{
-}
\ No newline at end of file
+++ /dev/null
-using Content.Shared.Explosion;
-using Content.Shared.Explosion.Components;
-
-namespace Content.Client.Explosion;
-
-[RegisterComponent, Access(typeof(TriggerSystem))]
-public sealed partial class TriggerOnProximityComponent : SharedTriggerOnProximityComponent {}
+++ /dev/null
-namespace Content.Client.Explosion;
-
-public sealed partial class TriggerSystem : EntitySystem
-{
- public override void Initialize()
- {
- base.Initialize();
- InitializeProximity();
- }
-}
using Content.Shared.HotPotato;
using Robust.Shared.Random;
+using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Content.Client.HotPotato;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
+ private readonly EntProtoId _hotPotatoEffectId = "HotPotatoEffect";
+
+ // TODO: particle system
public override void Update(float frameTime)
{
base.Update(frameTime);
if (_timing.CurTime < comp.TargetTime)
continue;
comp.TargetTime = _timing.CurTime + TimeSpan.FromSeconds(comp.EffectCooldown);
- Spawn("HotPotatoEffect", _transform.GetMapCoordinates(uid).Offset(_random.NextVector2(0.25f)));
+ Spawn(_hotPotatoEffectId, _transform.GetMapCoordinates(uid).Offset(_random.NextVector2(0.25f)));
}
}
}
+using Content.Client.Trigger.Systems;
using Robust.Client.Animations;
using Robust.Shared.Audio;
-namespace Content.Client.Trigger;
+namespace Content.Client.Trigger.Components;
[RegisterComponent]
[Access(typeof(TimerTriggerVisualizerSystem))]
/// <summary>
/// The RSI state used while the device has not been primed.
/// </summary>
- [DataField("unprimedSprite")]
- [ViewVariables(VVAccess.ReadWrite)]
+ [DataField]
public string UnprimedSprite = "icon";
/// <summary>
/// The RSI state used when the device is primed.
/// Not VVWrite-able because it's only used at component init to construct the priming animation.
/// </summary>
- [DataField("primingSprite")]
+ [DataField]
public string PrimingSprite = "primed";
/// <summary>
/// The sound played when the device is primed.
/// Not VVWrite-able because it's only used at component init to construct the priming animation.
/// </summary>
- [DataField("primingSound")]
+ [DataField, ViewVariables]
public SoundSpecifier? PrimingSound;
/// <summary>
/// The actual priming animation.
/// Constructed at component init from the sprite and sound.
/// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
+ [ViewVariables]
public Animation PrimingAnimation = default!;
}
using Content.Shared.Trigger;
+using Content.Shared.Trigger.Components.Triggers;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
using Robust.Shared.Animations;
-namespace Content.Client.Explosion;
+namespace Content.Client.Trigger.Systems;
-public sealed partial class TriggerSystem
+public sealed class ProximityTriggerAnimationSystem : EntitySystem
{
[Dependency] private readonly AnimationPlayerSystem _player = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
private const string AnimKey = "proximity";
- private static readonly Animation _flasherAnimation = new Animation
+ private static readonly Animation FlasherAnimation = new Animation
{
Length = TimeSpan.FromSeconds(0.6f),
AnimationTracks = {
}
};
- private void InitializeProximity()
+ public override void Initialize()
{
+ base.Initialize();
+
SubscribeLocalEvent<TriggerOnProximityComponent, ComponentInit>(OnProximityInit);
SubscribeLocalEvent<TriggerOnProximityComponent, AppearanceChangeEvent>(OnProxAppChange);
SubscribeLocalEvent<TriggerOnProximityComponent, AnimationCompletedEvent>(OnProxAnimation);
break;
case ProximityTriggerVisuals.Active:
if (_player.HasRunningAnimation(uid, player, AnimKey)) return;
- _player.Play((uid, player), _flasherAnimation, AnimKey);
+ _player.Play((uid, player), FlasherAnimation, AnimKey);
break;
case ProximityTriggerVisuals.Off:
default:
--- /dev/null
+using Content.Shared.Trigger.Systems;
+
+namespace Content.Client.Trigger.Systems;
+
+public sealed class ReleaseGasOnTriggerSystem : SharedReleaseGasOnTriggerSystem;
+using Content.Client.Trigger.Components;
using Content.Shared.Trigger;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
-using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
-using Robust.Shared.GameObjects;
-namespace Content.Client.Trigger;
+namespace Content.Client.Trigger.Systems;
public sealed class TimerTriggerVisualizerSystem : VisualizerSystem<TimerTriggerVisualsComponent>
{
SubscribeLocalEvent<TimerTriggerVisualsComponent, ComponentInit>(OnComponentInit);
}
- private void OnComponentInit(EntityUid uid, TimerTriggerVisualsComponent comp, ComponentInit args)
+ private void OnComponentInit(Entity<TimerTriggerVisualsComponent> ent, ref ComponentInit args)
{
- comp.PrimingAnimation = new Animation
+ ent.Comp.PrimingAnimation = new Animation
{
Length = TimeSpan.MaxValue,
AnimationTracks = {
- new AnimationTrackSpriteFlick() {
+ new AnimationTrackSpriteFlick()
+ {
LayerKey = TriggerVisualLayers.Base,
- KeyFrames = { new AnimationTrackSpriteFlick.KeyFrame(comp.PrimingSprite, 0f) }
+ KeyFrames = { new AnimationTrackSpriteFlick.KeyFrame(ent.Comp.PrimingSprite, 0f) }
}
},
};
- if (comp.PrimingSound != null)
+ if (ent.Comp.PrimingSound != null)
{
- comp.PrimingAnimation.AnimationTracks.Add(
+ ent.Comp.PrimingAnimation.AnimationTracks.Add(
new AnimationTrackPlaySound()
{
- KeyFrames = { new AnimationTrackPlaySound.KeyFrame(_audioSystem.ResolveSound(comp.PrimingSound), 0) }
+ KeyFrames = { new AnimationTrackPlaySound.KeyFrame(_audioSystem.ResolveSound(ent.Comp.PrimingSound), 0) }
}
);
}
using Content.IntegrationTests.Tests.Interaction;
-using Content.Server.Explosion.Components;
-using Content.Shared.Explosion.Components;
+using Content.Shared.Trigger.Components;
+using Content.Shared.Trigger.Systems;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
await InteractUsing(Cable);
// Insert & remove trigger
- AssertComp<OnUseTimerTriggerComponent>(false);
+ AssertComp<TimerTriggerComponent>(false);
await InteractUsing(Trigger);
- AssertComp<OnUseTimerTriggerComponent>();
+ AssertComp<TimerTriggerComponent>();
await FindEntity(Trigger, LookupFlags.Uncontained, shouldSucceed: false);
await InteractUsing(Pry);
- AssertComp<OnUseTimerTriggerComponent>(false);
+ AssertComp<TimerTriggerComponent>(false);
// Trigger was dropped to floor, not deleted.
await FindEntity(Trigger, LookupFlags.Uncontained);
// Re-insert
await InteractUsing(Trigger);
- AssertComp<OnUseTimerTriggerComponent>();
+ AssertComp<TimerTriggerComponent>();
// Insert & remove payload.
await InteractUsing(Payload);
await Pickup();
AssertComp<ActiveTimerTriggerComponent>(false);
await UseInHand();
+ AssertComp<ActiveTimerTriggerComponent>(true);
// So uhhh grenades in hands don't destroy themselves when exploding. Maybe that will be fixed eventually.
await Drop();
// Wait until grenade explodes
- var timer = Comp<ActiveTimerTriggerComponent>();
- while (timer.TimeRemaining >= 0)
+ var triggerSys = SEntMan.System<TriggerSystem>();
+ while (Target != null && triggerSys.GetRemainingTime(SEntMan.GetEntity(Target.Value))?.TotalSeconds >= 0.0)
{
await RunTicks(10);
}
+++ /dev/null
-using Content.Server.AlertLevel.Systems;
-
-namespace Content.Server.AlertLevel;
-/// <summary>
-/// This component is for changing the alert level of the station when triggered.
-/// </summary>
-[RegisterComponent, Access(typeof(AlertLevelChangeOnTriggerSystem))]
-public sealed partial class AlertLevelChangeOnTriggerComponent : Component
-{
- ///<summary>
- ///The alert level to change to when triggered.
- ///</summary>
- [DataField]
- public string Level = "blue";
-
- /// <summary>
- ///Whether to play the sound when the alert level changes.
- /// </summary>
- [DataField]
- public bool PlaySound = true;
-
- /// <summary>
- ///Whether to say the announcement when the alert level changes.
- /// </summary>
- [DataField]
- public bool Announce = true;
-
- /// <summary>
- ///Force the alert change. This applies if the alert level is not selectable or not.
- /// </summary>
- [DataField]
- public bool Force = false;
-}
using Content.Server.Mind;
using Content.Server.Popups;
using Content.Server.Radio;
-using Content.Server.Speech;
-using Content.Server.Speech.Components;
using Content.Server.Vocalization.Systems;
using Content.Shared.Animals.Components;
using Content.Shared.Animals.Systems;
using Content.Shared.Database;
using Content.Shared.Mobs.Systems;
+using Content.Shared.Speech;
+using Content.Shared.Speech.Components;
using Content.Shared.Whitelist;
using Robust.Shared.Network;
using Robust.Shared.Random;
+++ /dev/null
-using Content.Shared.Dataset;
-using Robust.Shared.Prototypes;
-
-namespace Content.Server.Chat;
-
-/// <summary>
-/// Makes the entity speak when triggered. If the item has UseDelay component, the system will respect that cooldown.
-/// </summary>
-[RegisterComponent]
-public sealed partial class SpeakOnTriggerComponent : Component
-{
- /// <summary>
- /// The identifier for the dataset prototype containing messages to be spoken by this entity.
- /// </summary>
- [DataField(required: true)]
- public ProtoId<LocalizedDatasetPrototype> Pack = string.Empty;
-}
if (!suicideGhostEvent.Handled || _tagSystem.HasTag(victim, CannotSuicideTag))
return false;
+ // TODO: fix this
+ // This is a handled event, but the result is never used
+ // It looks like TriggerOnMobstateChange is supposed to prevent you from suiciding
var suicideEvent = new SuicideEvent(victim);
RaiseLocalEvent(victim, suicideEvent);
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
[Dependency] private readonly ExamineSystemShared _examineSystem = default!;
- public const int VoiceRange = 10; // how far voice goes in world units
- public const int WhisperClearRange = 2; // how far whisper goes while still being understandable, in world units
- public const int WhisperMuffledRange = 5; // how far whisper goes at all, in world units
- public const string DefaultAnnouncementSound = "/Audio/Announcements/announce.ogg";
-
private bool _loocEnabled = true;
private bool _deadLoocEnabled;
private bool _critLoocEnabled;
+++ /dev/null
-using Content.Server.Explosion.EntitySystems;
-using Content.Shared.Damage;
-using Content.Shared.Damage.Components;
-
-namespace Content.Server.Damage.Systems;
-
-// System for damage that occurs on specific trigger, towards the user..
-public sealed class DamageUserOnTriggerSystem : EntitySystem
-{
- [Dependency] private readonly DamageableSystem _damageableSystem = default!;
-
- public override void Initialize()
- {
- SubscribeLocalEvent<DamageUserOnTriggerComponent, TriggerEvent>(OnTrigger);
- }
-
- private void OnTrigger(EntityUid uid, DamageUserOnTriggerComponent component, TriggerEvent args)
- {
- if (args.User is null)
- return;
-
- args.Handled |= OnDamageTrigger(uid, args.User.Value, component);
- }
-
- private bool OnDamageTrigger(EntityUid source, EntityUid target, DamageUserOnTriggerComponent? component = null)
- {
- if (!Resolve(source, ref component))
- {
- return false;
- }
-
- var damage = new DamageSpecifier(component.Damage);
- var ev = new BeforeDamageUserOnTriggerEvent(damage, target);
- RaiseLocalEvent(source, ev);
-
- return _damageableSystem.TryChangeDamage(target, ev.Damage, component.IgnoreResistances, origin: source) is not null;
- }
-}
-
-public sealed class BeforeDamageUserOnTriggerEvent : EntityEventArgs
-{
- public DamageSpecifier Damage { get; set; }
- public EntityUid Tripper { get; }
-
- public BeforeDamageUserOnTriggerEvent(DamageSpecifier damage, EntityUid target)
- {
- Damage = damage;
- Tripper = target;
- }
-}
using Content.Server.Defusable.Components;
-using Content.Server.Explosion.Components;
using Content.Server.Explosion.EntitySystems;
using Content.Server.Popups;
using Content.Server.Wires;
using Content.Shared.Database;
using Content.Shared.Defusable;
using Content.Shared.Examine;
-using Content.Shared.Explosion.Components;
-using Content.Shared.Explosion.Components.OnTrigger;
using Content.Shared.Popups;
+using Content.Shared.Trigger.Components;
+using Content.Shared.Trigger.Components.Effects;
+using Content.Shared.Trigger.Systems;
using Content.Shared.Verbs;
using Content.Shared.Wires;
using Robust.Server.GameObjects;
-using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
namespace Content.Server.Defusable.Systems;
{
args.PushMarkup(Loc.GetString("defusable-examine-defused", ("name", uid)));
}
- else if (comp.Activated && TryComp<ActiveTimerTriggerComponent>(uid, out var activeComp))
+ else if (comp.Activated)
{
- if (comp.DisplayTime)
+ var remaining = _trigger.GetRemainingTime(uid);
+ if (comp.DisplayTime && remaining != null)
{
args.PushMarkup(Loc.GetString("defusable-examine-live", ("name", uid),
- ("time", MathF.Floor(activeComp.TimeRemaining))));
+ ("time", Math.Floor(remaining.Value.TotalSeconds))));
}
else
{
SetActivated(comp, true);
_popup.PopupEntity(Loc.GetString("defusable-popup-begun", ("name", uid)), uid);
- if (TryComp<OnUseTimerTriggerComponent>(uid, out var timerTrigger))
+ if (TryComp<TimerTriggerComponent>(uid, out var timerTrigger))
{
- _trigger.HandleTimerTrigger(
- uid,
- user,
- timerTrigger.Delay,
- timerTrigger.BeepInterval,
- timerTrigger.InitialBeepDelay,
- timerTrigger.BeepSound
- );
+ _trigger.ActivateTimerTrigger((uid, timerTrigger));
}
RaiseLocalEvent(uid, new BombArmedEvent(uid));
RaiseLocalEvent(uid, new BombDetonatedEvent(uid));
- _explosion.TriggerExplosive(uid, user:detonator);
+ _explosion.TriggerExplosive(uid, user: detonator);
QueueDel(uid);
_appearance.SetData(uid, DefusableVisuals.Active, comp.Activated);
{
SetUsable(comp, false);
RemComp<ExplodeOnTriggerComponent>(uid);
- RemComp<OnUseTimerTriggerComponent>(uid);
+ RemComp<TimerTriggerComponent>(uid);
}
RemComp<ActiveTimerTriggerComponent>(uid);
if (comp is not { Activated: true, DelayWireUsed: false })
return;
- _trigger.TryDelay(wire.Owner, 30f);
+ _trigger.TryDelay(wire.Owner, TimeSpan.FromSeconds(30));
_popup.PopupEntity(Loc.GetString("defusable-popup-wire-chirp", ("name", wire.Owner)), wire.Owner);
comp.DelayWireUsed = true;
}
if (comp is { Activated: true, ProceedWireUsed: false })
{
comp.ProceedWireUsed = true;
- _trigger.TryDelay(wire.Owner, -15f);
+ _trigger.TryDelay(wire.Owner, TimeSpan.FromSeconds(-15));
}
_popup.PopupEntity(Loc.GetString("defusable-popup-wire-proceed-pulse", ("name", wire.Owner)), wire.Owner);
{
if (!comp.ActivatedWireUsed)
{
- _trigger.TryDelay(wire.Owner, 30f);
+ _trigger.TryDelay(wire.Owner, TimeSpan.FromSeconds(30));
_popup.PopupEntity(Loc.GetString("defusable-popup-wire-chirp", ("name", wire.Owner)), wire.Owner);
comp.ActivatedWireUsed = true;
}
using System.Diagnostics.CodeAnalysis;
+using System.Linq;
using Content.Server.Administration.Logs;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Body.Systems;
using Content.Shared.Database;
using Content.Shared.Destructible;
using Content.Shared.FixedPoint;
+using Content.Shared.Humanoid;
+using Content.Shared.Trigger.Systems;
using JetBrains.Annotations;
using Robust.Server.Audio;
-using Robust.Server.GameObjects;
using Robust.Shared.Containers;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
-using System.Linq;
-using Content.Shared.Humanoid;
-using Robust.Shared.Player;
namespace Content.Server.Destructible
{
{
public void Execute(EntityUid owner, DestructibleSystem system, EntityUid? cause = null)
{
- system.TriggerSystem.StartTimer(owner, cause);
+ system.TriggerSystem.ActivateTimerTrigger(owner, cause);
}
}
-namespace Content.Server.Destructible.Thresholds.Behaviors;
+using Content.Shared.Trigger.Systems;
+
+namespace Content.Server.Destructible.Thresholds.Behaviors;
[DataDefinition]
public sealed partial class TriggerBehavior : IThresholdBehavior
{
+ /// <summary>
+ /// The trigger key to use when triggering.
+ /// </summary>
+ [DataField]
+ public string? KeyOut { get; set; } = TriggerSystem.DefaultTriggerKey;
+
public void Execute(EntityUid owner, DestructibleSystem system, EntityUid? cause = null)
{
- system.TriggerSystem.Trigger(owner, cause);
+ system.TriggerSystem.Trigger(owner, cause, KeyOut);
}
}
using Content.Server.DeviceLinking.Components;
+using Content.Server.DeviceNetwork;
+using Content.Server.DeviceNetwork.Components;
using Content.Server.DeviceNetwork.Systems;
using Content.Shared.DeviceLinking;
using Content.Shared.DeviceLinking.Events;
using Content.Server.DeviceLinking.Components;
-using Content.Server.DeviceNetwork;
using Content.Shared.DeviceLinking;
using Content.Shared.DeviceLinking.Events;
using Content.Shared.DeviceNetwork;
using Content.Server.Administration.Logs;
using Content.Server.DeviceLinking.Components;
-using Content.Server.Explosion.EntitySystems;
using Content.Shared.Database;
using Content.Shared.Interaction.Events;
using Content.Shared.Timing;
public sealed class SignallerSystem : EntitySystem
{
[Dependency] private readonly DeviceLinkSystem _link = default!;
- [Dependency] private readonly UseDelaySystem _useDelay = default!;
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
public override void Initialize()
SubscribeLocalEvent<SignallerComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<SignallerComponent, UseInHandEvent>(OnUseInHand);
- SubscribeLocalEvent<SignallerComponent, TriggerEvent>(OnTrigger);
}
private void OnInit(EntityUid uid, SignallerComponent component, ComponentInit args)
_link.InvokePort(uid, component.Port);
args.Handled = true;
}
-
- private void OnTrigger(EntityUid uid, SignallerComponent component, TriggerEvent args)
- {
- if (!TryComp(uid, out UseDelayComponent? useDelay)
- // if on cooldown, do nothing
- // and set cooldown to prevent clocks
- || !_useDelay.TryResetDelay((uid, useDelay), true))
- return;
-
- _link.InvokePort(uid, component.Port);
- args.Handled = true;
- }
}
[Dependency] private readonly MetaDataSystem _metaData = default!;
[Dependency] private readonly TurfSystem _turf = default!;
- private static readonly ProtoId<StatusEffectPrototype> StatusEffectKey = "Electrocution";
+ private static readonly ProtoId<StatusEffectPrototype> StatusKeyIn = "Electrocution";
private static readonly ProtoId<DamageTypePrototype> DamageType = "Shock";
private static readonly ProtoId<TagPrototype> WindowTag = "Window";
}
if (!Resolve(uid, ref statusEffects, false) ||
- !_statusEffects.CanApplyEffect(uid, StatusEffectKey, statusEffects))
+ !_statusEffects.CanApplyEffect(uid, StatusKeyIn, statusEffects))
{
return false;
}
- if (!_statusEffects.TryAddStatusEffect<ElectrocutedComponent>(uid, StatusEffectKey, time, refresh, statusEffects))
+ if (!_statusEffects.TryAddStatusEffect<ElectrocutedComponent>(uid, StatusKeyIn, time, refresh, statusEffects))
return false;
var shouldStun = siemensCoefficient > 0.5f;
+++ /dev/null
-namespace Content.Server.Emp;
-
-/// <summary>
-/// Upon being triggered will EMP area around it.
-/// </summary>
-[RegisterComponent]
-[Access(typeof(EmpSystem))]
-public sealed partial class EmpOnTriggerComponent : Component
-{
- [DataField("range"), ViewVariables(VVAccess.ReadWrite)]
- public float Range = 1.0f;
-
- /// <summary>
- /// How much energy will be consumed per battery in range
- /// </summary>
- [DataField("energyConsumption"), ViewVariables(VVAccess.ReadWrite)]
- public float EnergyConsumption;
-
- /// <summary>
- /// How long it disables targets in seconds
- /// </summary>
- [DataField("disableDuration"), ViewVariables(VVAccess.ReadWrite)]
- public float DisableDuration = 60f;
-}
-using Content.Server.Explosion.EntitySystems;
using Content.Server.Power.EntitySystems;
using Content.Server.Radio;
using Content.Server.SurveillanceCamera;
{
base.Initialize();
SubscribeLocalEvent<EmpDisabledComponent, ExaminedEvent>(OnExamine);
- SubscribeLocalEvent<EmpOnTriggerComponent, TriggerEvent>(HandleEmpTrigger);
SubscribeLocalEvent<EmpDisabledComponent, RadioSendAttemptEvent>(OnRadioSendAttempt);
SubscribeLocalEvent<EmpDisabledComponent, RadioReceiveAttemptEvent>(OnRadioReceiveAttempt);
SubscribeLocalEvent<EmpDisabledComponent, SurveillanceCameraSetActiveAttemptEvent>(OnCameraSetActive);
}
- /// <summary>
- /// Triggers an EMP pulse at the given location, by first raising an <see cref="EmpAttemptEvent"/>, then a raising <see cref="EmpPulseEvent"/> on all entities in range.
- /// </summary>
- /// <param name="coordinates">The location to trigger the EMP pulse at.</param>
- /// <param name="range">The range of the EMP pulse.</param>
- /// <param name="energyConsumption">The amount of energy consumed by the EMP pulse.</param>
- /// <param name="duration">The duration of the EMP effects.</param>
- public void EmpPulse(MapCoordinates coordinates, float range, float energyConsumption, float duration)
+ public override void EmpPulse(MapCoordinates coordinates, float range, float energyConsumption, float duration)
{
foreach (var uid in _lookup.GetEntitiesInRange(coordinates, range))
{
args.PushMarkup(Loc.GetString("emp-disabled-comp-on-examine"));
}
- private void HandleEmpTrigger(EntityUid uid, EmpOnTriggerComponent comp, TriggerEvent args)
- {
- EmpPulse(_transform.GetMapCoordinates(uid), comp.Range, comp.EnergyConsumption, comp.DisableDuration);
- args.Handled = true;
- }
-
private void OnRadioSendAttempt(EntityUid uid, EmpDisabledComponent component, ref RadioSendAttemptEvent args)
{
args.Cancelled = true;
+++ /dev/null
-namespace Content.Server.Explosion.Components;
-
-[RegisterComponent]
-public sealed partial class ActiveTriggerOnTimedCollideComponent : Component { }
+++ /dev/null
-namespace Content.Server.Explosion.Components;
-
-/// <summary>
-/// Disallows starting the timer by hand, must be stuck or triggered by a system using <c>StartTimer</c>.
-/// </summary>
-[RegisterComponent]
-public sealed partial class AutomatedTimerComponent : Component
-{
-}
+++ /dev/null
-using Content.Server.Explosion.EntitySystems;
-
-namespace Content.Server.Explosion.Components;
-
-/// <summary>
-/// Will anchor the attached entity upon a <see cref="TriggerEvent"/>.
-/// </summary>
-[RegisterComponent]
-public sealed partial class AnchorOnTriggerComponent : Component
-{
- [DataField("removeOnTrigger")]
- public bool RemoveOnTrigger = true;
-}
+++ /dev/null
-using Content.Server.Explosion.EntitySystems;
-
-namespace Content.Server.Explosion.Components;
-
-/// <summary>
-/// Will delete the attached entity upon a <see cref="TriggerEvent"/>.
-/// </summary>
-[RegisterComponent]
-public sealed partial class DeleteOnTriggerComponent : Component
-{
-}
+++ /dev/null
-namespace Content.Server.Explosion.Components;
-
-/// <summary>
-/// Gibs on trigger, self explanatory.
-/// Also in case of an implant using this, gibs the implant user instead.
-/// </summary>
-[RegisterComponent]
-public sealed partial class GibOnTriggerComponent : Component
-{
- /// <summary>
- /// Should gibbing also delete the owners items?
- /// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("deleteItems")]
- public bool DeleteItems = false;
-}
+++ /dev/null
-using Content.Server.Explosion.EntitySystems;
-using Robust.Shared.Audio;
-
-namespace Content.Server.Explosion.Components;
-
-/// <summary>
-/// Will play sound from the attached entity upon a <see cref="TriggerEvent"/>.
-/// </summary>
-[RegisterComponent]
-public sealed partial class SoundOnTriggerComponent : Component
-{
- [DataField("removeOnTrigger")]
- public bool RemoveOnTrigger = true;
-
- [DataField("sound")]
- public SoundSpecifier? Sound = new SoundPathSpecifier("/Audio/Effects/Grenades/supermatter_start.ogg");
-}
+++ /dev/null
-using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
-
-namespace Content.Server.Explosion.Components.OnTrigger;
-
-/// <summary>
-/// After being triggered applies the specified components and runs triggers again.
-/// </summary>
-[RegisterComponent, AutoGenerateComponentPause]
-public sealed partial class TwoStageTriggerComponent : Component
-{
- /// <summary>
- /// How long it takes for the second stage to be triggered.
- /// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("triggerDelay")]
- public TimeSpan TriggerDelay = TimeSpan.FromSeconds(10);
-
- /// <summary>
- /// This list of components that will be added for the second trigger.
- /// </summary>
- [DataField("components", required: true)]
- public ComponentRegistry SecondStageComponents = new();
-
- [DataField("nextTriggerTime", customTypeSerializer: typeof(TimeOffsetSerializer))]
- [AutoPausedField]
- public TimeSpan? NextTriggerTime;
-
- [DataField("triggered")]
- public bool Triggered = false;
-
- [DataField("ComponentsIsLoaded")]
- public bool ComponentsIsLoaded = false;
-}
/// </summary>
[DataField]
public float MaxVelocity = 6f;
+
+ /// <summary>
+ /// The trigger key that will activate the grenade.
+ /// </summary>
+ [DataField]
+ public string TriggerKey = "timer";
}
+++ /dev/null
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
-using Content.Server.Explosion.EntitySystems;
-
-namespace Content.Server.Explosion.Components;
-
-/// <summary>
-/// A component that electrocutes an entity having this component when a trigger is triggered.
-/// </summary>
-[RegisterComponent, AutoGenerateComponentPause]
-[Access(typeof(TriggerSystem))]
-public sealed partial class ShockOnTriggerComponent : Component
-{
- /// <summary>
- /// The force of an electric shock when the trigger is triggered.
- /// </summary>
- [DataField]
- public int Damage = 5;
-
- /// <summary>
- /// Duration of electric shock when the trigger is triggered.
- /// </summary>
- [DataField]
- public TimeSpan Duration = TimeSpan.FromSeconds(2);
-
- /// <summary>
- /// The minimum delay between repeating triggers.
- /// </summary>
- [DataField]
- public TimeSpan Cooldown = TimeSpan.FromSeconds(4);
-
- /// <summary>
- /// When can the trigger run again?
- /// </summary>
- [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
- [AutoPausedField]
- public TimeSpan NextTrigger = TimeSpan.Zero;
-}
+++ /dev/null
-using Content.Server.Explosion.EntitySystems;
-using Robust.Shared.Prototypes;
-
-namespace Content.Server.Explosion.Components;
-
-/// <summary>
-/// Spawns a protoype when triggered.
-/// </summary>
-[RegisterComponent, Access(typeof(TriggerSystem))]
-public sealed partial class SpawnOnTriggerComponent : Component
-{
- /// <summary>
- /// The prototype to spawn.
- /// </summary>
- [DataField(required: true)]
- public EntProtoId Proto = string.Empty;
-
- /// <summary>
- /// Use MapCoordinates for spawning?
- /// Set to true if you don't want the new entity parented to the spawner.
- /// </summary>
- [DataField]
- public bool mapCoords;
-}
+++ /dev/null
-using Content.Shared.DeviceLinking;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-
-namespace Content.Server.Explosion.Components
-{
- /// <summary>
- /// Sends a trigger when signal is received.
- /// </summary>
- [RegisterComponent]
- public sealed partial class TimerStartOnSignalComponent : Component
- {
- [DataField("port", customTypeSerializer: typeof(PrototypeIdSerializer<SinkPortPrototype>))]
- public string Port = "Timer";
- }
-}
+++ /dev/null
-namespace Content.Server.Explosion.Components;
-
-/// <summary>
-/// Triggers on click.
-/// </summary>
-[RegisterComponent]
-public sealed partial class TriggerOnActivateComponent : Component { }
+++ /dev/null
-namespace Content.Server.Explosion.Components;
-
-/// <summary>
-/// Triggers when colliding with another entity.
-/// </summary>
-[RegisterComponent]
-public sealed partial class TriggerOnCollideComponent : Component
-{
- /// <summary>
- /// The fixture with which to collide.
- /// </summary>
- [DataField(required: true)]
- public string FixtureID = string.Empty;
-
- /// <summary>
- /// Doesn't trigger if the other colliding fixture is nonhard.
- /// </summary>
- [DataField]
- public bool IgnoreOtherNonHard = true;
-}
+++ /dev/null
-using Content.Shared.Mobs;
-
-namespace Content.Server.Explosion.Components;
-
-/// <summary>
-/// Use where you want something to trigger on mobstate change
-/// </summary>
-[RegisterComponent]
-public sealed partial class TriggerOnMobstateChangeComponent : Component
-{
- /// <summary>
- /// What state should trigger this?
- /// </summary>
- [ViewVariables]
- [DataField("mobState", required: true)]
- public List<MobState> MobState = new();
-
- /// <summary>
- /// If true, prevents suicide attempts for the trigger to prevent cheese.
- /// </summary>
- [ViewVariables]
- [DataField("preventSuicide")]
- public bool PreventSuicide = false;
-}
+++ /dev/null
-using Content.Server.Explosion.EntitySystems;
-using Content.Shared.Explosion;
-using Content.Shared.Explosion.Components;
-using Content.Shared.Physics;
-using Robust.Shared.Physics.Collision.Shapes;
-using Robust.Shared.Physics.Components;
-using Robust.Shared.Physics.Dynamics;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
-
-namespace Content.Server.Explosion.Components
-{
-
- /// <summary>
- /// Raises a <see cref="TriggerEvent"/> whenever an entity collides with a fixture attached to the owner of this component.
- /// </summary>
- [RegisterComponent, AutoGenerateComponentPause]
- public sealed partial class TriggerOnProximityComponent : SharedTriggerOnProximityComponent
- {
- public const string FixtureID = "trigger-on-proximity-fixture";
-
- [ViewVariables]
- public readonly Dictionary<EntityUid, PhysicsComponent> Colliding = new();
-
- /// <summary>
- /// What is the shape of the proximity fixture?
- /// </summary>
- [ViewVariables]
- [DataField("shape")]
- public IPhysShape Shape = new PhysShapeCircle(2f);
-
- /// <summary>
- /// How long the the proximity trigger animation plays for.
- /// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("animationDuration")]
- public TimeSpan AnimationDuration = TimeSpan.FromSeconds(0.6f);
-
- /// <summary>
- /// Whether the entity needs to be anchored for the proximity to work.
- /// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("requiresAnchored")]
- public bool RequiresAnchored = true;
-
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("enabled")]
- public bool Enabled = true;
-
- /// <summary>
- /// The minimum delay between repeating triggers.
- /// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("cooldown")]
- public TimeSpan Cooldown = TimeSpan.FromSeconds(5);
-
- /// <summary>
- /// When can the trigger run again?
- /// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("nextTrigger", customTypeSerializer: typeof(TimeOffsetSerializer))]
- [AutoPausedField]
- public TimeSpan NextTrigger = TimeSpan.Zero;
-
- /// <summary>
- /// When will the visual state be updated again after activation?
- /// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("nextVisualUpdate", customTypeSerializer: typeof(TimeOffsetSerializer))]
- [AutoPausedField]
- public TimeSpan NextVisualUpdate = TimeSpan.Zero;
-
- /// <summary>
- /// What speed should the other object be moving at to trigger the proximity fixture?
- /// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("triggerSpeed")]
- public float TriggerSpeed = 3.5f;
-
- /// <summary>
- /// If this proximity is triggered should we continually repeat it?
- /// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("repeating")]
- public bool Repeating = true;
-
- /// <summary>
- /// What layer is the trigger fixture on?
- /// </summary>
- [ViewVariables]
- [DataField("layer", customTypeSerializer: typeof(FlagSerializer<CollisionLayer>))]
- public int Layer = (int) (CollisionGroup.MidImpassable | CollisionGroup.LowImpassable | CollisionGroup.HighImpassable);
- }
-}
+++ /dev/null
-using Content.Shared.DeviceLinking;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-
-namespace Content.Server.Explosion.Components
-{
- /// <summary>
- /// Sends a trigger when signal is received.
- /// </summary>
- [RegisterComponent]
- public sealed partial class TriggerOnSignalComponent : Component
- {
- [DataField("port", customTypeSerializer: typeof(PrototypeIdSerializer<SinkPortPrototype>))]
- public string Port = "Trigger";
- }
-}
+++ /dev/null
-namespace Content.Server.Explosion.Components;
-
-[RegisterComponent]
-public sealed partial class TriggerOnSlipComponent : Component
-{
-}
+++ /dev/null
-namespace Content.Server.Explosion.Components;
-
-/// <summary>
-/// calls the trigger when the object is initialized
-/// </summary>
-[RegisterComponent]
-public sealed partial class TriggerOnSpawnComponent : Component
-{
-}
+++ /dev/null
-namespace Content.Server.Explosion.Components;
-
-/// <summary>
-/// This is used for entities that want the more generic 'trigger' behavior after a step trigger occurs.
-/// Not done by default, since it's not useful for everything and might cause weird behavior. But it is useful for a lot of stuff like mousetraps.
-/// </summary>
-[RegisterComponent]
-public sealed partial class TriggerOnStepTriggerComponent : Component
-{
-}
+++ /dev/null
-namespace Content.Server.Explosion.Components;
-
-/// <summary>
-/// Triggers when the entity is overlapped for the specified duration.
-/// </summary>
-[RegisterComponent]
-public sealed partial class TriggerOnTimedCollideComponent : Component
-{
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("threshold")]
- public float Threshold;
-
- /// <summary>
- /// A collection of entities that are colliding with this, and their own unique accumulator.
- /// </summary>
- [ViewVariables]
- public readonly Dictionary<EntityUid, float> Colliding = new();
-}
+++ /dev/null
-namespace Content.Server.Explosion.Components;
-
-/// <summary>
-/// Triggers on use in hand.
-/// </summary>
-[RegisterComponent]
-public sealed partial class TriggerOnUseComponent : Component { }
+++ /dev/null
-namespace Content.Server.Explosion.Components
-{
- /// <summary>
- /// Sends a trigger when the keyphrase is heard
- /// </summary>
- [RegisterComponent]
- public sealed partial class TriggerOnVoiceComponent : Component
- {
- public bool IsListening => IsRecording || !string.IsNullOrWhiteSpace(KeyPhrase);
-
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("keyPhrase")]
- public string? KeyPhrase;
-
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("listenRange")]
- public int ListenRange { get; private set; } = 4;
-
- [DataField("isRecording")]
- public bool IsRecording = false;
-
- [DataField("minLength")]
- public int MinLength = 3;
-
- [DataField("maxLength")]
- public int MaxLength = 50;
- }
-}
+++ /dev/null
-namespace Content.Server.Explosion.Components;
-
-/// <summary>
-/// Triggers a gun when attempting to shoot while it's empty
-/// </summary>
-[RegisterComponent]
-public sealed partial class TriggerWhenEmptyComponent : Component
-{
-}
+++ /dev/null
-using Content.Shared.Whitelist;
-
-namespace Content.Server.Explosion.Components;
-
-/// <summary>
-/// Checks if the user of a Trigger satisfies a whitelist and blacklist condition.
-/// Cancels the trigger otherwise.
-/// </summary>
-[RegisterComponent]
-public sealed partial class TriggerWhitelistComponent : Component
-{
- /// <summary>
- /// Whitelist for what entites can cause this trigger.
- /// </summary>
- [DataField]
- public EntityWhitelist? Whitelist;
-
- /// <summary>
- /// Blacklist for what entites can cause this trigger.
- /// </summary>
- [DataField]
- public EntityWhitelist? Blacklist;
-}
using Content.Server.Explosion.Components;
using Content.Server.Weapons.Ranged.Systems;
+using Content.Shared.Trigger;
using Robust.Server.GameObjects;
using Robust.Shared.Containers;
using Robust.Shared.Map;
/// </summary>
private void OnFragTrigger(Entity<ProjectileGrenadeComponent> entity, ref TriggerEvent args)
{
+ if (args.Key != entity.Comp.TriggerKey)
+ return;
+
FragmentIntoProjectiles(entity.Owner, entity.Comp);
args.Handled = true;
}
+++ /dev/null
-using Content.Server.Atmos.EntitySystems;
-using Content.Shared.Explosion.Components.OnTrigger;
-using Content.Shared.Explosion.EntitySystems;
-using Robust.Shared.Timing;
-
-namespace Content.Server.Explosion.EntitySystems;
-
-/// <summary>
-/// Releases a gas mixture to the atmosphere when triggered.
-/// Can also release gas over a set timespan to prevent trolling people
-/// with the instant-wall-of-pressure-inator.
-/// </summary>
-public sealed partial class ReleaseGasOnTriggerSystem : SharedReleaseGasOnTriggerSystem
-{
- [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
- [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
- [Dependency] private readonly IGameTiming _timing = default!;
-
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent<ReleaseGasOnTriggerComponent, TriggerEvent>(OnTrigger);
- }
-
- /// <summary>
- /// Shrimply sets the component to active when triggered, allowing it to release over time.
- /// </summary>
- private void OnTrigger(Entity<ReleaseGasOnTriggerComponent> ent, ref TriggerEvent args)
- {
- ent.Comp.Active = true;
- ent.Comp.NextReleaseTime = _timing.CurTime;
- ent.Comp.StartingTotalMoles = ent.Comp.Air.TotalMoles;
- UpdateAppearance(ent.Owner, true);
- }
-
- public override void Update(float frameTime)
- {
- base.Update(frameTime);
-
- var curTime = _timing.CurTime;
- var query = EntityQueryEnumerator<ReleaseGasOnTriggerComponent>();
-
- while (query.MoveNext(out var uid, out var comp))
- {
- if (!comp.Active || comp.NextReleaseTime > curTime)
- continue;
-
- var giverGasMix = comp.Air.Remove(comp.StartingTotalMoles * comp.RemoveFraction);
- var environment = _atmosphereSystem.GetContainingMixture(uid, false, true);
-
- if (environment == null)
- {
- UpdateAppearance(uid, false);
- RemCompDeferred<ReleaseGasOnTriggerComponent>(uid);
- continue;
- }
-
- _atmosphereSystem.Merge(environment, giverGasMix);
- comp.NextReleaseTime += comp.ReleaseInterval;
-
- if (comp.PressureLimit != 0 && environment.Pressure >= comp.PressureLimit ||
- comp.Air.TotalMoles <= 0)
- {
- UpdateAppearance(uid, false);
- RemCompDeferred<ReleaseGasOnTriggerComponent>(uid);
- continue;
- }
- }
- }
-
- private void UpdateAppearance(Entity<AppearanceComponent?> entity, bool state)
- {
- if (!Resolve(entity, ref entity.Comp, false))
- return;
-
- _appearance.SetData(entity, ReleaseGasOnTriggerVisuals.Key, state);
- }
-}
+++ /dev/null
-using Content.Shared.Explosion.Components.OnTrigger;
-using Content.Shared.Explosion.EntitySystems;
-using Content.Shared.RepulseAttract;
-using Content.Shared.Timing;
-
-namespace Content.Server.Explosion.EntitySystems;
-
-public sealed class RepulseAttractOnTriggerSystem : SharedRepulseAttractOnTriggerSystem
-{
- [Dependency] private readonly RepulseAttractSystem _repulse = default!;
- [Dependency] private readonly SharedTransformSystem _transform = default!;
- [Dependency] private readonly UseDelaySystem _delay = default!;
-
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent<SharedRepulseAttractOnTriggerComponent, TriggerEvent>(OnTrigger);
- }
-
- private void OnTrigger(Entity<SharedRepulseAttractOnTriggerComponent> ent, ref TriggerEvent args)
- {
- if (_delay.IsDelayed(ent.Owner))
- return;
-
- var position = _transform.GetMapCoordinates(ent);
- _repulse.TryRepulseAttract(position, args.User, ent.Comp.Speed, ent.Comp.Range, ent.Comp.Whitelist, ent.Comp.CollisionMask);
- }
-}
using Content.Shared.Explosion.Components;
using Content.Shared.Throwing;
+using Content.Shared.Trigger;
+using Content.Shared.Trigger.Systems;
+using Content.Shared.Trigger.Components;
using Robust.Server.GameObjects;
using Robust.Shared.Containers;
using Robust.Shared.Map;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly ThrowingSystem _throwingSystem = default!;
[Dependency] private readonly TransformSystem _transformSystem = default!;
+ [Dependency] private readonly TriggerSystem _trigger = default!;
public override void Initialize()
{
/// </summary>
private void OnScatteringTrigger(Entity<ScatteringGrenadeComponent> entity, ref TriggerEvent args)
{
+ if (args.Key != entity.Comp.TriggerKey)
+ return;
+
entity.Comp.IsTriggered = true;
args.Handled = true;
}
_throwingSystem.TryThrow(contentUid, direction, component.Velocity);
- if (component.TriggerContents)
+ if (component.TriggerContents && TryComp<TimerTriggerComponent>(contentUid, out var contentTimer))
{
additionalIntervalDelay += _random.NextFloat(component.IntervalBetweenTriggersMin, component.IntervalBetweenTriggersMax);
- var contentTimer = EnsureComp<ActiveTimerTriggerComponent>(contentUid);
- contentTimer.TimeRemaining = component.DelayBeforeTriggerContents + additionalIntervalDelay;
- var ev = new ActiveTimerTriggerEvent(contentUid, uid);
- RaiseLocalEvent(contentUid, ref ev);
+
+ _trigger.SetDelay((contentUid, contentTimer), TimeSpan.FromSeconds(component.DelayBeforeTriggerContents + additionalIntervalDelay));
+ _trigger.ActivateTimerTrigger((contentUid, contentTimer));
}
}
+++ /dev/null
-using Content.Shared.Explosion.Components;
-using Content.Shared.Explosion.EntitySystems;
-using Content.Server.Fluids.EntitySystems;
-using Content.Server.Spreader;
-using Content.Shared.Chemistry.Components;
-using Content.Shared.Coordinates.Helpers;
-using Content.Shared.Maps;
-using Robust.Server.GameObjects;
-using Robust.Shared.Map;
-
-namespace Content.Server.Explosion.EntitySystems;
-
-/// <summary>
-/// Handles creating smoke when <see cref="SmokeOnTriggerComponent"/> is triggered.
-/// </summary>
-public sealed class SmokeOnTriggerSystem : SharedSmokeOnTriggerSystem
-{
- [Dependency] private readonly IMapManager _mapMan = default!;
- [Dependency] private readonly SharedMapSystem _map = default!;
- [Dependency] private readonly SmokeSystem _smoke = default!;
- [Dependency] private readonly TransformSystem _transform = default!;
- [Dependency] private readonly SpreaderSystem _spreader = default!;
- [Dependency] private readonly TurfSystem _turf = default!;
-
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent<SmokeOnTriggerComponent, TriggerEvent>(OnTrigger);
- }
-
- private void OnTrigger(EntityUid uid, SmokeOnTriggerComponent comp, TriggerEvent args)
- {
- var xform = Transform(uid);
- var mapCoords = _transform.GetMapCoordinates(uid, xform);
- if (!_mapMan.TryFindGridAt(mapCoords, out var gridUid, out var grid) ||
- !_map.TryGetTileRef(gridUid, grid, xform.Coordinates, out var tileRef) ||
- tileRef.Tile.IsEmpty)
- {
- return;
- }
-
- if (_spreader.RequiresFloorToSpread(comp.SmokePrototype.ToString()) && _turf.IsSpace(tileRef))
- return;
-
- var coords = _map.MapToGrid(gridUid, mapCoords);
- var ent = Spawn(comp.SmokePrototype, coords.SnapToGrid());
- if (!TryComp<SmokeComponent>(ent, out var smoke))
- {
- Log.Error($"Smoke prototype {comp.SmokePrototype} was missing SmokeComponent");
- Del(ent);
- return;
- }
-
- _smoke.StartSmoke(ent, comp.Solution, comp.Duration, comp.SpreadAmount, smoke);
- }
-}
+++ /dev/null
-using Content.Server.Explosion.Components;
-using Content.Shared.Examine;
-using Content.Shared.Explosion.Components;
-using Content.Shared.Interaction.Events;
-using Content.Shared.Popups;
-using Content.Shared.Sticky;
-using Content.Shared.Verbs;
-
-namespace Content.Server.Explosion.EntitySystems;
-
-public sealed partial class TriggerSystem
-{
- [Dependency] private readonly SharedPopupSystem _popupSystem = default!;
-
- private void InitializeOnUse()
- {
- SubscribeLocalEvent<OnUseTimerTriggerComponent, UseInHandEvent>(OnTimerUse);
- SubscribeLocalEvent<OnUseTimerTriggerComponent, ExaminedEvent>(OnExamined);
- SubscribeLocalEvent<OnUseTimerTriggerComponent, GetVerbsEvent<AlternativeVerb>>(OnGetAltVerbs);
- SubscribeLocalEvent<OnUseTimerTriggerComponent, EntityStuckEvent>(OnStuck);
- SubscribeLocalEvent<RandomTimerTriggerComponent, MapInitEvent>(OnRandomTimerTriggerMapInit);
- }
-
- private void OnStuck(EntityUid uid, OnUseTimerTriggerComponent component, ref EntityStuckEvent args)
- {
- if (!component.StartOnStick)
- return;
-
- StartTimer((uid, component), args.User);
- }
-
- private void OnExamined(EntityUid uid, OnUseTimerTriggerComponent component, ExaminedEvent args)
- {
- if (args.IsInDetailsRange && component.Examinable)
- args.PushText(Loc.GetString("examine-trigger-timer", ("time", component.Delay)));
- }
-
- /// <summary>
- /// Add an alt-click interaction that cycles through delays.
- /// </summary>
- private void OnGetAltVerbs(EntityUid uid, OnUseTimerTriggerComponent component, GetVerbsEvent<AlternativeVerb> args)
- {
- if (!args.CanInteract || !args.CanAccess || args.Hands == null)
- return;
-
- if (component.UseVerbInstead)
- {
- args.Verbs.Add(new AlternativeVerb()
- {
- Text = Loc.GetString("verb-start-detonation"),
- Act = () => StartTimer((uid, component), args.User),
- Priority = 2
- });
- }
-
- if (component.AllowToggleStartOnStick)
- {
- args.Verbs.Add(new AlternativeVerb()
- {
- Text = Loc.GetString("verb-toggle-start-on-stick"),
- Act = () => ToggleStartOnStick(uid, args.User, component)
- });
- }
-
- if (component.DelayOptions == null || component.DelayOptions.Count == 1)
- return;
-
- args.Verbs.Add(new AlternativeVerb()
- {
- Category = TimerOptions,
- Text = Loc.GetString("verb-trigger-timer-cycle"),
- Act = () => CycleDelay(component, args.User),
- Priority = 1
- });
-
- foreach (var option in component.DelayOptions)
- {
- if (MathHelper.CloseTo(option, component.Delay))
- {
- args.Verbs.Add(new AlternativeVerb()
- {
- Category = TimerOptions,
- Text = Loc.GetString("verb-trigger-timer-set-current", ("time", option)),
- Disabled = true,
- Priority = (int) (-100 * option)
- });
- continue;
- }
-
- args.Verbs.Add(new AlternativeVerb()
- {
- Category = TimerOptions,
- Text = Loc.GetString("verb-trigger-timer-set", ("time", option)),
- Priority = (int) (-100 * option),
-
- Act = () =>
- {
- component.Delay = option;
- _popupSystem.PopupEntity(Loc.GetString("popup-trigger-timer-set", ("time", option)), args.User, args.User);
- },
- });
- }
- }
-
- private void OnRandomTimerTriggerMapInit(Entity<RandomTimerTriggerComponent> ent, ref MapInitEvent args)
- {
- var (_, comp) = ent;
-
- if (!TryComp<OnUseTimerTriggerComponent>(ent, out var timerTriggerComp))
- return;
-
- timerTriggerComp.Delay = _random.NextFloat(comp.Min, comp.Max);
- }
-
- private void CycleDelay(OnUseTimerTriggerComponent component, EntityUid user)
- {
- if (component.DelayOptions == null || component.DelayOptions.Count == 1)
- return;
-
- // This is somewhat inefficient, but its good enough. This is run rarely, and the lists should be short.
-
- component.DelayOptions.Sort();
-
- if (component.DelayOptions[^1] <= component.Delay)
- {
- component.Delay = component.DelayOptions[0];
- _popupSystem.PopupEntity(Loc.GetString("popup-trigger-timer-set", ("time", component.Delay)), user, user);
- return;
- }
-
- foreach (var option in component.DelayOptions)
- {
- if (option > component.Delay)
- {
- component.Delay = option;
- _popupSystem.PopupEntity(Loc.GetString("popup-trigger-timer-set", ("time", option)), user, user);
- return;
- }
- }
- }
-
- private void ToggleStartOnStick(EntityUid grenade, EntityUid user, OnUseTimerTriggerComponent comp)
- {
- if (comp.StartOnStick)
- {
- comp.StartOnStick = false;
- _popupSystem.PopupEntity(Loc.GetString("popup-start-on-stick-off"), grenade, user);
- }
- else
- {
- comp.StartOnStick = true;
- _popupSystem.PopupEntity(Loc.GetString("popup-start-on-stick-on"), grenade, user);
- }
- }
-
- private void OnTimerUse(EntityUid uid, OnUseTimerTriggerComponent component, UseInHandEvent args)
- {
- if (args.Handled || HasComp<AutomatedTimerComponent>(uid) || component.UseVerbInstead)
- return;
-
- if (component.DoPopup)
- _popupSystem.PopupEntity(Loc.GetString("trigger-activated", ("device", uid)), args.User, args.User);
-
- StartTimer((uid, component), args.User);
-
- args.Handled = true;
- }
-
- public static VerbCategory TimerOptions = new("verb-categories-timer", "/Textures/Interface/VerbIcons/clock.svg.192dpi.png");
-}
+++ /dev/null
-using Content.Server.Explosion.Components;
-using Content.Shared.Trigger;
-using Robust.Shared.Physics.Components;
-using Robust.Shared.Physics.Events;
-using Robust.Shared.Utility;
-using Robust.Shared.Timing;
-
-namespace Content.Server.Explosion.EntitySystems;
-
-public sealed partial class TriggerSystem
-{
- [Dependency] private readonly IGameTiming _timing = default!;
- [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
-
- private void InitializeProximity()
- {
- SubscribeLocalEvent<TriggerOnProximityComponent, StartCollideEvent>(OnProximityStartCollide);
- SubscribeLocalEvent<TriggerOnProximityComponent, EndCollideEvent>(OnProximityEndCollide);
- SubscribeLocalEvent<TriggerOnProximityComponent, MapInitEvent>(OnMapInit);
- SubscribeLocalEvent<TriggerOnProximityComponent, ComponentShutdown>(OnProximityShutdown);
- // Shouldn't need re-anchoring.
- SubscribeLocalEvent<TriggerOnProximityComponent, AnchorStateChangedEvent>(OnProximityAnchor);
- }
-
- private void OnProximityAnchor(EntityUid uid, TriggerOnProximityComponent component, ref AnchorStateChangedEvent args)
- {
- component.Enabled = !component.RequiresAnchored ||
- args.Anchored;
-
- SetProximityAppearance(uid, component);
-
- if (!component.Enabled)
- {
- component.Colliding.Clear();
- }
- // Re-check for contacts as we cleared them.
- else if (TryComp<PhysicsComponent>(uid, out var body))
- {
- _broadphase.RegenerateContacts((uid, body));
- }
- }
-
- private void OnProximityShutdown(EntityUid uid, TriggerOnProximityComponent component, ComponentShutdown args)
- {
- component.Colliding.Clear();
- }
-
- private void OnMapInit(EntityUid uid, TriggerOnProximityComponent component, MapInitEvent args)
- {
- component.Enabled = !component.RequiresAnchored ||
- Transform(uid).Anchored;
-
- SetProximityAppearance(uid, component);
-
- if (!TryComp<PhysicsComponent>(uid, out var body))
- return;
-
- _fixtures.TryCreateFixture(
- uid,
- component.Shape,
- TriggerOnProximityComponent.FixtureID,
- hard: false,
- body: body,
- collisionLayer: component.Layer);
- }
-
- private void OnProximityStartCollide(EntityUid uid, TriggerOnProximityComponent component, ref StartCollideEvent args)
- {
- if (args.OurFixtureId != TriggerOnProximityComponent.FixtureID)
- return;
-
- component.Colliding[args.OtherEntity] = args.OtherBody;
- }
-
- private static void OnProximityEndCollide(EntityUid uid, TriggerOnProximityComponent component, ref EndCollideEvent args)
- {
- if (args.OurFixtureId != TriggerOnProximityComponent.FixtureID)
- return;
-
- component.Colliding.Remove(args.OtherEntity);
- }
-
- private void SetProximityAppearance(EntityUid uid, TriggerOnProximityComponent component)
- {
- if (TryComp(uid, out AppearanceComponent? appearance))
- {
- _appearance.SetData(uid, ProximityTriggerVisualState.State, component.Enabled ? ProximityTriggerVisuals.Inactive : ProximityTriggerVisuals.Off, appearance);
- }
- }
-
- private void Activate(EntityUid uid, EntityUid user, TriggerOnProximityComponent component)
- {
- DebugTools.Assert(component.Enabled);
-
- var curTime = _timing.CurTime;
-
- if (!component.Repeating)
- {
- component.Enabled = false;
- component.Colliding.Clear();
- }
- else
- {
- component.NextTrigger = curTime + component.Cooldown;
- }
-
- // Queue a visual update for when the animation is complete.
- component.NextVisualUpdate = curTime + component.AnimationDuration;
-
- if (TryComp(uid, out AppearanceComponent? appearance))
- {
- _appearance.SetData(uid, ProximityTriggerVisualState.State, ProximityTriggerVisuals.Active, appearance);
- }
-
- Trigger(uid, user);
- }
-
- private void UpdateProximity()
- {
- var curTime = _timing.CurTime;
-
- var query = EntityQueryEnumerator<TriggerOnProximityComponent>();
- while (query.MoveNext(out var uid, out var trigger))
- {
- if (curTime >= trigger.NextVisualUpdate)
- {
- // Update the visual state once the animation is done.
- trigger.NextVisualUpdate = TimeSpan.MaxValue;
- SetProximityAppearance(uid, trigger);
- }
-
- if (!trigger.Enabled)
- continue;
-
- if (curTime < trigger.NextTrigger)
- // The trigger's on cooldown.
- continue;
-
- // Check for anything colliding and moving fast enough.
- foreach (var (collidingUid, colliding) in trigger.Colliding)
- {
- if (Deleted(collidingUid))
- continue;
-
- if (colliding.LinearVelocity.Length() < trigger.TriggerSpeed)
- continue;
-
- // Trigger!
- Activate(uid, collidingUid, trigger);
- break;
- }
- }
- }
-}
+++ /dev/null
-using Content.Server.DeviceLinking.Systems;
-using Content.Server.Explosion.Components;
-using Content.Shared.DeviceLinking.Events;
-
-namespace Content.Server.Explosion.EntitySystems
-{
- public sealed partial class TriggerSystem
- {
- [Dependency] private readonly DeviceLinkSystem _signalSystem = default!;
- private void InitializeSignal()
- {
- SubscribeLocalEvent<TriggerOnSignalComponent,SignalReceivedEvent>(OnSignalReceived);
- SubscribeLocalEvent<TriggerOnSignalComponent,ComponentInit>(OnInit);
-
- SubscribeLocalEvent<TimerStartOnSignalComponent,SignalReceivedEvent>(OnTimerSignalReceived);
- SubscribeLocalEvent<TimerStartOnSignalComponent,ComponentInit>(OnTimerSignalInit);
- }
-
- private void OnSignalReceived(EntityUid uid, TriggerOnSignalComponent component, ref SignalReceivedEvent args)
- {
- if (args.Port != component.Port)
- return;
-
- Trigger(uid, args.Trigger);
- }
- private void OnInit(EntityUid uid, TriggerOnSignalComponent component, ComponentInit args)
- {
- _signalSystem.EnsureSinkPorts(uid, component.Port);
- }
-
- private void OnTimerSignalReceived(EntityUid uid, TimerStartOnSignalComponent component, ref SignalReceivedEvent args)
- {
- if (args.Port != component.Port)
- return;
-
- StartTimer(uid, args.Trigger);
- }
- private void OnTimerSignalInit(EntityUid uid, TimerStartOnSignalComponent component, ComponentInit args)
- {
- _signalSystem.EnsureSinkPorts(uid, component.Port);
- }
- }
-}
+++ /dev/null
-using System.Linq;
-using Content.Server.Explosion.Components;
-using Content.Server.Explosion.EntitySystems;
-using Robust.Shared.Physics.Dynamics;
-using Robust.Shared.Physics.Events;
-
-namespace Content.Server.Explosion.EntitySystems;
-
-public sealed partial class TriggerSystem
-{
- private void InitializeTimedCollide()
- {
- SubscribeLocalEvent<TriggerOnTimedCollideComponent, StartCollideEvent>(OnTimerCollide);
- SubscribeLocalEvent<TriggerOnTimedCollideComponent, EndCollideEvent>(OnTimerEndCollide);
- SubscribeLocalEvent<TriggerOnTimedCollideComponent, ComponentRemove>(OnComponentRemove);
- }
-
- private void OnTimerCollide(EntityUid uid, TriggerOnTimedCollideComponent component, ref StartCollideEvent args)
- {
- //Ensures the entity trigger will have an active component
- EnsureComp<ActiveTriggerOnTimedCollideComponent>(uid);
- var otherUID = args.OtherEntity;
- if (component.Colliding.ContainsKey(otherUID))
- return;
- component.Colliding.Add(otherUID, 0);
- }
-
- private void OnTimerEndCollide(EntityUid uid, TriggerOnTimedCollideComponent component, ref EndCollideEvent args)
- {
- var otherUID = args.OtherEntity;
- component.Colliding.Remove(otherUID);
-
- if (component.Colliding.Count == 0 && HasComp<ActiveTriggerOnTimedCollideComponent>(uid))
- RemComp<ActiveTriggerOnTimedCollideComponent>(uid);
- }
-
- private void OnComponentRemove(EntityUid uid, TriggerOnTimedCollideComponent component, ComponentRemove args)
- {
- if (HasComp<ActiveTriggerOnTimedCollideComponent>(uid))
- RemComp<ActiveTriggerOnTimedCollideComponent>(uid);
- }
-
- private void UpdateTimedCollide(float frameTime)
- {
- var query = EntityQueryEnumerator<ActiveTriggerOnTimedCollideComponent, TriggerOnTimedCollideComponent>();
- while (query.MoveNext(out var uid, out _, out var triggerOnTimedCollide))
- {
- foreach (var (collidingEntity, collidingTimer) in triggerOnTimedCollide.Colliding)
- {
- triggerOnTimedCollide.Colliding[collidingEntity] += frameTime;
- if (collidingTimer > triggerOnTimedCollide.Threshold)
- {
- RaiseLocalEvent(uid, new TriggerEvent(uid, collidingEntity), true);
- triggerOnTimedCollide.Colliding[collidingEntity] -= triggerOnTimedCollide.Threshold;
- }
- }
- }
- }
-}
+++ /dev/null
-using Content.Server.Explosion.Components;
-using Content.Server.Speech;
-using Content.Server.Speech.Components;
-using Content.Shared.Database;
-using Content.Shared.Examine;
-using Content.Shared.Verbs;
-
-namespace Content.Server.Explosion.EntitySystems
-{
- public sealed partial class TriggerSystem
- {
- private void InitializeVoice()
- {
- SubscribeLocalEvent<TriggerOnVoiceComponent, ComponentInit>(OnVoiceInit);
- SubscribeLocalEvent<TriggerOnVoiceComponent, ExaminedEvent>(OnVoiceExamine);
- SubscribeLocalEvent<TriggerOnVoiceComponent, GetVerbsEvent<AlternativeVerb>>(OnVoiceGetAltVerbs);
- SubscribeLocalEvent<TriggerOnVoiceComponent, ListenEvent>(OnListen);
- }
-
- private void OnVoiceInit(EntityUid uid, TriggerOnVoiceComponent component, ComponentInit args)
- {
- if (component.IsListening)
- EnsureComp<ActiveListenerComponent>(uid).Range = component.ListenRange;
- else
- RemCompDeferred<ActiveListenerComponent>(uid);
- }
-
- private void OnListen(Entity<TriggerOnVoiceComponent> ent, ref ListenEvent args)
- {
- var component = ent.Comp;
- var message = args.Message.Trim();
-
- if (component.IsRecording)
- {
- var ev = new ListenAttemptEvent(args.Source);
- RaiseLocalEvent(ent, ev);
-
- if (ev.Cancelled)
- return;
-
- if (message.Length >= component.MinLength && message.Length <= component.MaxLength)
- FinishRecording(ent, args.Source, args.Message);
- else if (message.Length > component.MaxLength)
- _popupSystem.PopupEntity(Loc.GetString("popup-trigger-voice-record-failed-too-long"), ent);
- else if (message.Length < component.MinLength)
- _popupSystem.PopupEntity(Loc.GetString("popup-trigger-voice-record-failed-too-short"), ent);
-
- return;
- }
-
- if (!string.IsNullOrWhiteSpace(component.KeyPhrase) && message.IndexOf(component.KeyPhrase, StringComparison.InvariantCultureIgnoreCase) is var index and >= 0 )
- {
- _adminLogger.Add(LogType.Trigger, LogImpact.Medium,
- $"A voice-trigger on {ToPrettyString(ent):entity} was triggered by {ToPrettyString(args.Source):speaker} speaking the key-phrase {component.KeyPhrase}.");
- Trigger(ent, args.Source);
-
- var messageWithoutPhrase = message.Remove(index, component.KeyPhrase.Length).Trim();
-
- var voice = new VoiceTriggeredEvent(args.Source, message, messageWithoutPhrase);
- RaiseLocalEvent(ent, ref voice);
- }
- }
-
- private void OnVoiceGetAltVerbs(Entity<TriggerOnVoiceComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
- {
- if (!args.CanInteract || !args.CanAccess)
- return;
-
- var component = ent.Comp;
-
- var @event = args;
- args.Verbs.Add(new AlternativeVerb()
- {
- Text = Loc.GetString(component.IsRecording ? "verb-trigger-voice-stop" : "verb-trigger-voice-record"),
- Act = () =>
- {
- if (component.IsRecording)
- StopRecording(ent);
- else
- StartRecording(ent, @event.User);
- },
- Priority = 1
- });
-
- if (string.IsNullOrWhiteSpace(component.KeyPhrase))
- return;
-
- args.Verbs.Add(new AlternativeVerb()
- {
- Text = Loc.GetString("verb-trigger-voice-clear"),
- Act = () =>
- {
- component.KeyPhrase = null;
- component.IsRecording = false;
- RemComp<ActiveListenerComponent>(ent);
- }
- });
- }
-
- public void StartRecording(Entity<TriggerOnVoiceComponent> ent, EntityUid user)
- {
- var component = ent.Comp;
- component.IsRecording = true;
- EnsureComp<ActiveListenerComponent>(ent).Range = component.ListenRange;
-
- _adminLogger.Add(LogType.Trigger, LogImpact.Low,
- $"A voice-trigger on {ToPrettyString(ent):entity} has started recording. User: {ToPrettyString(user):user}");
-
- _popupSystem.PopupEntity(Loc.GetString("popup-trigger-voice-start-recording"), ent);
- }
-
- public void StopRecording(Entity<TriggerOnVoiceComponent> ent)
- {
- var component = ent.Comp;
- component.IsRecording = false;
- if (string.IsNullOrWhiteSpace(component.KeyPhrase))
- RemComp<ActiveListenerComponent>(ent);
-
- _popupSystem.PopupEntity(Loc.GetString("popup-trigger-voice-stop-recording"), ent);
- }
-
- public void FinishRecording(Entity<TriggerOnVoiceComponent> ent, EntityUid source, string message)
- {
- var component = ent.Comp;
- component.KeyPhrase = message;
- component.IsRecording = false;
-
- _adminLogger.Add(LogType.Trigger, LogImpact.Low,
- $"A voice-trigger on {ToPrettyString(ent):entity} has recorded a new keyphrase: '{component.KeyPhrase}'. Recorded from {ToPrettyString(source):speaker}");
-
- _popupSystem.PopupEntity(Loc.GetString("popup-trigger-voice-recorded", ("keyphrase", component.KeyPhrase!)), ent);
- }
-
- private void OnVoiceExamine(EntityUid uid, TriggerOnVoiceComponent component, ExaminedEvent args)
- {
- if (args.IsInDetailsRange)
- {
- args.PushText(string.IsNullOrWhiteSpace(component.KeyPhrase)
- ? Loc.GetString("trigger-voice-uninitialized")
- : Loc.GetString("examine-trigger-voice", ("keyphrase", component.KeyPhrase)));
- }
- }
- }
-}
-
-
-/// <summary>
-/// Raised when a voice trigger is activated, containing the message that triggered it.
-/// </summary>
-/// <param name="Source"> The EntityUid of the entity sending the message</param>
-/// <param name="Message"> The contents of the message</param>
-/// <param name="MessageWithoutPhrase"> The message without the phrase that triggered it.</param>
-[ByRefEvent]
-public readonly record struct VoiceTriggeredEvent(EntityUid Source, string Message, string MessageWithoutPhrase);
+++ /dev/null
-using Content.Server.Administration.Logs;
-using Content.Server.Body.Systems;
-using Content.Server.Explosion.Components;
-using Content.Shared.Flash;
-using Content.Server.Electrocution;
-using Content.Server.Pinpointer;
-using Content.Shared.Chemistry.EntitySystems;
-using Content.Shared.Flash.Components;
-using Content.Server.Radio.EntitySystems;
-using Content.Shared.Chemistry.Components;
-using Content.Shared.Chemistry.Components.SolutionManager;
-using Content.Shared.Database;
-using Content.Shared.Explosion.Components;
-using Content.Shared.Explosion.Components.OnTrigger;
-using Content.Shared.Implants.Components;
-using Content.Shared.Interaction;
-using Content.Shared.Interaction.Events;
-using Content.Shared.Inventory;
-using Content.Shared.Mobs;
-using Content.Shared.Mobs.Components;
-using Content.Shared.Payload.Components;
-using Content.Shared.Radio;
-using Content.Shared.Slippery;
-using Content.Shared.StepTrigger.Systems;
-using Content.Shared.Trigger;
-using Content.Shared.Weapons.Ranged.Events;
-using Content.Shared.Whitelist;
-using JetBrains.Annotations;
-using Robust.Shared.Audio;
-using Robust.Shared.Audio.Systems;
-using Robust.Shared.Containers;
-using Robust.Shared.Physics.Events;
-using Robust.Shared.Physics.Systems;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Random;
-using Robust.Shared.Utility;
-
-namespace Content.Server.Explosion.EntitySystems
-{
- /// <summary>
- /// Raised whenever something is Triggered on the entity.
- /// </summary>
- public sealed class TriggerEvent : HandledEntityEventArgs
- {
- public EntityUid Triggered { get; }
- public EntityUid? User { get; }
-
- public TriggerEvent(EntityUid triggered, EntityUid? user = null)
- {
- Triggered = triggered;
- User = user;
- }
- }
-
- /// <summary>
- /// Raised before a trigger is activated.
- /// </summary>
- [ByRefEvent]
- public record struct BeforeTriggerEvent(EntityUid Triggered, EntityUid? User, bool Cancelled = false);
-
- /// <summary>
- /// Raised when timer trigger becomes active.
- /// </summary>
- [ByRefEvent]
- public readonly record struct ActiveTimerTriggerEvent(EntityUid Triggered, EntityUid? User);
-
- [UsedImplicitly]
- public sealed partial class TriggerSystem : EntitySystem
- {
- [Dependency] private readonly ExplosionSystem _explosions = default!;
- [Dependency] private readonly FixtureSystem _fixtures = default!;
- [Dependency] private readonly SharedFlashSystem _flashSystem = default!;
- [Dependency] private readonly SharedBroadphaseSystem _broadphase = default!;
- [Dependency] private readonly IAdminLogManager _adminLogger = default!;
- [Dependency] private readonly SharedContainerSystem _container = default!;
- [Dependency] private readonly BodySystem _body = default!;
- [Dependency] private readonly SharedAudioSystem _audio = default!;
- [Dependency] private readonly SharedTransformSystem _transformSystem = default!;
- [Dependency] private readonly NavMapSystem _navMap = default!;
- [Dependency] private readonly RadioSystem _radioSystem = default!;
- [Dependency] private readonly IRobustRandom _random = default!;
- [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
- [Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
- [Dependency] private readonly InventorySystem _inventory = default!;
- [Dependency] private readonly ElectrocutionSystem _electrocution = default!;
- [Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
-
- public override void Initialize()
- {
- base.Initialize();
-
- InitializeProximity();
- InitializeOnUse();
- InitializeSignal();
- InitializeTimedCollide();
- InitializeVoice();
- InitializeMobstate();
-
- SubscribeLocalEvent<TriggerOnSpawnComponent, MapInitEvent>(OnSpawnTriggered);
- SubscribeLocalEvent<TriggerOnCollideComponent, StartCollideEvent>(OnTriggerCollide);
- SubscribeLocalEvent<TriggerOnActivateComponent, ActivateInWorldEvent>(OnActivate);
- SubscribeLocalEvent<TriggerOnUseComponent, UseInHandEvent>(OnUse);
- SubscribeLocalEvent<TriggerImplantActionComponent, ActivateImplantEvent>(OnImplantTrigger);
- SubscribeLocalEvent<TriggerOnStepTriggerComponent, StepTriggeredOffEvent>(OnStepTriggered);
- SubscribeLocalEvent<TriggerOnSlipComponent, SlipEvent>(OnSlipTriggered);
- SubscribeLocalEvent<TriggerWhenEmptyComponent, OnEmptyGunShotEvent>(OnEmptyTriggered);
- SubscribeLocalEvent<RepeatingTriggerComponent, MapInitEvent>(OnRepeatInit);
-
- SubscribeLocalEvent<SpawnOnTriggerComponent, TriggerEvent>(OnSpawnTrigger);
- SubscribeLocalEvent<DeleteOnTriggerComponent, TriggerEvent>(HandleDeleteTrigger);
- SubscribeLocalEvent<ExplodeOnTriggerComponent, TriggerEvent>(HandleExplodeTrigger);
- SubscribeLocalEvent<FlashOnTriggerComponent, TriggerEvent>(HandleFlashTrigger);
- SubscribeLocalEvent<GibOnTriggerComponent, TriggerEvent>(HandleGibTrigger);
-
- SubscribeLocalEvent<AnchorOnTriggerComponent, TriggerEvent>(OnAnchorTrigger);
- SubscribeLocalEvent<SoundOnTriggerComponent, TriggerEvent>(OnSoundTrigger);
- SubscribeLocalEvent<ShockOnTriggerComponent, TriggerEvent>(HandleShockTrigger);
- SubscribeLocalEvent<RattleComponent, TriggerEvent>(HandleRattleTrigger);
-
- SubscribeLocalEvent<TriggerWhitelistComponent, BeforeTriggerEvent>(HandleWhitelist);
- }
-
- private void HandleWhitelist(Entity<TriggerWhitelistComponent> ent, ref BeforeTriggerEvent args)
- {
- args.Cancelled = !_whitelist.CheckBoth(args.User, ent.Comp.Blacklist, ent.Comp.Whitelist);
- }
-
- private void OnSoundTrigger(EntityUid uid, SoundOnTriggerComponent component, TriggerEvent args)
- {
- if (component.RemoveOnTrigger) // if the component gets removed when it's triggered
- {
- var xform = Transform(uid);
- _audio.PlayPvs(component.Sound, xform.Coordinates); // play the sound at its last known coordinates
- }
- else // if the component doesn't get removed when triggered
- {
- _audio.PlayPvs(component.Sound, uid); // have the sound follow the entity itself
- }
- }
-
- private void HandleShockTrigger(Entity<ShockOnTriggerComponent> shockOnTrigger, ref TriggerEvent args)
- {
- if (!_container.TryGetContainingContainer(shockOnTrigger.Owner, out var container))
- return;
-
- var containerEnt = container.Owner;
- var curTime = _timing.CurTime;
-
- if (curTime < shockOnTrigger.Comp.NextTrigger)
- {
- // The trigger's on cooldown.
- return;
- }
-
- _electrocution.TryDoElectrocution(containerEnt, null, shockOnTrigger.Comp.Damage, shockOnTrigger.Comp.Duration, true, ignoreInsulation: true);
- shockOnTrigger.Comp.NextTrigger = curTime + shockOnTrigger.Comp.Cooldown;
- }
-
- private void OnAnchorTrigger(EntityUid uid, AnchorOnTriggerComponent component, TriggerEvent args)
- {
- var xform = Transform(uid);
-
- if (xform.Anchored)
- return;
-
- _transformSystem.AnchorEntity(uid, xform);
-
- if (component.RemoveOnTrigger)
- RemCompDeferred<AnchorOnTriggerComponent>(uid);
- }
-
- private void OnSpawnTrigger(Entity<SpawnOnTriggerComponent> ent, ref TriggerEvent args)
- {
- var xform = Transform(ent);
-
- if (ent.Comp.mapCoords)
- {
- var mapCoords = _transformSystem.GetMapCoordinates(ent, xform);
- Spawn(ent.Comp.Proto, mapCoords);
- }
- else
- {
- var coords = xform.Coordinates;
- if (!coords.IsValid(EntityManager))
- return;
- Spawn(ent.Comp.Proto, coords);
-
- }
- }
-
- private void HandleExplodeTrigger(EntityUid uid, ExplodeOnTriggerComponent component, TriggerEvent args)
- {
- _explosions.TriggerExplosive(uid, user: args.User);
- args.Handled = true;
- }
-
- private void HandleFlashTrigger(EntityUid uid, FlashOnTriggerComponent component, TriggerEvent args)
- {
- _flashSystem.FlashArea(uid, args.User, component.Range, component.Duration, probability: component.Probability);
- args.Handled = true;
- }
-
- private void HandleDeleteTrigger(EntityUid uid, DeleteOnTriggerComponent component, TriggerEvent args)
- {
- QueueDel(uid);
- args.Handled = true;
- }
-
- private void HandleGibTrigger(EntityUid uid, GibOnTriggerComponent component, TriggerEvent args)
- {
- if (!TryComp(uid, out TransformComponent? xform))
- return;
- if (component.DeleteItems)
- {
- var items = _inventory.GetHandOrInventoryEntities(xform.ParentUid);
- foreach (var item in items)
- {
- Del(item);
- }
- }
- _body.GibBody(xform.ParentUid, true);
- args.Handled = true;
- }
-
-
- private void HandleRattleTrigger(EntityUid uid, RattleComponent component, TriggerEvent args)
- {
- if (!TryComp<SubdermalImplantComponent>(uid, out var implanted))
- return;
-
- if (implanted.ImplantedEntity == null)
- return;
-
- // Gets location of the implant
- var posText = FormattedMessage.RemoveMarkupOrThrow(_navMap.GetNearestBeaconString(uid));
- var critMessage = Loc.GetString(component.CritMessage, ("user", implanted.ImplantedEntity.Value), ("position", posText));
- var deathMessage = Loc.GetString(component.DeathMessage, ("user", implanted.ImplantedEntity.Value), ("position", posText));
-
- if (!TryComp<MobStateComponent>(implanted.ImplantedEntity, out var mobstate))
- return;
-
- // Sends a message to the radio channel specified by the implant
- if (mobstate.CurrentState == MobState.Critical)
- _radioSystem.SendRadioMessage(uid, critMessage, _prototypeManager.Index<RadioChannelPrototype>(component.RadioChannel), uid);
- if (mobstate.CurrentState == MobState.Dead)
- _radioSystem.SendRadioMessage(uid, deathMessage, _prototypeManager.Index<RadioChannelPrototype>(component.RadioChannel), uid);
-
- args.Handled = true;
- }
-
- private void OnTriggerCollide(EntityUid uid, TriggerOnCollideComponent component, ref StartCollideEvent args)
- {
- if (args.OurFixtureId == component.FixtureID && (!component.IgnoreOtherNonHard || args.OtherFixture.Hard))
- Trigger(uid, args.OtherEntity);
- }
-
- private void OnSpawnTriggered(EntityUid uid, TriggerOnSpawnComponent component, MapInitEvent args)
- {
- Trigger(uid);
- }
-
- private void OnActivate(EntityUid uid, TriggerOnActivateComponent component, ActivateInWorldEvent args)
- {
- if (args.Handled || !args.Complex)
- return;
-
- Trigger(uid, args.User);
- args.Handled = true;
- }
-
- private void OnUse(Entity<TriggerOnUseComponent> ent, ref UseInHandEvent args)
- {
- if (args.Handled)
- return;
-
- Trigger(ent.Owner, args.User);
- args.Handled = true;
- }
-
- private void OnImplantTrigger(EntityUid uid, TriggerImplantActionComponent component, ActivateImplantEvent args)
- {
- args.Handled = Trigger(uid);
- }
-
- private void OnStepTriggered(EntityUid uid, TriggerOnStepTriggerComponent component, ref StepTriggeredOffEvent args)
- {
- Trigger(uid, args.Tripper);
- }
-
- private void OnSlipTriggered(EntityUid uid, TriggerOnSlipComponent component, ref SlipEvent args)
- {
- Trigger(uid, args.Slipped);
- }
-
- private void OnEmptyTriggered(EntityUid uid, TriggerWhenEmptyComponent component, ref OnEmptyGunShotEvent args)
- {
- Trigger(uid, args.EmptyGun);
- }
-
- private void OnRepeatInit(Entity<RepeatingTriggerComponent> ent, ref MapInitEvent args)
- {
- ent.Comp.NextTrigger = _timing.CurTime + ent.Comp.Delay;
- }
-
- public bool Trigger(EntityUid trigger, EntityUid? user = null)
- {
- var beforeTriggerEvent = new BeforeTriggerEvent(trigger, user);
- RaiseLocalEvent(trigger, ref beforeTriggerEvent);
- if (beforeTriggerEvent.Cancelled)
- return false;
-
- var triggerEvent = new TriggerEvent(trigger, user);
- EntityManager.EventBus.RaiseLocalEvent(trigger, triggerEvent, true);
- return triggerEvent.Handled;
- }
-
- public void TryDelay(EntityUid uid, float amount, ActiveTimerTriggerComponent? comp = null)
- {
- if (!Resolve(uid, ref comp, false))
- return;
-
- comp.TimeRemaining += amount;
- }
-
- /// <summary>
- /// Start the timer for triggering the device.
- /// </summary>
- public void StartTimer(Entity<OnUseTimerTriggerComponent?> ent, EntityUid? user)
- {
- if (!Resolve(ent, ref ent.Comp, false))
- return;
-
- var comp = ent.Comp;
- HandleTimerTrigger(ent, user, comp.Delay, comp.BeepInterval, comp.InitialBeepDelay, comp.BeepSound);
- }
-
- public void HandleTimerTrigger(EntityUid uid, EntityUid? user, float delay, float beepInterval, float? initialBeepDelay, SoundSpecifier? beepSound)
- {
- if (delay <= 0)
- {
- RemComp<ActiveTimerTriggerComponent>(uid);
- Trigger(uid, user);
- return;
- }
-
- if (HasComp<ActiveTimerTriggerComponent>(uid))
- return;
-
- if (user != null)
- {
- // Check if entity is bomb/mod. grenade/etc
- if (_container.TryGetContainer(uid, "payload", out BaseContainer? container) &&
- container.ContainedEntities.Count > 0 &&
- TryComp(container.ContainedEntities[0], out ChemicalPayloadComponent? chemicalPayloadComponent))
- {
- // If a beaker is missing, the entity won't explode, so no reason to log it
- if (chemicalPayloadComponent?.BeakerSlotA.Item is not { } beakerA ||
- chemicalPayloadComponent?.BeakerSlotB.Item is not { } beakerB ||
- !TryComp(beakerA, out SolutionContainerManagerComponent? containerA) ||
- !TryComp(beakerB, out SolutionContainerManagerComponent? containerB) ||
- !TryComp(beakerA, out FitsInDispenserComponent? fitsA) ||
- !TryComp(beakerB, out FitsInDispenserComponent? fitsB) ||
- !_solutionContainerSystem.TryGetSolution((beakerA, containerA), fitsA.Solution, out _, out var solutionA) ||
- !_solutionContainerSystem.TryGetSolution((beakerB, containerB), fitsB.Solution, out _, out var solutionB))
- return;
-
- _adminLogger.Add(LogType.Trigger,
- $"{ToPrettyString(user.Value):user} started a {delay} second timer trigger on entity {ToPrettyString(uid):timer}, which contains {SharedSolutionContainerSystem.ToPrettyString(solutionA)} in one beaker and {SharedSolutionContainerSystem.ToPrettyString(solutionB)} in the other.");
- }
- else
- {
- _adminLogger.Add(LogType.Trigger,
- $"{ToPrettyString(user.Value):user} started a {delay} second timer trigger on entity {ToPrettyString(uid):timer}");
- }
-
- }
- else
- {
- _adminLogger.Add(LogType.Trigger,
- $"{delay} second timer trigger started on entity {ToPrettyString(uid):timer}");
- }
-
- var active = AddComp<ActiveTimerTriggerComponent>(uid);
- active.TimeRemaining = delay;
- active.User = user;
- active.BeepSound = beepSound;
- active.BeepInterval = beepInterval;
- active.TimeUntilBeep = initialBeepDelay == null ? active.BeepInterval : initialBeepDelay.Value;
-
- var ev = new ActiveTimerTriggerEvent(uid, user);
- RaiseLocalEvent(uid, ref ev);
-
- if (TryComp<AppearanceComponent>(uid, out var appearance))
- _appearance.SetData(uid, TriggerVisuals.VisualState, TriggerVisualState.Primed, appearance);
- }
-
- public override void Update(float frameTime)
- {
- base.Update(frameTime);
-
- UpdateProximity();
- UpdateTimer(frameTime);
- UpdateTimedCollide(frameTime);
- UpdateRepeat();
- }
-
- private void UpdateTimer(float frameTime)
- {
- HashSet<EntityUid> toRemove = new();
- var query = EntityQueryEnumerator<ActiveTimerTriggerComponent>();
- while (query.MoveNext(out var uid, out var timer))
- {
- timer.TimeRemaining -= frameTime;
- timer.TimeUntilBeep -= frameTime;
-
- if (timer.TimeRemaining <= 0)
- {
- Trigger(uid, timer.User);
- toRemove.Add(uid);
- continue;
- }
-
- if (timer.BeepSound == null || timer.TimeUntilBeep > 0)
- continue;
-
- timer.TimeUntilBeep += timer.BeepInterval;
- _audio.PlayPvs(timer.BeepSound, uid, timer.BeepSound.Params);
- }
-
- foreach (var uid in toRemove)
- {
- RemComp<ActiveTimerTriggerComponent>(uid);
-
- // In case this is a re-usable grenade, un-prime it.
- if (TryComp<AppearanceComponent>(uid, out var appearance))
- _appearance.SetData(uid, TriggerVisuals.VisualState, TriggerVisualState.Unprimed, appearance);
- }
- }
-
- private void UpdateRepeat()
- {
- var now = _timing.CurTime;
- var query = EntityQueryEnumerator<RepeatingTriggerComponent>();
- while (query.MoveNext(out var uid, out var comp))
- {
- if (comp.NextTrigger > now)
- continue;
-
- comp.NextTrigger = now + comp.Delay;
- Trigger(uid);
- }
- }
- }
-}
+++ /dev/null
-using Robust.Shared.Timing;
-using Robust.Shared.Serialization.Manager;
-using Content.Server.Explosion.Components.OnTrigger;
-
-namespace Content.Server.Explosion.EntitySystems;
-
-public sealed class TwoStageTriggerSystem : EntitySystem
-{
- [Dependency] private readonly IGameTiming _timing = default!;
- [Dependency] private readonly ISerializationManager _serializationManager = default!;
- [Dependency] private readonly TriggerSystem _triggerSystem = default!;
-
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent<TwoStageTriggerComponent, TriggerEvent>(OnTrigger);
- }
-
- private void OnTrigger(EntityUid uid, TwoStageTriggerComponent component, TriggerEvent args)
- {
- if (component.Triggered)
- return;
-
- component.Triggered = true;
- component.NextTriggerTime = _timing.CurTime + component.TriggerDelay;
- }
-
- private void LoadComponents(EntityUid uid, TwoStageTriggerComponent component)
- {
- foreach (var (name, entry) in component.SecondStageComponents)
- {
- var comp = (Component) Factory.GetComponent(name);
- var temp = (object)comp;
-
- if (EntityManager.TryGetComponent(uid, entry.Component.GetType(), out var c))
- RemComp(uid, c);
-
- _serializationManager.CopyTo(entry.Component, ref temp);
- AddComp(uid, comp);
- }
- component.ComponentsIsLoaded = true;
- }
-
- public override void Update(float frameTime)
- {
- base.Update(frameTime);
-
- var enumerator = EntityQueryEnumerator<TwoStageTriggerComponent>();
- while (enumerator.MoveNext(out var uid, out var component))
- {
- if (!component.Triggered)
- continue;
-
- if (!component.ComponentsIsLoaded)
- LoadComponents(uid, component);
-
- if (_timing.CurTime < component.NextTriggerTime)
- continue;
-
- component.NextTriggerTime = null;
- _triggerSystem.Trigger(uid);
- }
- }
-}
+++ /dev/null
-namespace Content.Server.GhostKick;
-
-[RegisterComponent]
-public sealed partial class GhostKickUserOnTriggerComponent : Component
-{
-
-}
+++ /dev/null
-using Content.Server.Explosion.EntitySystems;
-using Robust.Shared.Player;
-
-namespace Content.Server.GhostKick;
-
-public sealed class GhostKickUserOnTriggerSystem : EntitySystem
-{
- [Dependency] private readonly GhostKickManager _ghostKickManager = default!;
-
- public override void Initialize()
- {
- SubscribeLocalEvent<GhostKickUserOnTriggerComponent, TriggerEvent>(HandleMineTriggered);
- }
-
- private void HandleMineTriggered(EntityUid uid, GhostKickUserOnTriggerComponent userOnTriggerComponent, TriggerEvent args)
- {
- if (!TryComp(args.User, out ActorComponent? actor))
- return;
-
- _ghostKickManager.DoDisconnect(
- actor.PlayerSession.Channel,
- "Tripped over a kick mine, crashed through the fourth wall");
-
- args.Handled = true;
- }
-}
using Content.Server.Chat.Systems;
using Content.Server.Popups;
using Content.Server.Power.EntitySystems;
-using Content.Server.Speech.Components;
using Content.Server.Telephone;
using Content.Shared.Access.Systems;
using Content.Shared.Audio;
using Content.Shared.Power;
using Content.Shared.Silicons.StationAi;
using Content.Shared.Speech;
+using Content.Shared.Speech.Components;
using Content.Shared.Telephone;
using Content.Shared.UserInterface;
using Content.Shared.Verbs;
entity.Comp.User = (user.Value, holopadUser);
}
- // Add the new user to PVS and sync their appearance with any
+ // Add the new user to PVS and sync their appearance with any
// holopads connected to the one they are using
_pvs.AddGlobalOverride(user.Value);
SyncHolopadHologramAppearanceWithTarget(entity, entity.Comp.User);
-using Content.Server.Audio;
-using Content.Server.Explosion.EntitySystems;
-using Content.Shared.Damage.Systems;
-using Content.Shared.Hands.Components;
-using Content.Shared.Hands.EntitySystems;
using Content.Shared.HotPotato;
-using Content.Shared.Popups;
-using Content.Shared.Weapons.Melee.Events;
namespace Content.Server.HotPotato;
-public sealed class HotPotatoSystem : SharedHotPotatoSystem
-{
- [Dependency] private readonly SharedHandsSystem _hands = default!;
- [Dependency] private readonly SharedPopupSystem _popup = default!;
- [Dependency] private readonly AmbientSoundSystem _ambientSound = default!;
- [Dependency] private readonly DamageOnHoldingSystem _damageOnHolding = default!;
-
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent<HotPotatoComponent, ActiveTimerTriggerEvent>(OnActiveTimer);
- SubscribeLocalEvent<HotPotatoComponent, MeleeHitEvent>(OnMeleeHit);
- }
-
- private void OnActiveTimer(EntityUid uid, HotPotatoComponent comp, ref ActiveTimerTriggerEvent args)
- {
- EnsureComp<ActiveHotPotatoComponent>(uid);
- comp.CanTransfer = false;
- _ambientSound.SetAmbience(uid, true);
- _damageOnHolding.SetEnabled(uid, true);
- Dirty(uid, comp);
- }
-
- private void OnMeleeHit(EntityUid uid, HotPotatoComponent comp, MeleeHitEvent args)
- {
- if (!HasComp<ActiveHotPotatoComponent>(uid))
- return;
-
- comp.CanTransfer = true;
- foreach (var hitEntity in args.HitEntities)
- {
- if (!TryComp<HandsComponent>(hitEntity, out var hands))
- continue;
-
- if (!_hands.IsHolding((hitEntity, hands), uid, out _) && _hands.TryForcePickupAnyHand(hitEntity, uid, handsComp: hands))
- {
- _popup.PopupEntity(Loc.GetString("hot-potato-passed",
- ("from", args.User), ("to", hitEntity)), uid, PopupType.Medium);
- break;
- }
-
- _popup.PopupEntity(Loc.GetString("hot-potato-failed",
- ("to", hitEntity)), uid, PopupType.Medium);
-
- break;
- }
- comp.CanTransfer = false;
- Dirty(uid, comp);
- }
-}
+public sealed class HotPotatoSystem : SharedHotPotatoSystem;
+++ /dev/null
-using Robust.Shared.Audio;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
-
-namespace Content.Server.IgnitionSource;
-
-/// <summary>
-/// Ignites for a certain length of time when triggered.
-/// Requires <see cref="Shared.IgnitionSourceComponent"/> along with triggering components.
-/// </summary>
-[RegisterComponent, Access(typeof(IgniteOnTriggerSystem))]
-public sealed partial class IgniteOnTriggerComponent : Component
-{
- /// <summary>
- /// Once ignited, the time it will unignite at.
- /// </summary>
- [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
- public TimeSpan IgnitedUntil = TimeSpan.Zero;
-
- /// <summary>
- /// How long the ignition source is active for after triggering.
- /// </summary>
- [DataField, ViewVariables(VVAccess.ReadWrite)]
- public TimeSpan IgnitedTime = TimeSpan.FromSeconds(0.5);
-
- /// <summary>
- /// Sound to play when igniting.
- /// </summary>
- [DataField]
- public SoundSpecifier IgniteSound = new SoundCollectionSpecifier("WelderOn");
-}
-using Content.Server.Explosion.EntitySystems;
using Content.Shared.Armable;
using Content.Shared.Item.ItemToggle.Components;
using Content.Shared.LandMines;
using Content.Shared.Popups;
using Content.Shared.StepTrigger.Systems;
+using Content.Shared.Trigger.Systems;
using Robust.Shared.Audio.Systems;
namespace Content.Server.LandMines;
/// </summary>
private void HandleStepOnTriggered(EntityUid uid, LandMineComponent component, ref StepTriggeredOnEvent args)
{
- if (!string.IsNullOrEmpty(component.TriggerText))
- {
- _popupSystem.PopupCoordinates(
- Loc.GetString(component.TriggerText, ("mine", uid)),
- Transform(uid).Coordinates,
- args.Tripper,
- PopupType.LargeCaution);
- }
- _audioSystem.PlayPvs(component.Sound, uid);
+ if (!string.IsNullOrEmpty(component.TriggerText))
+ {
+ _popupSystem.PopupCoordinates(
+ Loc.GetString(component.TriggerText, ("mine", uid)),
+ Transform(uid).Coordinates,
+ args.Tripper,
+ PopupType.LargeCaution);
+ }
+ _audioSystem.PlayPvs(component.Sound, uid);
}
/// <summary>
/// </summary>
private void HandleStepOffTriggered(EntityUid uid, LandMineComponent component, ref StepTriggeredOffEvent args)
{
- _trigger.Trigger(uid, args.Tripper);
+ // TODO: Adjust to the new trigger system
+ _trigger.Trigger(uid, args.Tripper, TriggerSystem.DefaultTriggerKey);
}
/// <summary>
+++ /dev/null
-using Content.Server.Damage.Systems;
-using Content.Server.Explosion.EntitySystems;
-using Content.Server.Popups;
-using Content.Shared.Interaction.Events;
-using Content.Shared.Inventory;
-using Content.Shared.Mousetrap;
-using Content.Shared.StepTrigger;
-using Content.Shared.StepTrigger.Systems;
-using Robust.Server.GameObjects;
-using Robust.Shared.Physics.Components;
-using Robust.Shared.Player;
-
-namespace Content.Server.Mousetrap;
-
-public sealed class MousetrapSystem : EntitySystem
-{
- [Dependency] private readonly PopupSystem _popupSystem = default!;
- [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
-
- public override void Initialize()
- {
- SubscribeLocalEvent<MousetrapComponent, UseInHandEvent>(OnUseInHand);
- SubscribeLocalEvent<MousetrapComponent, BeforeDamageUserOnTriggerEvent>(BeforeDamageOnTrigger);
- SubscribeLocalEvent<MousetrapComponent, StepTriggerAttemptEvent>(OnStepTriggerAttempt);
- SubscribeLocalEvent<MousetrapComponent, TriggerEvent>(OnTrigger);
- }
-
- private void OnUseInHand(EntityUid uid, MousetrapComponent component, UseInHandEvent args)
- {
- if (args.Handled)
- return;
-
- component.IsActive = !component.IsActive;
- _popupSystem.PopupEntity(component.IsActive
- ? Loc.GetString("mousetrap-on-activate")
- : Loc.GetString("mousetrap-on-deactivate"),
- uid,
- args.User);
-
- UpdateVisuals(uid);
-
- args.Handled = true;
- }
-
- private void OnStepTriggerAttempt(EntityUid uid, MousetrapComponent component, ref StepTriggerAttemptEvent args)
- {
- args.Continue |= component.IsActive;
- }
-
- private void BeforeDamageOnTrigger(EntityUid uid, MousetrapComponent component, BeforeDamageUserOnTriggerEvent args)
- {
- if (TryComp(args.Tripper, out PhysicsComponent? physics) && physics.Mass != 0)
- {
- // The idea here is inverse,
- // Small - big damage,
- // Large - small damage
- // yes i punched numbers into a calculator until the graph looked right
- var scaledDamage = -50 * Math.Atan(physics.Mass - component.MassBalance) + (25 * Math.PI);
- args.Damage *= scaledDamage;
- }
- }
-
- private void OnTrigger(EntityUid uid, MousetrapComponent component, TriggerEvent args)
- {
- component.IsActive = false;
- UpdateVisuals(uid);
- }
-
- private void UpdateVisuals(EntityUid uid, MousetrapComponent? mousetrap = null, AppearanceComponent? appearance = null)
- {
- if (!Resolve(uid, ref mousetrap, ref appearance, false))
- {
- return;
- }
-
- _appearance.SetData(uid, MousetrapVisuals.Visual,
- mousetrap.IsActive ? MousetrapVisuals.Armed : MousetrapVisuals.Unarmed, appearance);
- }
-}
-using Content.Server.Explosion.EntitySystems;
using Content.Server.Mind;
using Content.Server.Objectives.Components;
using Content.Server.Popups;
using Content.Shared.Ninja.Systems;
using Content.Shared.Roles;
using Content.Shared.Sticky;
+using Content.Shared.Trigger;
namespace Content.Server.Ninja.Systems;
/// </summary>
private void OnExplode(EntityUid uid, SpiderChargeComponent comp, TriggerEvent args)
{
+ if (args.Key != comp.TriggerKey)
+ return;
+
if (!TryComp<SpaceNinjaComponent>(comp.Planter, out var ninja))
return;
-using Content.Server.Explosion.EntitySystems;
using Content.Server.Fluids.EntitySystems;
using Content.Server.Nutrition.Components;
using Content.Server.Popups;
using Content.Shared.Containers.ItemSlots;
-using Content.Shared.Explosion.Components;
using Content.Shared.IdentityManagement;
using Content.Shared.Nutrition;
using Content.Shared.Nutrition.Components;
using Content.Shared.Nutrition.EntitySystems;
using Content.Shared.Rejuvenate;
using Content.Shared.Throwing;
+using Content.Shared.Trigger.Components;
+using Content.Shared.Trigger.Systems;
using Content.Shared.Chemistry.EntitySystems;
using JetBrains.Annotations;
using Robust.Shared.Audio;
{
if (_itemSlots.TryEject(uid, itemSlot, user: null, out var item))
{
- if (TryComp<OnUseTimerTriggerComponent>(item.Value, out var timerTrigger))
+ if (TryComp<TimerTriggerComponent>(item.Value, out var timerTrigger))
{
- _trigger.HandleTimerTrigger(
- item.Value,
- null,
- timerTrigger.Delay,
- timerTrigger.BeepInterval,
- timerTrigger.InitialBeepDelay,
- timerTrigger.BeepSound);
+ _trigger.ActivateTimerTrigger((item.Value, timerTrigger));
}
}
}
using Content.Server.Administration.Logs;
-using Content.Server.Explosion.EntitySystems;
using Content.Shared.Chemistry.Components;
using Content.Shared.Database;
using Content.Shared.Examine;
using Content.Shared.Payload.Components;
using Content.Shared.Tag;
+using Content.Shared.Trigger;
using Content.Shared.Chemistry.EntitySystems;
using Robust.Shared.Containers;
using Robust.Shared.Serialization.Manager;
private void OnCaseTriggered(EntityUid uid, PayloadCaseComponent component, TriggerEvent args)
{
+ // TODO: Adjust to the new trigger system
+
if (!TryComp(uid, out ContainerManagerComponent? contMan))
return;
// Pass trigger event onto all contained payloads. Payload capacity configurable by construction graphs.
foreach (var ent in GetAllPayloads(uid, contMan))
{
- RaiseLocalEvent(ent, args, false);
+ RaiseLocalEvent(ent, ref args, false);
}
}
private void OnTriggerTriggered(EntityUid uid, PayloadTriggerComponent component, TriggerEvent args)
{
+ // TODO: Adjust to the new trigger system
+
if (!component.Active)
return;
// Ensure we don't enter a trigger-loop
DebugTools.Assert(!_tagSystem.HasTag(uid, PayloadTag));
- RaiseLocalEvent(parent, args, false);
+ RaiseLocalEvent(parent, ref args);
}
private void OnEntityInserted(EntityUid uid, PayloadCaseComponent _, EntInsertedIntoContainerMessage args)
private void HandleChemicalPayloadTrigger(Entity<ChemicalPayloadComponent> entity, ref TriggerEvent args)
{
+ // TODO: Adjust to the new trigger system
if (entity.Comp.BeakerSlotA.Item is not EntityUid beakerA
|| entity.Comp.BeakerSlotB.Item is not EntityUid beakerB
|| !TryComp(beakerA, out FitsInDispenserComponent? compA)
+++ /dev/null
-using Content.Shared.Polymorph;
-using Robust.Shared.Prototypes;
-
-namespace Content.Server.Polymorph.Components;
-
-/// <summary>
-/// Intended for use with the trigger system.
-/// Polymorphs the user of the trigger.
-/// </summary>
-[RegisterComponent]
-public sealed partial class PolymorphOnTriggerComponent : Component
-{
- /// <summary>
- /// Polymorph settings.
- /// </summary>
- [DataField(required: true)]
- public ProtoId<PolymorphPrototype> Polymorph;
-}
+++ /dev/null
-using Content.Shared.Polymorph;
-using Content.Server.Polymorph.Components;
-using Content.Server.Explosion.EntitySystems;
-using Robust.Shared.Prototypes;
-
-namespace Content.Server.Polymorph.Systems;
-
-public sealed partial class PolymorphSystem
-{
- /// <summary>
- /// Need to do this so we don't get a collection enumeration error in physics by polymorphing
- /// an entity we're colliding with in case of TriggerOnCollide.
- /// Also makes sure other trigger effects don't activate in nullspace after we have polymorphed.
- /// </summary>
- private Queue<(EntityUid Ent, ProtoId<PolymorphPrototype> Polymorph)> _queuedPolymorphUpdates = new();
-
- private void InitializeTrigger()
- {
- SubscribeLocalEvent<PolymorphOnTriggerComponent, TriggerEvent>(OnTrigger);
- }
-
- private void OnTrigger(Entity<PolymorphOnTriggerComponent> ent, ref TriggerEvent args)
- {
- if (args.User == null)
- return;
-
- _queuedPolymorphUpdates.Enqueue((args.User.Value, ent.Comp.Polymorph));
- args.Handled = true;
- }
-
- public void UpdateTrigger()
- {
- while (_queuedPolymorphUpdates.TryDequeue(out var data))
- {
- if (TerminatingOrDeleted(data.Item1))
- continue;
-
- PolymorphEntity(data.Item1, data.Item2);
- }
- }
-}
SubscribeLocalEvent<PolymorphedEntityComponent, DestructionEventArgs>(OnDestruction);
InitializeMap();
- InitializeTrigger();
}
public override void Update(float frameTime)
Revert((uid, comp));
}
}
-
- UpdateTrigger();
}
private void OnComponentStartup(Entity<PolymorphableComponent> ent, ref ComponentStartup args)
using Content.Server.Chat.Systems;
using Content.Server.Interaction;
using Content.Server.Popups;
-using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
using Content.Server.Radio.Components;
-using Content.Server.Speech;
-using Content.Server.Speech.Components;
using Content.Shared.Examine;
using Content.Shared.Interaction;
using Content.Shared.Power;
using Content.Shared.Radio;
+using Content.Shared.Speech;
+using Content.Shared.Speech.Components;
using Content.Shared.Chat;
using Content.Shared.Radio.Components;
using Robust.Shared.Prototypes;
var message = Loc.GetString(ent.Comp.DestroyingPopup, ("name", Name(ent)));
Popup.PopupEntity(message, ent);
- _trigger.StartTimer(ent.Owner, user: null);
+ _trigger.ActivateTimerTrigger(ent.Owner);
// prevent a shitter borg running into people
RemComp<InputMoverComponent>(ent);
using Content.Server.Administration.Logs;
using Content.Server.Administration.Managers;
using Content.Server.DeviceNetwork.Systems;
-using Content.Server.Explosion.EntitySystems;
using Content.Server.Hands.Systems;
using Content.Server.PowerCell;
using Content.Shared.Alert;
using Content.Shared.Silicons.Borgs;
using Content.Shared.Silicons.Borgs.Components;
using Content.Shared.Throwing;
+using Content.Shared.Trigger.Systems;
using Content.Shared.Whitelist;
using Content.Shared.Wires;
using Robust.Server.GameObjects;
+++ /dev/null
-using Content.Server.Explosion.EntitySystems;
-using Content.Shared.Sound.Components;
-
-namespace Content.Server.Sound.Components
-{
- /// <summary>
- /// Whenever a <see cref="TriggerEvent"/> is run play a sound in PVS range.
- /// </summary>
- [RegisterComponent]
- public sealed partial class EmitSoundOnTriggerComponent : BaseEmitSoundComponent
- {
- }
-}
-using Content.Server.Explosion.EntitySystems;
-using Content.Server.Sound.Components;
-using Content.Shared.UserInterface;
using Content.Shared.Sound;
using Content.Shared.Sound.Components;
using Robust.Shared.Timing;
{
base.Initialize();
- SubscribeLocalEvent<EmitSoundOnTriggerComponent, TriggerEvent>(HandleEmitSoundOnTrigger);
SubscribeLocalEvent<SpamEmitSoundComponent, MapInitEvent>(HandleSpamEmitSoundMapInit);
}
- private void HandleEmitSoundOnTrigger(EntityUid uid, EmitSoundOnTriggerComponent component, TriggerEvent args)
- {
- TryEmitSound(uid, component, args.User, false);
- args.Handled = true;
- }
-
private void HandleSpamEmitSoundMapInit(Entity<SpamEmitSoundComponent> entity, ref MapInitEvent args)
{
SpamEmitSoundReset(entity);
+++ /dev/null
-using Content.Server.Chat.Systems;
-
-namespace Content.Server.Speech.Components;
-
-/// <summary>
-/// This component is used to relay speech events to other systems.
-/// </summary>
-[RegisterComponent]
-public sealed partial class ActiveListenerComponent : Component
-{
- [DataField("range")]
- public float Range = ChatSystem.VoiceRange;
-}
using Content.Server.Speech.Components;
+using Content.Shared.Speech;
namespace Content.Server.Speech.EntitySystems;
using Content.Server.Chat.Systems;
-using Content.Server.Speech.Components;
+using Content.Shared.Speech;
+using Content.Shared.Speech.Components;
namespace Content.Server.Speech.EntitySystems;
using Content.Server.Chat.Systems;
-using Content.Server.Speech;
-using Content.Server.Speech.Components;
+using Content.Shared.Speech;
+using Content.Shared.Speech.Components;
using Content.Shared.Whitelist;
using Robust.Shared.Player;
using static Content.Server.Chat.Systems.ChatSystem;
using Content.Server.Chat.Systems;
using Content.Server.Interaction;
using Content.Server.Power.EntitySystems;
-using Content.Server.Speech;
-using Content.Server.Speech.Components;
using Content.Shared.Chat;
using Content.Shared.Database;
using Content.Shared.Labels.Components;
using Content.Shared.Silicons.StationAi;
using Content.Shared.Silicons.Borgs.Components;
using Content.Shared.Speech;
+using Content.Shared.Speech.Components;
using Content.Shared.Telephone;
using Robust.Server.GameObjects;
using Robust.Shared.Audio.Systems;
using Content.Server.AlertLevel;
-using Content.Server.Explosion.EntitySystems;
+using Content.Shared.Trigger;
+using Content.Shared.Trigger.Components.Effects;
using Content.Server.Station.Systems;
-namespace Content.Server.AlertLevel.Systems;
+namespace Content.Server.Trigger.Systems;
public sealed class AlertLevelChangeOnTriggerSystem : EntitySystem
{
private void OnTrigger(Entity<AlertLevelChangeOnTriggerComponent> ent, ref TriggerEvent args)
{
+ if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+ return;
+
var stationUid = _station.GetOwningStation(ent.Owner);
- if (!stationUid.HasValue)
+ if (stationUid == null)
return;
_alertLevelSystem.SetLevel(stationUid.Value, ent.Comp.Level, ent.Comp.PlaySound, ent.Comp.Announce, ent.Comp.Force);
+ args.Handled = true;
}
}
--- /dev/null
+using Content.Shared.Trigger;
+using Content.Shared.Trigger.Components.Effects;
+using Content.Server.GhostKick;
+using Robust.Shared.Player;
+
+namespace Content.Server.Trigger.Systems;
+
+public sealed class GhostKickUserOnTriggerSystem : EntitySystem
+{
+ [Dependency] private readonly GhostKickManager _ghostKickManager = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<GhostKickOnTriggerComponent, TriggerEvent>(OnTrigger);
+ }
+
+ private void OnTrigger(Entity<GhostKickOnTriggerComponent> ent, ref TriggerEvent args)
+ {
+ if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+ return;
+
+ var target = ent.Comp.TargetUser ? args.User : ent.Owner;
+
+ if (target == null)
+ return;
+
+ if (!TryComp(target, out ActorComponent? actor))
+ return;
+
+ _ghostKickManager.DoDisconnect(
+ actor.PlayerSession.Channel,
+ Loc.GetString(ent.Comp.Reason));
+
+ args.Handled = true;
+ }
+}
-using Content.Server.Explosion.EntitySystems;
using Content.Shared.IgnitionSource;
-using Content.Shared.Timing;
-using Robust.Shared.Audio.Systems;
+using Content.Shared.Trigger;
+using Content.Shared.Trigger.Components.Effects;
using Robust.Shared.Timing;
-namespace Content.Server.IgnitionSource;
+namespace Content.Server.Trigger.Systems;
/// <summary>
/// Handles igniting when triggered and stopping ignition after the delay.
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedIgnitionSourceSystem _source = default!;
- [Dependency] private readonly SharedAudioSystem _audio = default!;
- [Dependency] private readonly UseDelaySystem _useDelay = default!;
public override void Initialize()
{
SubscribeLocalEvent<IgniteOnTriggerComponent, TriggerEvent>(OnTrigger);
}
+ // TODO: move this into ignition source component
+ // it already has an update loop
public override void Update(float deltaTime)
{
base.Update(deltaTime);
private void OnTrigger(Entity<IgniteOnTriggerComponent> ent, ref TriggerEvent args)
{
- // prevent spamming sound and ignition
- if (!TryComp(ent.Owner, out UseDelayComponent? useDelay) || _useDelay.IsDelayed((ent.Owner, useDelay)))
+ if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
return;
- _source.SetIgnited(ent.Owner);
- _audio.PlayPvs(ent.Comp.IgniteSound, ent);
+ var target = ent.Comp.TargetUser ? args.User : ent.Owner;
- _useDelay.TryResetDelay((ent.Owner, useDelay));
+ if (target == null)
+ return;
+
+ _source.SetIgnited(target.Value);
ent.Comp.IgnitedUntil = _timing.CurTime + ent.Comp.IgnitedTime;
+ Dirty(ent);
+
+ args.Handled = true;
}
}
--- /dev/null
+using Content.Server.Polymorph.Systems;
+using Content.Shared.Polymorph;
+using Content.Shared.Trigger;
+using Content.Shared.Trigger.Components.Effects;
+using Robust.Shared.Prototypes;
+
+namespace Content.Server.Trigger.Systems;
+
+public sealed partial class PolymorphOnTriggerSystem : EntitySystem
+{
+ [Dependency] private readonly PolymorphSystem _polymorph = default!;
+
+ /// <summary>
+ /// Need to do this so we don't get a collection enumeration error in physics by polymorphing
+ /// an entity we're colliding with in case of TriggerOnCollide.
+ /// Also makes sure other trigger effects don't activate in nullspace after we have polymorphed.
+ /// </summary>
+ private Queue<(EntityUid Uid, ProtoId<PolymorphPrototype> Polymorph)> _queuedPolymorphUpdates = new();
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<PolymorphOnTriggerComponent, TriggerEvent>(OnTrigger);
+ }
+
+ private void OnTrigger(Entity<PolymorphOnTriggerComponent> ent, ref TriggerEvent args)
+ {
+ if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+ return;
+
+ var target = ent.Comp.TargetUser ? args.User : ent.Owner;
+
+ if (target == null)
+ return;
+
+ _queuedPolymorphUpdates.Enqueue((target.Value, ent.Comp.Polymorph));
+ args.Handled = true;
+ }
+
+ public override void Update(float frametime)
+ {
+ while (_queuedPolymorphUpdates.TryDequeue(out var data))
+ {
+ if (TerminatingOrDeleted(data.Uid))
+ continue;
+
+ _polymorph.PolymorphEntity(data.Uid, data.Polymorph);
+ }
+ }
+}
--- /dev/null
+using Content.Server.Radio.EntitySystems;
+using Content.Server.Pinpointer;
+using Content.Shared.Mobs.Components;
+using Content.Shared.Trigger;
+using Content.Shared.Trigger.Components.Effects;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Server.Trigger.Systems;
+
+public sealed class RattleOnTriggerSystem : EntitySystem
+{
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+ [Dependency] private readonly RadioSystem _radio = default!;
+ [Dependency] private readonly NavMapSystem _navMap = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<RattleOnTriggerComponent, TriggerEvent>(OnTrigger);
+ }
+
+ private void OnTrigger(Entity<RattleOnTriggerComponent> ent, ref TriggerEvent args)
+ {
+ if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+ return;
+
+ var target = ent.Comp.TargetUser ? args.User : ent.Owner;
+
+ if (target == null)
+ return;
+
+ if (!TryComp<MobStateComponent>(target.Value, out var mobstate))
+ return;
+
+ args.Handled = true;
+
+ if (!ent.Comp.Messages.TryGetValue(mobstate.CurrentState, out var messageId))
+ return;
+
+ // Gets the location of the user
+ var posText = FormattedMessage.RemoveMarkupOrThrow(_navMap.GetNearestBeaconString(target.Value));
+
+ var message = Loc.GetString(messageId, ("user", target.Value), ("position", posText));
+ // Sends a message to the radio channel specified by the implant
+ _radio.SendRadioMessage(ent.Owner, message, _prototypeManager.Index(ent.Comp.RadioChannel), ent.Owner);
+ }
+}
--- /dev/null
+using Content.Server.Atmos.EntitySystems;
+using Content.Shared.Trigger.Components.Effects;
+using Content.Shared.Trigger.Systems;
+using Robust.Shared.Timing;
+
+namespace Content.Server.Trigger.Systems;
+
+public sealed class ReleaseGasOnTriggerSystem : SharedReleaseGasOnTriggerSystem
+{
+ [Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
+ [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ var curTime = _timing.CurTime;
+ var query = EntityQueryEnumerator<ReleaseGasOnTriggerComponent>();
+
+ while (query.MoveNext(out var uid, out var comp))
+ {
+ if (!comp.Active || comp.NextReleaseTime > curTime)
+ continue;
+
+ var giverGasMix = comp.Air.Remove(comp.StartingTotalMoles * comp.RemoveFraction);
+ var environment = _atmosphereSystem.GetContainingMixture(uid, false, true);
+
+ if (environment == null)
+ {
+ _appearance.SetData(uid, ReleaseGasOnTriggerVisuals.Key, false);
+ RemCompDeferred<ReleaseGasOnTriggerComponent>(uid);
+ continue;
+ }
+
+ _atmosphereSystem.Merge(environment, giverGasMix);
+ comp.NextReleaseTime += comp.ReleaseInterval;
+
+ if (comp.PressureLimit != 0 && environment.Pressure >= comp.PressureLimit ||
+ comp.Air.TotalMoles <= 0)
+ {
+ _appearance.SetData(uid, ReleaseGasOnTriggerVisuals.Key, false);
+ RemCompDeferred<ReleaseGasOnTriggerComponent>(uid);
+ }
+ }
+ }
+}
--- /dev/null
+using Content.Server.Fluids.EntitySystems;
+using Content.Server.Spreader;
+using Content.Shared.Chemistry.Components;
+using Content.Shared.Coordinates.Helpers;
+using Content.Shared.Maps;
+using Content.Shared.Trigger;
+using Content.Shared.Trigger.Components.Effects;
+using Robust.Server.GameObjects;
+using Robust.Shared.Map;
+
+namespace Content.Server.Trigger.Systems;
+
+/// <summary>
+/// Handles creating smoke when <see cref="SmokeOnTriggerComponent"/> is triggered.
+/// </summary>
+public sealed class SmokeOnTriggerSystem : EntitySystem
+{
+ [Dependency] private readonly IMapManager _mapMan = default!;
+ [Dependency] private readonly MapSystem _map = default!;
+ [Dependency] private readonly SmokeSystem _smoke = default!;
+ [Dependency] private readonly TransformSystem _transform = default!;
+ [Dependency] private readonly SpreaderSystem _spreader = default!;
+ [Dependency] private readonly TurfSystem _turf = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<SmokeOnTriggerComponent, TriggerEvent>(OnTrigger);
+ }
+
+ private void OnTrigger(Entity<SmokeOnTriggerComponent> ent, ref TriggerEvent args)
+ {
+ if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+ return;
+
+ var target = ent.Comp.TargetUser ? args.User : ent.Owner;
+
+ if (target == null)
+ return;
+
+ // TODO: move all of this into an API function in SmokeSystem
+ var xform = Transform(target.Value);
+ var mapCoords = _transform.GetMapCoordinates(target.Value, xform);
+ if (!_mapMan.TryFindGridAt(mapCoords, out var gridUid, out var gridComp) ||
+ !_map.TryGetTileRef(gridUid, gridComp, xform.Coordinates, out var tileRef) ||
+ tileRef.Tile.IsEmpty)
+ {
+ return;
+ }
+
+ if (_spreader.RequiresFloorToSpread(ent.Comp.SmokePrototype.ToString()) && _turf.IsSpace(tileRef))
+ return;
+
+ var coords = _map.MapToGrid(gridUid, mapCoords);
+ var smoke = Spawn(ent.Comp.SmokePrototype, coords.SnapToGrid());
+ if (!TryComp<SmokeComponent>(smoke, out var smokeComp))
+ {
+ Log.Error($"Smoke prototype {ent.Comp.SmokePrototype} was missing SmokeComponent");
+ Del(smoke);
+ return;
+ }
+
+ _smoke.StartSmoke(smoke, ent.Comp.Solution, (float)ent.Comp.Duration.TotalSeconds, ent.Comp.SpreadAmount, smokeComp);
+
+ args.Handled = true;
+ }
+}
-using Content.Server.Explosion.EntitySystems;
-using Content.Shared.Timing;
+using Content.Server.Chat.Systems;
+using Content.Shared.Trigger;
+using Content.Shared.Trigger.Components.Effects;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
-namespace Content.Server.Chat.Systems;
+namespace Content.Server.Trigger.Systems;
public sealed class SpeakOnTriggerSystem : EntitySystem
{
- [Dependency] private readonly UseDelaySystem _useDelay = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly ChatSystem _chat = default!;
public override void Initialize()
{
base.Initialize();
+
SubscribeLocalEvent<SpeakOnTriggerComponent, TriggerEvent>(OnTrigger);
}
private void OnTrigger(Entity<SpeakOnTriggerComponent> ent, ref TriggerEvent args)
{
- TrySpeak(ent);
- args.Handled = true;
- }
-
- private void TrySpeak(Entity<SpeakOnTriggerComponent> ent)
- {
- // If it doesn't have the use delay component, still send the message.
- if (TryComp<UseDelayComponent>(ent.Owner, out var useDelay) && _useDelay.IsDelayed((ent.Owner, useDelay)))
+ if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
return;
- if (!_prototypeManager.TryIndex(ent.Comp.Pack, out var messagePack))
+ var target = ent.Comp.TargetUser ? args.User : ent.Owner;
+
+ if (target == null)
return;
- var message = Loc.GetString(_random.Pick(messagePack.Values));
+ string message;
+ if (ent.Comp.Text != null)
+ message = Loc.GetString(ent.Comp.Text);
+ else
+ {
+ if (!_prototypeManager.TryIndex(ent.Comp.Pack, out var messagePack))
+ return;
+ message = Loc.GetString(_random.Pick(messagePack.Values));
+ }
// Chatcode moment: messages starting with "." are considered radio messages.
// Prepending ">" forces the message to be spoken instead.
// TODO chat refactor: remove this
message = '>' + message;
- _chat.TrySendInGameICMessage(ent.Owner, message, InGameICChatType.Speak, true);
-
- if (useDelay != null)
- _useDelay.TryResetDelay((ent.Owner, useDelay));
+ _chat.TrySendInGameICMessage(target.Value, message, InGameICChatType.Speak, true);
+ args.Handled = true;
}
}
using Content.Shared.Inventory;
using Content.Shared.Popups;
using Content.Shared.Storage;
+using Content.Shared.Trigger;
using Robust.Server.Containers;
namespace Content.Server.VoiceTrigger;
-using Content.Shared.Explosion.Components.OnTrigger;
+using Content.Shared.Explosion.Components;
namespace Content.Server.Xenoarchaeology.Artifact.XAE.Components;
/// <summary>
-/// Activates 'trigger' for <see cref="ExplodeOnTriggerComponent"/>.
+/// Activates <see cref="ExplosiveComponent"/> to explode.
/// </summary>
[RegisterComponent, Access(typeof(XAETriggerExplosivesSystem))]
public sealed partial class XAETriggerExplosivesComponent : Component;
public const char WhisperPrefix = ',';
public const char DefaultChannelKey = 'h';
+ public const int VoiceRange = 10; // how far voice goes in world units
+ public const int WhisperClearRange = 2; // how far whisper goes while still being understandable, in world units
+ public const int WhisperMuffledRange = 5; // how far whisper goes at all, in world units
+ public const string DefaultAnnouncementSound = "/Audio/Announcements/announce.ogg";
+
public static readonly ProtoId<RadioChannelPrototype> CommonChannel = "Common";
public static readonly string DefaultChannelPrefix = $"{RadioChannelPrefix}{DefaultChannelKey}";
+++ /dev/null
-namespace Content.Shared.Damage.Components;
-
-[RegisterComponent]
-public sealed partial class DamageUserOnTriggerComponent : Component
-{
- [DataField("ignoreResistances")] public bool IgnoreResistances;
-
- [DataField("damage", required: true)]
- public DamageSpecifier Damage = default!;
-}
+using Robust.Shared.Map;
using Robust.Shared.Timing;
namespace Content.Shared.Emp;
[Dependency] protected readonly IGameTiming Timing = default!;
protected const string EmpDisabledEffectPrototype = "EffectEmpDisabled";
+
+ /// <summary>
+ /// Triggers an EMP pulse at the given location, by first raising an <see cref="EmpAttemptEvent"/>, then a raising <see cref="EmpPulseEvent"/> on all entities in range.
+ /// </summary>
+ /// <param name="coordinates">The location to trigger the EMP pulse at.</param>
+ /// <param name="range">The range of the EMP pulse.</param>
+ /// <param name="energyConsumption">The amount of energy consumed by the EMP pulse.</param>
+ /// <param name="duration">The duration of the EMP effects.</param>
+ public virtual void EmpPulse(MapCoordinates coordinates, float range, float energyConsumption, float duration)
+ {
+ }
}
+++ /dev/null
-using Robust.Shared.Audio;
-using Robust.Shared.GameStates;
-
-namespace Content.Shared.Explosion.Components;
-
-/// <summary>
-/// Component for tracking active trigger timers. A timers can activated by some other component, e.g. <see cref="OnUseTimerTriggerComponent"/>.
-/// </summary>
-[RegisterComponent]
-public sealed partial class ActiveTimerTriggerComponent : Component
-{
- [DataField] public float TimeRemaining;
-
- [DataField] public EntityUid? User;
-
- [DataField] public float BeepInterval;
-
- [DataField] public float TimeUntilBeep;
-
- [DataField] public SoundSpecifier? BeepSound;
-}
+++ /dev/null
-using Robust.Shared.GameStates;
-
-namespace Content.Shared.Explosion.Components.OnTrigger;
-
-/// <summary>
-/// Explode using the entity's <see cref="ExplosiveComponent"/> if Triggered.
-/// </summary>
-[RegisterComponent, NetworkedComponent]
-public sealed partial class ExplodeOnTriggerComponent : Component
-{
-}
+++ /dev/null
-using System.Linq;
-using Content.Shared.Guidebook;
-using Robust.Shared.Audio;
-using Robust.Shared.GameStates;
-
-namespace Content.Shared.Explosion.Components
-{
- [RegisterComponent, NetworkedComponent]
- public sealed partial class OnUseTimerTriggerComponent : Component
- {
- [DataField] public float Delay = 1f;
-
- /// <summary>
- /// If not null, a user can use verbs to configure the delay to one of these options.
- /// </summary>
- [DataField] public List<float>? DelayOptions = null;
-
- /// <summary>
- /// If not null, this timer will periodically play this sound while active.
- /// </summary>
- [DataField] public SoundSpecifier? BeepSound;
-
- /// <summary>
- /// Time before beeping starts. Defaults to a single beep interval. If set to zero, will emit a beep immediately after use.
- /// </summary>
- [DataField] public float? InitialBeepDelay;
-
- [DataField] public float BeepInterval = 1;
-
- /// <summary>
- /// Whether the timer should instead be activated through a verb in the right-click menu
- /// </summary>
- [DataField] public bool UseVerbInstead = false;
-
- /// <summary>
- /// Should timer be started when it was stuck to another entity.
- /// Used for C4 charges and similar behaviour.
- /// </summary>
- [DataField] public bool StartOnStick;
-
- /// <summary>
- /// Allows changing the start-on-stick quality.
- /// </summary>
- [DataField("canToggleStartOnStick")] public bool AllowToggleStartOnStick;
-
- /// <summary>
- /// Whether you can examine the item to see its timer or not.
- /// </summary>
- [DataField] public bool Examinable = true;
-
- /// <summary>
- /// Whether or not to show the user a popup when starting the timer.
- /// </summary>
- [DataField] public bool DoPopup = true;
-
- #region GuidebookData
-
- [GuidebookData]
- public float? ShortestDelayOption => DelayOptions?.Min();
-
- [GuidebookData]
- public float? LongestDelayOption => DelayOptions?.Max();
-
- #endregion GuidebookData
- }
-}
/// We need to store this because we are only allowed to spawn and trigger timed entities on the next available frame update
/// </summary>
public bool IsTriggered = false;
+
+ /// <summary>
+ /// The trigger key that will activate the grenade.
+ /// </summary>
+ [DataField]
+ public string TriggerKey = "timer";
}
+++ /dev/null
-using Robust.Shared.GameStates;
-
-namespace Content.Shared.Explosion.Components;
-
-[NetworkedComponent]
-public abstract partial class SharedTriggerOnProximityComponent : Component
-{
-
-}
+++ /dev/null
-namespace Content.Shared.Explosion.EntitySystems;
-
-public abstract partial class SharedReleaseGasOnTriggerSystem : EntitySystem;
-
-// I have dreams of Atmos in shared.
+++ /dev/null
-namespace Content.Shared.Explosion.EntitySystems;
-
-public abstract class SharedRepulseAttractOnTriggerSystem : EntitySystem;
+++ /dev/null
-namespace Content.Shared.Explosion.EntitySystems;
-
-public abstract class SharedSmokeOnTriggerSystem : EntitySystem
-{
-}
+++ /dev/null
-namespace Content.Shared.Explosion.EntitySystems;
-
-public abstract class SharedTriggerSystem : EntitySystem
-{
-
-}
\ No newline at end of file
+++ /dev/null
-using Robust.Shared.GameStates;
-namespace Content.Shared.Flash.Components;
-
-/// <summary>
-/// Upon being triggered will flash in an area around it.
-/// </summary>
-[RegisterComponent, NetworkedComponent]
-public sealed partial class FlashOnTriggerComponent : Component
-{
- [DataField]
- public float Range = 1.0f;
-
- [DataField]
- public TimeSpan Duration = TimeSpan.FromSeconds(8);
-
- [DataField]
- public float Probability = 1.0f;
-}
/// <summary>
/// Hot potato effect spawn cooldown in seconds
/// </summary>
- [DataField("effectCooldown"), ViewVariables(VVAccess.ReadWrite)]
+ [DataField]
public float EffectCooldown = 0.3f;
/// <summary>
/// <summary>
/// If set to true entity can be removed by hitting entities if they have hands
/// </summary>
- [DataField("canTransfer"), ViewVariables(VVAccess.ReadWrite)]
- [AutoNetworkedField]
+ [DataField, AutoNetworkedField]
public bool CanTransfer = true;
}
+using Content.Shared.Audio;
+using Content.Shared.Damage.Systems;
+using Content.Shared.Hands.Components;
+using Content.Shared.Hands.EntitySystems;
+using Content.Shared.IdentityManagement;
+using Content.Shared.Popups;
+using Content.Shared.Trigger;
+using Content.Shared.Weapons.Melee.Events;
using Robust.Shared.Containers;
+using Robust.Shared.Timing;
namespace Content.Shared.HotPotato;
public abstract class SharedHotPotatoSystem : EntitySystem
{
+ [Dependency] private readonly SharedHandsSystem _hands = default!;
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
+ [Dependency] private readonly SharedAmbientSoundSystem _ambientSound = default!;
+ [Dependency] private readonly DamageOnHoldingSystem _damageOnHolding = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+
+
public override void Initialize()
{
base.Initialize();
+
SubscribeLocalEvent<HotPotatoComponent, ContainerGettingRemovedAttemptEvent>(OnRemoveAttempt);
+ SubscribeLocalEvent<HotPotatoComponent, ActiveTimerTriggerEvent>(OnActiveTimer);
+ SubscribeLocalEvent<HotPotatoComponent, MeleeHitEvent>(OnMeleeHit);
}
- private void OnRemoveAttempt(EntityUid uid, HotPotatoComponent comp, ContainerGettingRemovedAttemptEvent args)
+ private void OnRemoveAttempt(Entity<HotPotatoComponent> ent, ref ContainerGettingRemovedAttemptEvent args)
{
- if (!comp.CanTransfer)
+ if (!_timing.ApplyingState && !ent.Comp.CanTransfer)
args.Cancel();
}
+
+ private void OnActiveTimer(Entity<HotPotatoComponent> ent, ref ActiveTimerTriggerEvent args)
+ {
+ EnsureComp<ActiveHotPotatoComponent>(ent);
+ ent.Comp.CanTransfer = false;
+ _ambientSound.SetAmbience(ent.Owner, true);
+ _damageOnHolding.SetEnabled(ent.Owner, true);
+ Dirty(ent);
+ }
+
+ private void OnMeleeHit(Entity<HotPotatoComponent> ent, ref MeleeHitEvent args)
+ {
+ if (!HasComp<ActiveHotPotatoComponent>(ent))
+ return;
+
+ ent.Comp.CanTransfer = true;
+ foreach (var hitEntity in args.HitEntities)
+ {
+ if (!TryComp<HandsComponent>(hitEntity, out var hands))
+ continue;
+
+ if (!_hands.IsHolding((hitEntity, hands), ent.Owner, out _) && _hands.TryForcePickupAnyHand(hitEntity, ent.Owner, handsComp: hands))
+ {
+ _popup.PopupPredicted(
+ Loc.GetString("hot-potato-passed", ("from", Identity.Entity(args.User, EntityManager)), ("to", Identity.Entity(hitEntity, EntityManager))),
+ ent.Owner,
+ args.User,
+ PopupType.Medium);
+ break;
+ }
+
+ _popup.PopupClient(
+ Loc.GetString("hot-potato-failed", ("to", Identity.Entity(hitEntity, EntityManager))),
+ ent.Owner,
+ args.User,
+ PopupType.Medium);
+
+ break;
+ }
+
+ ent.Comp.CanTransfer = false;
+ }
}
+++ /dev/null
-using Content.Shared.Radio;
-using Robust.Shared.GameStates;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.Implants.Components;
-
-[RegisterComponent, NetworkedComponent]
-public sealed partial class RattleComponent : Component
-{
- // The radio channel the message will be sent to
- [DataField]
- public ProtoId<RadioChannelPrototype> RadioChannel = "Syndicate";
-
- // The message that the implant will send when crit
- [DataField]
- public LocId CritMessage = "deathrattle-implant-critical-message";
-
- // The message that the implant will send when dead
- [DataField]
- public LocId DeathMessage = "deathrattle-implant-dead-message";
-}
+++ /dev/null
-using Robust.Shared.GameStates;
-
-namespace Content.Shared.Implants.Components;
-
-/// <summary>
-/// Triggers implants when the action is pressed
-/// </summary>
-[RegisterComponent, NetworkedComponent]
-public sealed partial class TriggerImplantActionComponent : Component
-{
-
-}
if (!_container.TryGetContainer(uid, ImplanterComponent.ImplantSlotId, out var implantContainer))
return;
- var relayEv = new ImplantRelayEvent<T>(args);
+ var relayEv = new ImplantRelayEvent<T>(args, uid);
foreach (var implant in implantContainer.ContainedEntities)
{
if (args is HandledEntityEventArgs { Handled : true })
{
public readonly T Event;
- public ImplantRelayEvent(T ev)
+ public readonly EntityUid ImplantedEntity;
+
+ public ImplantRelayEvent(T ev, EntityUid implantedEntity)
{
Event = ev;
+ ImplantedEntity = implantedEntity;
}
}
/// /// <remarks>
/// If server-side systems affect the item's toggle, like charge/fuel systems, then the item is not predictable.
/// </remarks>
- [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField]
+ [DataField, AutoNetworkedField]
public bool Predictable = true;
/// <summary>
/// The noise this item makes when it is toggled on.
/// </summary>
- [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField]
+ [DataField, AutoNetworkedField]
public SoundSpecifier? SoundActivate;
/// <summary>
/// The noise this item makes when it is toggled off.
/// </summary>
- [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField]
+ [DataField, AutoNetworkedField]
public SoundSpecifier? SoundDeactivate;
+ /// <summary>
+ /// The popup to show to someone activating this item.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public LocId? PopupActivate;
+
+ /// <summary>
+ /// The popup to show to someone deactivating this item.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public LocId? PopupDeactivate;
+
/// <summary>
/// The noise this item makes when it is toggled on.
/// </summary>
- [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField]
+ [DataField, AutoNetworkedField]
public SoundSpecifier? SoundFailToActivate;
}
/// 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)
+ public bool Toggle(Entity<ItemToggleComponent?> ent, EntityUid? user = null, bool predicted = true, bool showPopup = true)
{
if (!_query.Resolve(ent, ref ent.Comp, false))
return false;
- return TrySetActive(ent, !ent.Comp.Activated, user, predicted);
+ return TrySetActive(ent, !ent.Comp.Activated, user, predicted, showPopup);
}
/// <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)
+ public bool TrySetActive(Entity<ItemToggleComponent?> ent, bool active, EntityUid? user = null, bool predicted = true, bool showPopup = true)
{
if (active)
- return TryActivate(ent, user, predicted: predicted);
+ return TryActivate(ent, user, predicted: predicted, showPopup);
else
- return TryDeactivate(ent, user, predicted: predicted);
+ return TryDeactivate(ent, user, predicted: predicted, showPopup);
}
/// <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)
+ public bool TryActivate(Entity<ItemToggleComponent?> ent, EntityUid? user = null, bool predicted = true, bool showPopup = true)
{
if (!_query.Resolve(ent, ref ent.Comp, false))
return false;
else
_audio.PlayPvs(comp.SoundFailToActivate, uid);
- if (attempt.Popup != null && user != null)
+ if (showPopup && attempt.Popup != null && user != null)
{
if (predicted)
_popup.PopupClient(attempt.Popup, uid, user.Value);
return false;
}
- Activate((uid, comp), predicted, user);
+ Activate((uid, comp), predicted, user, showPopup);
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)
+ public bool TryDeactivate(Entity<ItemToggleComponent?> ent, EntityUid? user = null, bool predicted = true, bool showPopup = true)
{
if (!_query.Resolve(ent, ref ent.Comp, false))
return false;
if (attempt.Silent)
return false;
- if (attempt.Popup != null && user != null)
+ if (showPopup && attempt.Popup != null && user != null)
{
if (predicted)
_popup.PopupClient(attempt.Popup, uid, user.Value);
return false;
}
- Deactivate((uid, comp), predicted, user);
+ Deactivate((uid, comp), predicted, user, showPopup);
return true;
}
- private void Activate(Entity<ItemToggleComponent> ent, bool predicted, EntityUid? user = null)
+ private void Activate(Entity<ItemToggleComponent> ent, bool predicted, EntityUid? user = null, bool showPopup = true)
{
var (uid, comp) = ent;
var soundToPlay = comp.SoundActivate;
if (predicted)
+ {
_audio.PlayPredicted(soundToPlay, uid, user);
+ if (showPopup && ent.Comp.PopupActivate != null && user != null)
+ _popup.PopupClient(Loc.GetString(ent.Comp.PopupActivate), user.Value, user.Value);
+ }
else
+ {
_audio.PlayPvs(soundToPlay, uid);
+ if (showPopup && ent.Comp.PopupActivate != null && user != null)
+ _popup.PopupEntity(Loc.GetString(ent.Comp.PopupActivate), user.Value, user.Value);
+ }
comp.Activated = true;
UpdateVisuals((uid, comp));
/// <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)
+ private void Deactivate(Entity<ItemToggleComponent> ent, bool predicted, EntityUid? user = null, bool showPopup = true)
{
var (uid, comp) = ent;
var soundToPlay = comp.SoundDeactivate;
if (predicted)
+ {
_audio.PlayPredicted(soundToPlay, uid, user);
+ if (showPopup && ent.Comp.PopupDeactivate != null && user != null)
+ _popup.PopupClient(Loc.GetString(ent.Comp.PopupDeactivate), user.Value, user.Value);
+ }
else
+ {
_audio.PlayPvs(soundToPlay, uid);
+ if (showPopup && ent.Comp.PopupDeactivate != null && user != null)
+ _popup.PopupEntity(Loc.GetString(ent.Comp.PopupDeactivate), user.Value, user.Value);
+ }
comp.Activated = false;
UpdateVisuals((uid, comp));
namespace Content.Shared.Mousetrap;
-[RegisterComponent, NetworkedComponent]
+/// <summary>
+/// Component inteded to be used for mouse traps.
+/// Will stop step triggers from happening unless armed via <see cref="Item.ItemToggle.Components.ItemToggleComponent"/>
+/// and will scale damage taken from <see cref="Trigger.Components.Effects.DamageOnTriggerComponent"/>
+/// depending on mass.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class MousetrapComponent : Component
{
- [ViewVariables]
- [DataField("isActive")]
- public bool IsActive = false;
-
/// <summary>
- /// Set this to change where the
- /// inflection point in the scaling
- /// equation will occur.
- /// The default is 10.
+ /// Set this to change where the
+ /// inflection point in the damage scaling
+ /// equation will occur.
/// </summary>
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("massBalance")]
+ [DataField, AutoNetworkedField]
public int MassBalance = 10;
}
--- /dev/null
+using Content.Shared.Item.ItemToggle.Components;
+using Content.Shared.Trigger.Systems;
+using Content.Shared.StepTrigger.Systems;
+using Robust.Shared.Physics.Components;
+
+namespace Content.Shared.Mousetrap;
+
+public sealed class MousetrapSystem : EntitySystem
+{
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<MousetrapComponent, BeforeDamageOnTriggerEvent>(BeforeDamageOnTrigger);
+ SubscribeLocalEvent<MousetrapComponent, StepTriggerAttemptEvent>(OnStepTriggerAttempt);
+ }
+
+ // only allow step triggers to trigger if the trap is armed
+ // TODO: refactor Steptriggers to get rid of this
+ // they should just use the new trigger conditions
+ private void OnStepTriggerAttempt(Entity<MousetrapComponent> ent, ref StepTriggerAttemptEvent args)
+ {
+ if (!TryComp<ItemToggleComponent>(ent, out var toggle))
+ return;
+
+ args.Continue |= toggle.Activated;
+ }
+
+ // scale the damage according to mass
+ private void BeforeDamageOnTrigger(Entity<MousetrapComponent> ent, ref BeforeDamageOnTriggerEvent args)
+ {
+ if (TryComp(args.Tripper, out PhysicsComponent? physics) && physics.Mass != 0)
+ {
+ // The idea here is inverse,
+ // Small - big damage,
+ // Large - small damage
+ // yes i punched numbers into a calculator until the graph looked right
+ var scaledDamage = -50 * Math.Atan(physics.Mass - ent.Comp.MassBalance) + 25 * Math.PI;
+ args.Damage *= scaledDamage;
+ }
+ }
+}
+++ /dev/null
-using Robust.Shared.Serialization;
-
-namespace Content.Shared.Mousetrap;
-
-[Serializable, NetSerializable]
-public enum MousetrapVisuals : byte
-{
- Visual,
- Armed,
- Unarmed
-}
[RegisterComponent, NetworkedComponent, Access(typeof(SharedSpiderChargeSystem))]
public sealed partial class SpiderChargeComponent : Component
{
- /// Range for planting within the target area
+ /// <summary>
+ /// Range for planting within the target area.
+ /// </summary>
[DataField]
public float Range = 10f;
- /// The ninja that planted this charge
+ /// <summary>
+ /// The ninja that planted this charge.
+ /// </summary>
[DataField]
public EntityUid? Planter;
+
+ /// <summary>
+ /// The trigger that will mark the objective as successful.
+ /// </summary>
+ [DataField]
+ public string TriggerKey = "timer";
}
-using Content.Shared.Explosion.Components;
+using Content.Shared.Trigger.Components;
+using Content.Shared.Trigger.Components.Triggers;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
/// <remarks>
/// This component performs two functions. Firstly, it will add or remove other components to some entity when this
/// item is installed inside of it. This is intended for use with constructible grenades. For example, this allows
-/// you to add things like <see cref="OnUseTimerTriggerComponent"/>, or <see cref="TriggerOnProximityComponent"/>.
+/// you to add things like <see cref="TimerTriggerComponent"/>, or <see cref="TriggerOnProximityComponent"/>.
/// This is required because otherwise you would have to forward arbitrary interaction directed at the casing
/// through to the trigger, which would be quite complicated. Also proximity triggers don't really work inside of
/// containers.
/// <summary>
/// List of components to add or remove from an entity when this trigger is (un)installed.
/// </summary>
- [DataField("components", serverOnly:true, readOnly: true)]
+ [DataField(serverOnly: true, readOnly: true)]
public ComponentRegistry? Components = null;
/// <summary>
/// when removing the component, to ensure that removal of this trigger only removes the components that it was
/// responsible for adding.
/// </remarks>
- [DataField("grantedComponents", serverOnly: true)]
+ [DataField(serverOnly: true)]
public HashSet<Type> GrantedComponents = new();
}
-using Content.Shared.Damage.Components;
-using Content.Shared.Actions;
+using Content.Shared.Actions;
using Content.Shared.Actions.Components;
using Content.Shared.Alert;
using Content.Shared.Coordinates;
using Content.Shared.Movement.Systems;
using Content.Shared.Slippery;
using Content.Shared.Toggleable;
+using Content.Shared.Trigger.Components.Effects;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Events;
if (!ent.Comp.Rooted)
return;
- if (args.SlipCausingEntity != null && HasComp<DamageUserOnTriggerComponent>(args.SlipCausingEntity))
+ if (args.SlipCausingEntity != null && HasComp<DamageOnTriggerComponent>(args.SlipCausingEntity))
return;
args.NoSlip = true;
--- /dev/null
+using Content.Shared.Chat;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Speech.Components;
+
+/// <summary>
+/// This component is used to relay speech events to other systems.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+public sealed partial class ActiveListenerComponent : Component
+{
+ /// <summary>
+ /// The range in which to listen to speech.
+ /// </summary>
+ [DataField]
+ public float Range = SharedChatSystem.VoiceRange;
+}
-namespace Content.Server.Speech;
+namespace Content.Shared.Speech;
public sealed class ListenEvent : EntityEventArgs
{
using Content.Shared.Sticky.Systems;
-using Content.Shared.Whitelist;
+using Content.Shared.Whitelist;
using Robust.Shared.GameStates;
using Robust.Shared.Utility;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly MetaDataSystem _metadata = default!;
- private const string DefaultId = "default";
+ public const string DefaultId = "default";
public override void Initialize()
{
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components;
+
+/// <summary>
+/// Component used for tracking active timers triggers.
+/// Used internally for performance reasons.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+public sealed partial class ActiveTimerTriggerComponent : Component;
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components;
+
+/// <summary>
+/// Component used for tracking active two-stage triggers.
+/// Used internally for performance reasons.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+public sealed partial class ActiveTwoStageTriggerComponent : Component;
--- /dev/null
+using Content.Shared.Trigger.Systems;
+
+namespace Content.Shared.Trigger.Components.Conditions;
+
+/// <summary>
+/// Base class for components that add a condition to triggers.
+/// </summary>
+public abstract partial class BaseTriggerConditionComponent : Component
+{
+ /// <summary>
+ /// The keys that are checked for the condition.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public HashSet<string> Keys = new() { TriggerSystem.DefaultTriggerKey };
+}
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Conditions;
+
+/// <summary>
+/// Adds an alt verb that can be used to toggle a trigger.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class ToggleTriggerConditionComponent : BaseTriggerConditionComponent
+{
+ /// <summary>
+ /// Is the component currently enabled?
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool Enabled = true;
+
+ /// <summary>
+ /// The text of the toggle verb.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public LocId ToggleVerb = "toggle-trigger-condition-default-verb";
+
+ /// <summary>
+ /// The popup to show when toggled on.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public LocId ToggleOn = "toggle-trigger-condition-default-on";
+
+ /// <summary>
+ /// The popup to show when toggled off.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public LocId ToggleOff = "toggle-trigger-condition-default-off";
+}
--- /dev/null
+using Content.Shared.Timing;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Conditions;
+
+/// <summary>
+/// Checks if the triggered entity has an active UseDelay.
+/// </summary>
+/// <remarks>
+/// TODO: Support specific UseDelay IDs for each trigger key.
+/// </remarks>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class UseDelayTriggerConditionComponent : BaseTriggerConditionComponent
+{
+ /// <summary>
+ /// Checks if the triggered entity has an active UseDelay.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public string UseDelayId = UseDelaySystem.DefaultId;
+}
--- /dev/null
+using Content.Shared.Whitelist;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Conditions;
+
+/// <summary>
+/// Checks if the user of a trigger satisfies a whitelist and blacklist condition for the triggered entity or the one triggering it.
+/// Cancels the trigger otherwise.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class WhitelistTriggerConditionComponent : BaseTriggerConditionComponent
+{
+ /// <summary>
+ /// Whitelist for what entites can cause this trigger.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public EntityWhitelist? UserWhitelist;
+
+ /// <summary>
+ /// Blacklist for what entites can cause this trigger.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public EntityWhitelist? UserBlacklist;
+}
--- /dev/null
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+/// <summary>
+/// Adds the specified components when triggered.
+/// If TargetUser is true they will be added to the user.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class AddComponentsOnTriggerComponent : BaseXOnTriggerComponent
+{
+ /// <summary>
+ /// The list of components that will be added.
+ /// </summary>
+ [DataField(required: true)]
+ public ComponentRegistry Components = new();
+
+ /// <summary>
+ /// If this component has been triggered at least once already.
+ /// If this is true the components have been added.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool Triggered = false;
+
+ /// <summary>
+ /// If this effect can only be triggered once.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool TriggerOnce = false;
+
+ /// <summary>
+ /// Should components that already exist on the entity be overwritten?
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool RemoveExisting = false;
+}
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+/// <summary>
+/// Changes the alert level of the station when triggered.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class AlertLevelChangeOnTriggerComponent : BaseXOnTriggerComponent
+{
+ ///<summary>
+ /// The alert level to change to when triggered.
+ ///</summary>
+ [DataField, AutoNetworkedField]
+ public string Level = "blue";
+
+ /// <summary>
+ /// Whether to play the sound when the alert level changes.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool PlaySound = true;
+
+ /// <summary>
+ /// Whether to say the announcement when the alert level changes.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool Announce = true;
+
+ /// <summary>
+ /// Force the alert change. This applies if the alert level is not selectable or not.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool Force = false;
+}
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+/// <summary>
+/// Will (un)anchor the entity when triggered.
+/// If TargetUser is true they will be (un)anchored instead.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class AnchorOnTriggerComponent : BaseXOnTriggerComponent
+{
+ /// <summary>
+ /// Anchor the entity on trigger if it is currently unanchored?
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool CanAnchor = true;
+
+ /// <summary>
+ /// Unanchor the entity on trigger if it is currently anchored?
+ /// If both this and CanAnchor are true then the trigger will toggle between states.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool CanUnanchor = false;
+
+ /// <summary>
+ /// Removes this component when triggered so it can only be activated once.
+ /// </summary>
+ /// <remarks>
+ /// TODO: Make this a generic thing for all triggers.
+ /// Or just add a RemoveComponentsOnTriggerComponent.
+ /// </remarks>
+ [DataField, AutoNetworkedField]
+ public bool RemoveOnTrigger = true;
+}
--- /dev/null
+using Content.Shared.Trigger.Systems;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+/// <summary>
+/// Base class for components that do something when triggered.
+/// </summary>
+public abstract partial class BaseXOnTriggerComponent : Component
+{
+ /// <summary>
+ /// The keys that will activate the effect.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public HashSet<string> KeysIn = new() { TriggerSystem.DefaultTriggerKey };
+
+ /// <summary>
+ /// Set to true to make the user of the trigger the effect target.
+ /// Set to false to make the owner of this component the target.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool TargetUser = false;
+}
--- /dev/null
+using Content.Shared.Damage;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+/// <summary>
+/// Will damage an entity when triggered.
+/// If TargetUser is true it the user will take damage instead.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class DamageOnTriggerComponent : BaseXOnTriggerComponent
+{
+ /// <summary>
+ /// Should the damage ignore resistances?
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool IgnoreResistances;
+
+ /// <summary>
+ /// The base damage amount that is dealt.
+ /// May be further modified by <see cref="Systems.BeforeDamageOnTriggerEvent"/> subscriptions.
+ /// </summary>
+ [DataField(required: true), AutoNetworkedField]
+ public DamageSpecifier Damage = default!;
+}
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+/// <summary>
+/// Will delete the entity when triggered.
+/// If TargetUser is true it will delete them instead.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class DeleteOnTriggerComponent : BaseXOnTriggerComponent;
--- /dev/null
+using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+/// <summary>
+/// Will play a sound in PVS range when triggered.
+/// If TargetUser is true it will be played at their position.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class EmitSoundOnTriggerComponent : BaseXOnTriggerComponent
+{
+ /// <summary>
+ /// The <see cref="SoundSpecifier"/> to play.
+ /// </summary>
+ [DataField(required: true), AutoNetworkedField]
+ public SoundSpecifier? Sound;
+
+ /// <summary>
+ /// Play the sound at the position instead of parented to the source entity.
+ /// Useful if the entity is deleted after.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool Positional;
+
+ /// <summary>
+ /// Should this sound be predicted for the User?
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool Predicted;
+}
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+/// <summary>
+/// Will cause an EMP at the entity's location when triggered.
+/// If TargetUser is true then it will be spawned at their position.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class EmpOnTriggerComponent : BaseXOnTriggerComponent
+{
+ /// <summary>
+ /// EMP range.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public float Range = 1.0f;
+
+ /// <summary>
+ /// How much energy (in Joules) will be consumed per battery in range.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public float EnergyConsumption;
+
+ /// <summary>
+ /// How long it disables targets.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public TimeSpan DisableDuration = TimeSpan.FromSeconds(60);
+}
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+/// <summary>
+/// Will explode using the entity's <see cref="ExplosiveComponent"/> when triggered.
+/// TargetUser will only work of the user has ExplosiveComponent as well.
+/// The User will be logged in the admin logs.
+/// </summary>
+/// <summary>
+/// TODO: Allow this to work without an ExplosiveComponent on the user via QueueExplosion.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class ExplodeOnTriggerComponent : BaseXOnTriggerComponent;
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+/// <summary>
+/// Will cause a flash in an area around the entity when triggered.
+/// If TargetUser is true then their location will be used.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class FlashOnTriggerComponent : BaseXOnTriggerComponent
+{
+ /// <summary>
+ /// The range in which to flash entities in.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public float Range = 1.0f;
+
+ /// <summary>
+ /// The duration of the status effect.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public TimeSpan Duration = TimeSpan.FromSeconds(8);
+
+ /// <summary>
+ /// The probability to apply the status effect.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public float Probability = 1.0f;
+}
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+/// <summary>
+/// Will kick a player from the server as if their connection dropped if triggered.
+/// Yes, really. Don't use this component.
+/// If TargetUser is true then the user of the trigger will be kicked, otherwise the entity itself.
+/// <see cref="Server.GhostKick.GhostKickManager"/>
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class GhostKickOnTriggerComponent : BaseXOnTriggerComponent
+{
+ /// <summary>
+ /// The reason that will be displayed in the server log when a player is kicked.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public LocId Reason = "ghost-kick-on-trigger-default";
+}
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+/// <summary>
+/// Will gib the entity when triggered.
+/// If TargetUser is true the user will be gibbed instead.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class GibOnTriggerComponent : BaseXOnTriggerComponent
+{
+ /// <summary>
+ /// Should gibbing also delete the owners items?
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool DeleteItems = false;
+}
--- /dev/null
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+/// <summary>
+/// Will ignite for a certain length of time when triggered.
+/// Requires <see cref="IgnitionSourceComponent"/> along with triggering components.
+/// The if TargetUser is true they will be ignited instead (they need IgnitionSourceComponent as well).
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+[AutoGenerateComponentState, AutoGenerateComponentPause]
+public sealed partial class IgniteOnTriggerComponent : BaseXOnTriggerComponent
+{
+ /// <summary>
+ /// Once ignited, the time it will unignite at.
+ /// </summary>
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+ [AutoNetworkedField, AutoPausedField]
+ public TimeSpan IgnitedUntil = TimeSpan.Zero;
+
+ /// <summary>
+ /// How long the ignition source is active for after triggering.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public TimeSpan IgnitedTime = TimeSpan.FromSeconds(0.5);
+}
--- /dev/null
+using Content.Shared.Item.ItemToggle.Components;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+/// <summary>
+/// Will toggle an item when triggered. Requires <see cref="ItemToggleComponent"/>.
+/// If TargetUser is true and they have that component they will be toggled instead.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class ItemToggleOnTriggerComponent : BaseXOnTriggerComponent
+{
+ /// <summary>
+ /// Can the item be toggled on using the trigger?
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool CanActivate = true;
+
+ /// <summary>
+ /// Can the item be toggled on using the trigger?
+ /// If both this and CanActivate are true then the trigger will toggle between states.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool CanDeactivate = true;
+
+ /// <summary>
+ /// Can the audio and popups be predicted?
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool Predicted = true;
+
+ /// <summary>
+ /// Show a popup to the user when toggling the item?
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool ShowPopup = true;
+}
--- /dev/null
+using Content.Shared.Polymorph;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+/// <summary>
+/// Polymorphs the enity when triggered.
+/// If TargetUser is true it will polymorph the user instead.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class PolymorphOnTriggerComponent : BaseXOnTriggerComponent
+{
+ /// <summary>
+ /// Polymorph settings.
+ /// </summary>
+ [DataField(required: true)]
+ public ProtoId<PolymorphPrototype> Polymorph;
+}
--- /dev/null
+using Content.Shared.Mobs;
+using Content.Shared.Radio;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+/// <summary>
+/// Sends an emergency message over coms when triggered giving information about the entity's mob status.
+/// If TargetUser is true then the user's mob state will be used instead.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class RattleOnTriggerComponent : BaseXOnTriggerComponent
+{
+ /// <summary>
+ /// The radio channel the message will be sent to.
+ /// </summary>
+ [DataField]
+ public ProtoId<RadioChannelPrototype> RadioChannel = "Syndicate";
+
+ /// <summary>
+ /// The message to be send depending on the target's current mob state.
+ /// </summary>
+ [DataField]
+ public Dictionary<MobState, LocId> Messages = new()
+ {
+ {MobState.Critical, "deathrattle-implant-critical-message"},
+ {MobState.Dead, "deathrattle-implant-dead-message"}
+ };
+}
using Content.Shared.Atmos;
-using Content.Shared.Explosion.EntitySystems;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
-namespace Content.Shared.Explosion.Components.OnTrigger;
+namespace Content.Shared.Trigger.Components.Effects;
/// <summary>
/// Contains a GasMixture that will release its contents to the atmosphere when triggered.
/// </summary>
[RegisterComponent, NetworkedComponent]
-[AutoGenerateComponentPause]
-[Access(typeof(SharedReleaseGasOnTriggerSystem))]
-public sealed partial class ReleaseGasOnTriggerComponent : Component
+[AutoGenerateComponentState, AutoGenerateComponentPause]
+public sealed partial class ReleaseGasOnTriggerComponent : BaseXOnTriggerComponent
{
/// <summary>
/// Whether this grenade is active and releasing gas.
using Content.Shared.Physics;
using Content.Shared.Whitelist;
+using Robust.Shared.GameStates;
-namespace Content.Shared.Explosion.Components.OnTrigger;
+namespace Content.Shared.Trigger.Components.Effects;
/// <summary>
-/// Generates a gravity pulse/repulse using the RepulseAttractComponent when the entity is triggered
+/// Generates a gravity pulse/repulse using the RepulseAttractComponent around the entity when triggered.
+/// If TargetUser is true their location will be used instead.
/// </summary>
-[RegisterComponent]
-public sealed partial class SharedRepulseAttractOnTriggerComponent : Component
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class RepulseAttractOnTriggerComponent : BaseXOnTriggerComponent
{
/// <summary>
/// How fast should the Repulsion/Attraction be?
- /// A positive value will repulse objects, a negative value will attract
+ /// A positive value will repulse objects, a negative value will attract.
/// </summary>
- [DataField]
- public float Speed;
+ [DataField, AutoNetworkedField]
+ public float Speed = 5.0f;
/// <summary>
/// How close do the entities need to be?
/// </summary>
- [DataField]
- public float Range;
+ [DataField, AutoNetworkedField]
+ public float Range = 5.0f;
/// <summary>
/// What kind of entities should this effect apply to?
/// </summary>
- [DataField]
+ [DataField, AutoNetworkedField]
public EntityWhitelist? Whitelist;
/// <summary>
/// What collision layers should be excluded?
/// The default excludes ghost mobs, revenants, the AI camera etc.
/// </summary>
- [DataField]
+ [DataField, AutoNetworkedField]
public CollisionGroup CollisionMask = CollisionGroup.GhostImpassable;
}
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+/// <summary>
+/// Will electrocute the entity when triggered.
+/// If TargetUser is true it will electrocute the user instead.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class ShockOnTriggerComponent : BaseXOnTriggerComponent
+{
+ /// <summary>
+ /// Electrocute entity containing this entity instead (for example for wearable clothing).
+ /// Has priority over TargetUser.
+ /// </summary>
+ /// <remarks>
+ /// TODO: Make this more generic so it can be used for all triggers.
+ /// Maybe a BeforeTriggerEvent where we modify the target.
+ /// </remarks>
+ [DataField, AutoNetworkedField]
+ public bool TargetContainer;
+
+ /// <summary>
+ /// The force of an electric shock when the trigger is triggered.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public int Damage = 5;
+
+ /// <summary>
+ /// Duration of electric shock when the trigger is triggered.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public TimeSpan Duration = TimeSpan.FromSeconds(2);
+}
--- /dev/null
+using Content.Shared.DeviceLinking;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+/// <summary>
+/// Sends a device link signal when triggered.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class SignalOnTriggerComponent : BaseXOnTriggerComponent
+{
+ /// <summary>
+ /// The port that gets signaled when the switch turns on.
+ /// </summary>
+ [DataField]
+ public ProtoId<SourcePortPrototype> Port = "Trigger";
+}
-using Content.Shared.Explosion.EntitySystems;
using Content.Shared.Chemistry.Components;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
-namespace Content.Shared.Explosion.Components;
+namespace Content.Shared.Trigger.Components.Effects;
/// <summary>
/// Creates a smoke cloud when triggered, with an optional solution to include in it.
-/// No sound is played incase a grenade is stealthy, use <see cref="SoundOnTriggerComponent"/> if you want a sound.
+/// No sound is played incase a grenade is stealthy, use <see cref="EmitSoundOnTriggerComponent"/> if you want a sound.
+/// If TargetUser is true the smoke is spawned at their location.
/// </summary>
-[RegisterComponent, NetworkedComponent, Access(typeof(SharedSmokeOnTriggerSystem))]
-public sealed partial class SmokeOnTriggerComponent : Component
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class SmokeOnTriggerComponent : BaseXOnTriggerComponent
{
/// <summary>
- /// How long the smoke stays for, after it has spread.
+ /// How long the smoke stays for, after it has spread (in seconds).
/// </summary>
- [DataField, ViewVariables(VVAccess.ReadWrite)]
- public float Duration = 10;
+ [DataField, AutoNetworkedField]
+ public TimeSpan Duration = TimeSpan.FromSeconds(10);
/// <summary>
/// How much the smoke will spread.
/// </summary>
- [DataField(required: true), ViewVariables(VVAccess.ReadWrite)]
+ [DataField(required: true), AutoNetworkedField]
public int SpreadAmount;
/// <summary>
/// Smoke entity to spawn.
/// Defaults to smoke but you can use foam if you want.
/// </summary>
- [DataField, ViewVariables(VVAccess.ReadWrite)]
+ [DataField, AutoNetworkedField]
public EntProtoId SmokePrototype = "Smoke";
/// <summary>
/// <remarks>
/// When using repeating trigger this essentially gets multiplied so dont do anything crazy like omnizine or lexorin.
/// </remarks>
- [DataField, ViewVariables(VVAccess.ReadWrite)]
+ [DataField, AutoNetworkedField]
public Solution Solution = new();
}
--- /dev/null
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+/// <summary>
+/// Spawns a protoype when triggered.
+/// If TargetUser is true it will be spawned at their location.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class SpawnOnTriggerComponent : BaseXOnTriggerComponent
+{
+ /// <summary>
+ /// The prototype to spawn.
+ /// </summary>
+ [DataField(required: true), AutoNetworkedField]
+ public EntProtoId Proto = string.Empty;
+
+ /// <summary>
+ /// Use MapCoordinates for spawning?
+ /// Set to true if you don't want the new entity parented to the spawner.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool UseMapCoords;
+
+ /// <summary>
+ /// Whether or not to use predicted spawning.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool Predicted;
+}
--- /dev/null
+using Content.Shared.Dataset;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+/// <summary>
+/// Makes the entity speak a message when triggered.
+/// If TargetUser is true then they will be forced to speak instead.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class SpeakOnTriggerComponent : BaseXOnTriggerComponent
+{
+ /// <summary>
+ /// The text to speak. This has priority over Pack.
+ /// </summary>
+ [DataField]
+ public LocId? Text;
+
+ /// <summary>
+ /// The identifier for the dataset prototype containing messages to be spoken by this entity.
+ /// The spoken text will be picked randomly from it.
+ /// </summary>
+ [DataField]
+ public ProtoId<LocalizedDatasetPrototype>? Pack;
+}
--- /dev/null
+using Content.Shared.Timing;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Effects;
+
+/// <summary>
+/// Will activate an UseDelay on the target when triggered.
+/// </summary>
+/// <remarks>
+/// TODO: Support specific UseDelay IDs for each trigger key.
+/// </remarks>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class UseDelayOnTriggerComponent : BaseXOnTriggerComponent
+{
+ /// <summary>
+ /// The UseDelay Id to delay.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public string UseDelayId = UseDelaySystem.DefaultId;
+
+ /// <summary>
+ /// If true ongoing delays won't be reset.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool CheckDelayed;
+}
-using Content.Server.Explosion.EntitySystems;
+using Robust.Shared.GameStates;
-namespace Content.Server.Explosion.Components;
+namespace Content.Shared.Trigger.Components;
/// <summary>
-/// This is used for randomizing a <see cref="RandomTimerTriggerComponent"/> on MapInit
+/// This is used for randomizing a <see cref="TimerTriggerComponent"/> on MapInit.
/// </summary>
-[RegisterComponent, Access(typeof(TriggerSystem))]
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class RandomTimerTriggerComponent : Component
{
/// <summary>
/// The minimum random trigger time.
/// </summary>
- [DataField]
+ [DataField, AutoNetworkedField]
public float Min;
/// <summary>
/// The maximum random trigger time.
/// </summary>
- [DataField]
+ [DataField, AutoNetworkedField]
public float Max;
}
--- /dev/null
+using Content.Shared.Guidebook;
+using Content.Shared.Trigger.Systems;
+using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+using System.Linq;
+
+namespace Content.Shared.Trigger.Components;
+
+/// <summary>
+/// Starts a timer when activated by a trigger.
+/// Will cause a different trigger once the time is over.
+/// Can play a sound while the timer is active.
+/// The time can be set by other components, for example <see cref="RandomTimerTriggerComponent"/>.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause]
+public sealed partial class TimerTriggerComponent : Component
+{
+ /// <summary>
+ /// The keys that will activate the timer.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public List<string> KeysIn = new() { TriggerSystem.DefaultTriggerKey };
+
+ /// <summary>
+ /// The key that will trigger once the timer is finished.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public string? KeyOut = "timer";
+
+ /// <summary>
+ /// The time after which this timer will trigger after it is activated.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public TimeSpan Delay = TimeSpan.FromSeconds(1);
+
+ /// <summary>
+ /// If not empty, a user can use verbs to configure the delay to one of these options.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public List<TimeSpan> DelayOptions = new();
+
+ /// <summary>
+ /// The time at which this trigger will activate.
+ /// </summary>
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+ [AutoNetworkedField, AutoPausedField]
+ public TimeSpan NextTrigger = TimeSpan.Zero;
+
+ /// <summary>
+ /// Time of the next beeping sound.
+ /// </summary>
+ /// <remarks>
+ /// Not networked because it's only used server side.
+ /// </remarks>
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+ [AutoPausedField]
+ public TimeSpan NextBeep = TimeSpan.Zero;
+
+ /// <summary>
+ /// Initial beep delay.
+ /// Defaults to a single BeepInterval if null.
+ /// </summary>
+ /// <remarks>
+ /// Not networked because it's only used server side.
+ /// </remarks>
+ [DataField]
+ public TimeSpan? InitialBeepDelay;
+
+ /// <summary>
+ /// The time between beeps.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public TimeSpan BeepInterval = TimeSpan.FromSeconds(1);
+
+ /// <summary>
+ /// The entity that activated this trigger.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public EntityUid? User;
+
+ /// <summary>
+ /// The beeping sound, if any.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public SoundSpecifier? BeepSound;
+
+ /// <summary>
+ /// Whether you can examine the item to see its timer or not.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool Examinable = true;
+
+ /// <summary>
+ /// The popup to show the user when starting the timer, if any.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public LocId? Popup = "timer-trigger-activated";
+
+ #region GuidebookData
+
+ [GuidebookData]
+ public float? ShortestDelayOption => DelayOptions.Count == 0 ? null : (float)DelayOptions.Min().TotalSeconds;
+
+ [GuidebookData]
+ public float? LongestDelayOption => DelayOptions.Count == 0 ? null : (float)DelayOptions.Max().TotalSeconds;
+
+ #endregion GuidebookData
+}
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+[RegisterComponent, NetworkedComponent]
+public sealed partial class ActiveTriggerOnTimedCollideComponent : Component;
--- /dev/null
+using Content.Shared.Trigger.Systems;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+/// <summary>
+/// Base class for components that cause a trigger to be activated.
+/// </summary>
+public abstract partial class BaseTriggerOnXComponent : Component
+{
+ /// <summary>
+ /// The key that the trigger will activate.
+ /// null will activate all triggers.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public string? KeyOut = TriggerSystem.DefaultTriggerKey;
+}
-using Content.Server.Explosion.EntitySystems;
+using Robust.Shared.GameStates;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
-namespace Content.Server.Explosion.Components;
+namespace Content.Shared.Trigger.Components.Triggers;
/// <summary>
/// Constantly triggers after being added to an entity.
/// </summary>
-[RegisterComponent, Access(typeof(TriggerSystem))]
-[AutoGenerateComponentPause]
-public sealed partial class RepeatingTriggerComponent : Component
+[RegisterComponent, NetworkedComponent]
+[AutoGenerateComponentState, AutoGenerateComponentPause]
+public sealed partial class RepeatingTriggerComponent : BaseTriggerOnXComponent
{
/// <summary>
/// How long to wait between triggers.
/// The first trigger starts this long after the component is added.
/// </summary>
- [DataField]
+ [DataField, AutoNetworkedField]
public TimeSpan Delay = TimeSpan.FromSeconds(1);
/// <summary>
/// When the next trigger will be.
/// </summary>
- [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
- public TimeSpan NextTrigger;
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+ [AutoNetworkedField, AutoPausedField]
+ public TimeSpan NextTrigger = TimeSpan.Zero;
}
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+/// <summary>
+/// Triggers when activated in hand or by clicking on the entity.
+/// The user is the player activating it.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class TriggerOnActivateComponent : BaseTriggerOnXComponent
+{
+ /// <summary>
+ /// Is this interaction a complex interaction?
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool RequireComplex = true;
+}
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+/// <summary>
+/// Triggers when activating an action granted by an implant.
+/// The user is the player activating it.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class TriggerOnActivateImplantComponent : BaseTriggerOnXComponent;
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+/// <summary>
+/// Triggers when colliding with another entity.
+/// The user is the entity collided with.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class TriggerOnCollideComponent : BaseTriggerOnXComponent
+{
+ /// <summary>
+ /// The fixture with which to collide.
+ /// </summary>
+ [DataField(required: true), AutoNetworkedField]
+ public string FixtureID = string.Empty;
+
+ /// <summary>
+ /// Doesn't trigger if the other colliding fixture is nonhard.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool IgnoreOtherNonHard = true;
+}
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+/// <summary>
+/// Triggers when attempting to shoot a gun while it's empty.
+/// The user is the player holding the gun.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class TriggerOnEmptyGunshotComponent : BaseTriggerOnXComponent;
--- /dev/null
+using Content.Shared.Mobs;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+/// <summary>
+/// Triggers when this entity's mob state changes.
+/// The user is the entity that caused the state change or the owner depending on the settings.
+/// If added to an implant it will trigger when the implanted entity's mob state changes.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class TriggerOnMobstateChangeComponent : BaseTriggerOnXComponent
+{
+ /// <summary>
+ /// What states should trigger this?
+ /// </summary>
+ [DataField(required: true), AutoNetworkedField]
+ public List<MobState> MobState = new();
+
+ /// <summary>
+ /// If true, prevents suicide attempts for the trigger to prevent cheese.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool PreventSuicide = false;
+
+ /// <summary>
+ /// If false, the trigger user will be the entity that caused the mobstate to change.
+ /// If true, the trigger user will the entity that changed its mob state.
+ /// </summary>
+ /// <summary>
+ /// Set this to true for implants that apply an effect on the implanted entity.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool TargetMobstateEntity = true;
+}
--- /dev/null
+using Content.Shared.Physics;
+using Robust.Shared.GameStates;
+using Robust.Shared.Physics.Collision.Shapes;
+using Robust.Shared.Physics.Components;
+using Robust.Shared.Physics.Dynamics;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+/// <summary>
+/// Triggers whenever an entity collides with a fixture attached to the owner of this component.
+/// The user is the entity that collided with the fixture.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+[AutoGenerateComponentState, AutoGenerateComponentPause]
+public sealed partial class TriggerOnProximityComponent : BaseTriggerOnXComponent
+{
+ /// <summary>
+ /// The ID if the fixture that is observed for collisions.
+ /// </summary>
+ public const string FixtureID = "trigger-on-proximity-fixture";
+
+ /// <summary>
+ /// Currently colliding entities.
+ /// </summary>
+ [ViewVariables]
+ public readonly Dictionary<EntityUid, PhysicsComponent> Colliding = new();
+
+ /// <summary>
+ /// What is the shape of the proximity fixture?
+ /// </summary>
+ [ViewVariables]
+ [DataField]
+ public IPhysShape Shape = new PhysShapeCircle(2f);
+
+ /// <summary>
+ /// How long the the proximity trigger animation plays for.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public TimeSpan AnimationDuration = TimeSpan.FromSeconds(0.6f);
+
+ /// <summary>
+ /// Whether the entity needs to be anchored for the proximity to work.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool RequiresAnchored = true;
+
+ /// <summary>
+ /// Whether the proximity trigger is currently enabled.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool Enabled = true;
+
+ /// <summary>
+ /// The minimum delay between repeating triggers.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public TimeSpan Cooldown = TimeSpan.FromSeconds(5);
+
+ /// <summary>
+ /// When can the trigger run again?
+ /// </summary>
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+ [AutoNetworkedField, AutoPausedField]
+ public TimeSpan NextTrigger = TimeSpan.Zero;
+
+ /// <summary>
+ /// When will the visual state be updated again after activation?
+ /// </summary>
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+ [AutoNetworkedField, AutoPausedField]
+ public TimeSpan NextVisualUpdate = TimeSpan.Zero;
+
+ /// <summary>
+ /// What speed should the other object be moving at to trigger the proximity fixture?
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public float TriggerSpeed = 3.5f;
+
+ /// <summary>
+ /// If this proximity is triggered should we continually repeat it?
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool Repeating = true;
+
+ /// <summary>
+ /// What layer is the trigger fixture on?
+ /// </summary>
+ [DataField(customTypeSerializer: typeof(FlagSerializer<CollisionLayer>))]
+ public int Layer = (int)(CollisionGroup.MidImpassable | CollisionGroup.LowImpassable | CollisionGroup.HighImpassable);
+}
--- /dev/null
+using Content.Shared.DeviceLinking;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+/// <summary>
+/// Sends a trigger when signal is received.
+/// The user is the sender of the signal.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class TriggerOnSignalComponent : BaseTriggerOnXComponent
+{
+ /// <summary>
+ /// The sink port prototype we can connect devices to.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public ProtoId<SinkPortPrototype> Port = "Trigger";
+}
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+/// <summary>
+/// Triggers an entity when someone slipped on it.
+/// The user is the entity that was slipped.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class TriggerOnSlipComponent : BaseTriggerOnXComponent;
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+/// <summary>
+/// Triggers when the entity is initialized.
+/// The user is null.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class TriggerOnSpawnComponent : BaseTriggerOnXComponent;
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+/// <summary>
+/// Triggers if a StepTrigger is activated by someone stepping on this entity.
+/// The user is the mob who stepped on it.
+/// </summary>
+/// <remarks>
+/// This is used for entities that want the more generic 'trigger' behavior after a step trigger occurs.
+/// Not done by default, since it's not useful for everything and might cause weird behavior. But it is useful for a lot of stuff like mousetraps.
+/// </remarks>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class TriggerOnStepTriggerComponent : BaseTriggerOnXComponent;
--- /dev/null
+using Content.Shared.Sticky.Components;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+/// <summary>
+/// Triggers when an entity with <see cref="StickyComponent"/> is stuck to something.
+/// The user is the player doing so.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class TriggerOnStuckComponent : BaseTriggerOnXComponent;
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+/// <summary>
+/// Triggers when the entity is overlapped for the specified duration.
+/// The user is the entity that passes the time threshold while colliding.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class TriggerOnTimedCollideComponent : BaseTriggerOnXComponent
+{
+ /// <summary>
+ /// The time an entity has to collide until the trigger is activated.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public TimeSpan Threshold = TimeSpan.FromSeconds(1);
+
+ /// <summary>
+ /// A collection of entities that are currently colliding with this, and their own unique accumulator.
+ /// </summary>
+ /// <remarks>
+ /// TODO: Add AutoPausedField and (de)serialize values as time offsets when https://github.com/space-wizards/RobustToolbox/issues/3768 is fixed.
+ /// </remarks>
+ [DataField, AutoNetworkedField]
+ public Dictionary<EntityUid, TimeSpan> Colliding = new();
+}
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+/// <summary>
+/// Triggers on use in hand.
+/// The user is the player holding the item.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class TriggerOnUseComponent : BaseTriggerOnXComponent;
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+/// <summary>
+/// Starts a trigger when a verb is selected.
+/// The user is the player selecting the verb.
+/// </summary>
+/// <remarks>
+/// TODO: Support multiple verbs and trigger keys.
+/// </remarks>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class TriggerOnVerbComponent : BaseTriggerOnXComponent
+{
+ /// <summary>
+ /// The text to display in the verb.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public LocId Text = "trigger-on-verb-default";
+}
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Trigger.Components.Triggers;
+
+/// <summary>
+/// Sends a trigger when the keyphrase is heard.
+/// The User is the speaker.
+/// </summary>
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+public sealed partial class TriggerOnVoiceComponent : BaseTriggerOnXComponent
+{
+ /// <summary>
+ /// Whether or not the component is actively listening at the moment.
+ /// </summary>
+ [ViewVariables]
+ public bool IsListening => IsRecording || !string.IsNullOrWhiteSpace(KeyPhrase);
+
+ /// <summary>
+ /// The keyphrase that has been set to trigger it.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public string? KeyPhrase;
+
+ /// <summary>
+ /// Range in which we listen for the keyphrase.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public int ListenRange = 4;
+
+ /// <summary>
+ /// Whether we are currently recording a new keyphrase.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool IsRecording;
+
+ /// <summary>
+ /// Minimum keyphrase length.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public int MinLength = 3;
+
+ /// <summary>
+ /// Maximum keyphrase length.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public int MaxLength = 50;
+}
--- /dev/null
+using Content.Shared.Trigger.Systems;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Shared.Trigger.Components;
+
+/// <summary>
+/// After being triggered applies the specified components and runs triggers again.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+[AutoGenerateComponentState, AutoGenerateComponentPause]
+public sealed partial class TwoStageTriggerComponent : Component
+{
+ /// <summary>
+ /// The keys that will activate the timer and add the given components (first stage).
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public List<string> KeysIn = new() { TriggerSystem.DefaultTriggerKey };
+
+ /// <summary>
+ /// The key that will trigger once the timer is finished (second stage).
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public string? KeyOut = "stageTwo";
+
+ /// <summary>
+ /// How long it takes for the second stage to be triggered.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public TimeSpan TriggerDelay = TimeSpan.FromSeconds(10);
+
+ /// <summary>
+ /// This list of components that will be added on the first trigger.
+ /// </summary>
+ [DataField(required: true)]
+ public ComponentRegistry Components = new();
+
+ /// <summary>
+ /// The time at which the second stage will trigger.
+ /// </summary>
+ [DataField(customTypeSerializer: typeof(TimeOffsetSerializer))]
+ [AutoNetworkedField, AutoPausedField]
+ public TimeSpan? NextTriggerTime;
+
+ /// <summary>
+ /// Has this entity been triggered already?
+ /// Used to prevent the components from being added multiple times.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public bool Triggered = false;
+
+ /// <summary>
+ /// The entity that activated this trigger.
+ /// </summary>
+ [DataField, AutoNetworkedField]
+ public EntityUid? User;
+}
--- /dev/null
+using Content.Shared.Trigger.Components.Effects;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed partial class AddComponentsOnTriggerSystem : EntitySystem
+{
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<AddComponentsOnTriggerComponent, TriggerEvent>(OnTrigger);
+ }
+
+ private void OnTrigger(Entity<AddComponentsOnTriggerComponent> ent, ref TriggerEvent args)
+ {
+ if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+ return;
+
+ var target = ent.Comp.TargetUser ? args.User : ent.Owner;
+
+ if (target == null)
+ return;
+
+ if (ent.Comp.TriggerOnce && ent.Comp.Triggered)
+ return;
+
+ EntityManager.AddComponents(target.Value, ent.Comp.Components, ent.Comp.RemoveExisting);
+ ent.Comp.Triggered = true;
+ Dirty(ent);
+
+ args.Handled = true;
+ }
+}
--- /dev/null
+using Content.Shared.Damage;
+using Content.Shared.Trigger.Components.Effects;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed class DamageOnTriggerSystem : EntitySystem
+{
+ [Dependency] private readonly DamageableSystem _damageableSystem = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<DamageOnTriggerComponent, TriggerEvent>(OnTrigger);
+ }
+
+ private void OnTrigger(Entity<DamageOnTriggerComponent> ent, ref TriggerEvent args)
+ {
+ if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+ return;
+
+ var target = ent.Comp.TargetUser ? args.User : ent.Owner;
+
+ if (target == null)
+ return;
+
+ var damage = new DamageSpecifier(ent.Comp.Damage);
+ var ev = new BeforeDamageOnTriggerEvent(damage, target.Value);
+ RaiseLocalEvent(ent.Owner, ref ev);
+
+ args.Handled |= _damageableSystem.TryChangeDamage(target, ev.Damage, ent.Comp.IgnoreResistances, origin: ent.Owner) is not null;
+ }
+}
+
+/// <summary>
+/// Raised on an entity before it deals damage using DamageOnTriggerComponent.
+/// Used to modify the damage that will be dealt.
+/// </summary>
+[ByRefEvent]
+public record struct BeforeDamageOnTriggerEvent(DamageSpecifier Damage, EntityUid Tripper);
--- /dev/null
+using Content.Shared.Trigger.Components.Effects;
+using Robust.Shared.Audio.Systems;
+using Robust.Shared.Network;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed class EmitSoundOnTriggerSystem : EntitySystem
+{
+ [Dependency] private readonly INetManager _netMan = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<EmitSoundOnTriggerComponent, TriggerEvent>(OnTrigger);
+ }
+
+ private void OnTrigger(Entity<EmitSoundOnTriggerComponent> ent, ref TriggerEvent args)
+ {
+ if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+ return;
+
+ var target = ent.Comp.TargetUser ? args.User : ent.Owner;
+
+ if (target == null)
+ return;
+
+ args.Handled |= TryEmitSound(ent, target.Value, args.User);
+ }
+
+ private bool TryEmitSound(Entity<EmitSoundOnTriggerComponent> ent, EntityUid target, EntityUid? user = null)
+ {
+ if (ent.Comp.Sound == null)
+ return false;
+
+ if (ent.Comp.Positional)
+ {
+ var coords = Transform(target).Coordinates;
+ if (ent.Comp.Predicted)
+ _audio.PlayPredicted(ent.Comp.Sound, coords, user);
+ else if (_netMan.IsServer)
+ _audio.PlayPvs(ent.Comp.Sound, coords);
+ }
+ else
+ {
+ if (ent.Comp.Predicted)
+ _audio.PlayPredicted(ent.Comp.Sound, target, user);
+ else if (_netMan.IsServer)
+ _audio.PlayPvs(ent.Comp.Sound, target);
+ }
+
+ return true;
+ }
+}
--- /dev/null
+using Content.Shared.Emp;
+using Content.Shared.Trigger.Components.Effects;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed class EmpOnTriggerSystem : EntitySystem
+{
+ [Dependency] private readonly SharedEmpSystem _emp = default!;
+ [Dependency] private readonly SharedTransformSystem _transform = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<EmpOnTriggerComponent, TriggerEvent>(OnTrigger);
+ }
+
+ private void OnTrigger(Entity<EmpOnTriggerComponent> ent, ref TriggerEvent args)
+ {
+ if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+ return;
+
+ var target = ent.Comp.TargetUser ? args.User : ent.Owner;
+
+ if (target == null)
+ return;
+
+ _emp.EmpPulse(_transform.GetMapCoordinates(target.Value), ent.Comp.Range, ent.Comp.EnergyConsumption, (float)ent.Comp.DisableDuration.TotalSeconds);
+ args.Handled = true;
+ }
+}
--- /dev/null
+using Content.Shared.Explosion.EntitySystems;
+using Content.Shared.Trigger.Components.Effects;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed class ExplodeOnTriggerSystem : EntitySystem
+{
+ [Dependency] private readonly SharedExplosionSystem _explosion = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<ExplodeOnTriggerComponent, TriggerEvent>(OnTrigger);
+ }
+
+ private void OnTrigger(Entity<ExplodeOnTriggerComponent> ent, ref TriggerEvent args)
+ {
+ if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+ return;
+
+ var target = ent.Comp.TargetUser ? args.User : ent.Owner;
+
+ if (target == null)
+ return;
+
+ _explosion.TriggerExplosive(target.Value, user: args.User);
+ args.Handled = true;
+ }
+}
--- /dev/null
+using Content.Shared.Flash;
+using Content.Shared.Trigger.Components.Effects;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed class FlashOnTriggerSystem : EntitySystem
+{
+ [Dependency] private readonly SharedFlashSystem _flash = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<FlashOnTriggerComponent, TriggerEvent>(OnTrigger);
+ }
+
+ private void OnTrigger(Entity<FlashOnTriggerComponent> ent, ref TriggerEvent args)
+ {
+ if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+ return;
+
+ var target = ent.Comp.TargetUser ? args.User : ent.Owner;
+
+ if (target == null)
+ return;
+
+ _flash.FlashArea(target.Value, args.User, ent.Comp.Range, ent.Comp.Duration, probability: ent.Comp.Probability);
+ args.Handled = true;
+ }
+}
--- /dev/null
+using Content.Shared.Body.Systems;
+using Content.Shared.Inventory;
+using Content.Shared.Trigger.Components.Effects;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed class GibOnTriggerSystem : EntitySystem
+{
+ [Dependency] private readonly SharedBodySystem _body = default!;
+ [Dependency] private readonly InventorySystem _inventory = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<GibOnTriggerComponent, TriggerEvent>(OnTrigger);
+ }
+
+ private void OnTrigger(Entity<GibOnTriggerComponent> ent, ref TriggerEvent args)
+ {
+ if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+ return;
+
+ var target = ent.Comp.TargetUser ? args.User : ent.Owner;
+
+ if (target == null)
+ return;
+
+ if (ent.Comp.DeleteItems)
+ {
+ var items = _inventory.GetHandOrInventoryEntities(target.Value);
+ foreach (var item in items)
+ {
+ PredictedQueueDel(item);
+ }
+ }
+ _body.GibBody(target.Value, true);
+ args.Handled = true;
+ }
+}
--- /dev/null
+using Content.Shared.Trigger;
+using Content.Shared.Trigger.Components.Effects;
+using Content.Shared.RepulseAttract;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed class RepulseAttractOnTriggerSystem : EntitySystem
+{
+ [Dependency] private readonly RepulseAttractSystem _repulse = default!;
+ [Dependency] private readonly SharedTransformSystem _transform = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<RepulseAttractOnTriggerComponent, TriggerEvent>(OnTrigger);
+ }
+
+ private void OnTrigger(Entity<RepulseAttractOnTriggerComponent> ent, ref TriggerEvent args)
+ {
+ if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+ return;
+
+ var target = ent.Comp.TargetUser ? args.User : ent.Owner;
+
+ if (target == null)
+ return;
+
+ var position = _transform.GetMapCoordinates(target.Value);
+ _repulse.TryRepulseAttract(position, args.User, ent.Comp.Speed, ent.Comp.Range, ent.Comp.Whitelist, ent.Comp.CollisionMask);
+
+ args.Handled = true;
+ }
+}
--- /dev/null
+using Content.Shared.Trigger.Components.Effects;
+using Robust.Shared.Timing;
+
+namespace Content.Shared.Trigger.Systems;
+
+/// <summary>
+/// Releases a gas mixture to the atmosphere when triggered.
+/// Can also release gas over a set timespan to prevent trolling people
+/// with the instant-wall-of-pressure-inator.
+/// </summary>
+public abstract class SharedReleaseGasOnTriggerSystem : EntitySystem
+{
+ [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<ReleaseGasOnTriggerComponent, TriggerEvent>(OnTrigger);
+ }
+
+ /// <summary>
+ /// Shrimply sets the component to active when triggered, allowing it to release over time.
+ /// </summary>
+ private void OnTrigger(Entity<ReleaseGasOnTriggerComponent> ent, ref TriggerEvent args)
+ {
+ if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+ return;
+
+ ent.Comp.Active = true;
+ ent.Comp.NextReleaseTime = _timing.CurTime;
+ ent.Comp.StartingTotalMoles = ent.Comp.Air.TotalMoles;
+ _appearance.SetData(ent, ReleaseGasOnTriggerVisuals.Key, true);
+ }
+}
--- /dev/null
+using Content.Shared.Electrocution;
+using Content.Shared.Trigger.Components.Effects;
+using Robust.Shared.Containers;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed class ShockOnTriggerSystem : EntitySystem
+{
+ [Dependency] private readonly SharedContainerSystem _container = default!;
+ [Dependency] private readonly SharedElectrocutionSystem _electrocution = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<ShockOnTriggerComponent, TriggerEvent>(OnTrigger);
+ }
+
+ private void OnTrigger(Entity<ShockOnTriggerComponent> ent, ref TriggerEvent args)
+ {
+ if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+ return;
+
+ EntityUid? target;
+ if (ent.Comp.TargetContainer)
+ {
+ // shock whoever is wearing this clothing item
+ if (!_container.TryGetContainingContainer(ent.Owner, out var container))
+ return;
+ target = container.Owner;
+ }
+ else
+ {
+ target = ent.Comp.TargetUser ? args.User : ent.Owner;
+ }
+
+ if (target == null)
+ return;
+
+ _electrocution.TryDoElectrocution(target.Value, null, ent.Comp.Damage, ent.Comp.Duration, true, ignoreInsulation: true);
+ args.Handled = true;
+ }
+
+}
--- /dev/null
+using Content.Shared.Implants.Components;
+using Content.Shared.Trigger.Components.Triggers;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed partial class TriggerOnActivateImplantSystem : EntitySystem
+{
+ [Dependency] private readonly TriggerSystem _trigger = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<TriggerOnActivateImplantComponent, ActivateImplantEvent>(OnActivateImplant);
+ }
+
+ private void OnActivateImplant(Entity<TriggerOnActivateImplantComponent> ent, ref ActivateImplantEvent args)
+ {
+ _trigger.Trigger(ent.Owner, args.Performer, ent.Comp.KeyOut);
+ args.Handled = true;
+ }
+}
--- /dev/null
+using Content.Shared.Trigger.Components.Triggers;
+using Content.Shared.Weapons.Ranged.Events;
+
+namespace Content.Shared.Trigger.Systems;
+public sealed partial class TriggerOnEmptyGunshotSystem : EntitySystem
+{
+ [Dependency] private readonly TriggerSystem _trigger = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<TriggerOnEmptyGunshotComponent, OnEmptyGunShotEvent>(OnEmptyGunShot);
+ }
+
+ private void OnEmptyGunShot(Entity<TriggerOnEmptyGunshotComponent> ent, ref OnEmptyGunShotEvent args)
+ {
+ _trigger.Trigger(ent.Owner, args.User, ent.Comp.KeyOut);
+ }
+}
-using Content.Server.Explosion.Components;
-using Content.Shared.Explosion.Components;
-using Content.Shared.Implants;
+using Content.Shared.Implants;
using Content.Shared.Interaction.Events;
using Content.Shared.Mobs;
+using Content.Shared.Popups;
+using Content.Shared.Trigger.Components.Triggers;
-namespace Content.Server.Explosion.EntitySystems;
+namespace Content.Shared.Trigger.Systems;
-public sealed partial class TriggerSystem
+public sealed partial class TriggerOnMobstateChangeSystem : EntitySystem
{
- private void InitializeMobstate()
+ [Dependency] private readonly TriggerSystem _trigger = default!;
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
+
+ public override void Initialize()
{
+ base.Initialize();
+
SubscribeLocalEvent<TriggerOnMobstateChangeComponent, MobStateChangedEvent>(OnMobStateChanged);
SubscribeLocalEvent<TriggerOnMobstateChangeComponent, SuicideEvent>(OnSuicide);
- SubscribeLocalEvent<TriggerOnMobstateChangeComponent, ImplantRelayEvent<SuicideEvent>>(OnSuicideRelay);
SubscribeLocalEvent<TriggerOnMobstateChangeComponent, ImplantRelayEvent<MobStateChangedEvent>>(OnMobStateRelay);
+ SubscribeLocalEvent<TriggerOnMobstateChangeComponent, ImplantRelayEvent<SuicideEvent>>(OnSuicideRelay);
}
private void OnMobStateChanged(EntityUid uid, TriggerOnMobstateChangeComponent component, MobStateChangedEvent args)
if (!component.MobState.Contains(args.NewMobState))
return;
- //This chains Mobstate Changed triggers with OnUseTimerTrigger if they have it
- //Very useful for things that require a mobstate change and a timer
- if (TryComp<OnUseTimerTriggerComponent>(uid, out var timerTrigger))
- {
- HandleTimerTrigger(
- uid,
- args.Origin,
- timerTrigger.Delay,
- timerTrigger.BeepInterval,
- timerTrigger.InitialBeepDelay,
- timerTrigger.BeepSound);
- }
- else
- Trigger(uid);
+ _trigger.Trigger(uid, component.TargetMobstateEntity ? uid : args.Origin, component.KeyOut);
+ }
+
+ private void OnMobStateRelay(EntityUid uid, TriggerOnMobstateChangeComponent component, ImplantRelayEvent<MobStateChangedEvent> args)
+ {
+ if (!component.MobState.Contains(args.Event.NewMobState))
+ return;
+
+ _trigger.Trigger(uid, component.TargetMobstateEntity ? args.ImplantedEntity : args.Event.Origin, component.KeyOut);
}
/// <summary>
/// Checks if the user has any implants that prevent suicide to avoid some cheesy strategies
/// Prevents suicide by handling the event without killing the user
+ /// TODO: This doesn't seem to work at the moment as the event is never checked for being handled.
/// </summary>
private void OnSuicide(EntityUid uid, TriggerOnMobstateChangeComponent component, SuicideEvent args)
{
if (!component.PreventSuicide)
return;
- _popupSystem.PopupEntity(Loc.GetString("suicide-prevented"), args.Victim, args.Victim);
+ _popup.PopupClient(Loc.GetString("suicide-prevented"), args.Victim);
args.Handled = true;
}
private void OnSuicideRelay(EntityUid uid, TriggerOnMobstateChangeComponent component, ImplantRelayEvent<SuicideEvent> args)
{
- OnSuicide(uid, component, args.Event);
- }
+ if (args.Event.Handled)
+ return;
- private void OnMobStateRelay(EntityUid uid, TriggerOnMobstateChangeComponent component, ImplantRelayEvent<MobStateChangedEvent> args)
- {
- OnMobStateChanged(uid, component, args.Event);
+ if (!component.PreventSuicide)
+ return;
+
+ _popup.PopupClient(Loc.GetString("suicide-prevented"), args.Event.Victim);
+ args.Event.Handled = true;
}
}
--- /dev/null
+using Content.Shared.Slippery;
+using Content.Shared.Trigger.Components.Triggers;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed partial class TriggerOnSlipSystem : EntitySystem
+{
+ [Dependency] private readonly TriggerSystem _trigger = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<TriggerOnSlipComponent, SlipEvent>(OnSlip);
+ }
+
+ private void OnSlip(Entity<TriggerOnSlipComponent> ent, ref SlipEvent args)
+ {
+ _trigger.Trigger(ent.Owner, args.Slipped, ent.Comp.KeyOut);
+ }
+}
--- /dev/null
+using Content.Shared.Sticky;
+using Content.Shared.Trigger.Components.Triggers;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed class TriggerOnStuckSystem : EntitySystem
+{
+ [Dependency] private readonly TriggerSystem _trigger = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<TriggerOnStuckComponent, EntityStuckEvent>(OnStuck);
+ }
+
+ private void OnStuck(Entity<TriggerOnStuckComponent> ent, ref EntityStuckEvent args)
+ {
+ _trigger.Trigger(ent.Owner, args.User, ent.Comp.KeyOut);
+ }
+}
--- /dev/null
+using Content.Shared.Verbs;
+using Content.Shared.Trigger.Components.Triggers;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed partial class TriggerOnVerbSystem : EntitySystem
+{
+ [Dependency] private readonly TriggerSystem _trigger = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<TriggerOnVerbComponent, GetVerbsEvent<AlternativeVerb>>(OnGetAltVerbs);
+ }
+
+ private void OnGetAltVerbs(Entity<TriggerOnVerbComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
+ {
+ if (!args.CanInteract || !args.CanAccess || args.Hands == null)
+ return;
+
+ var user = args.User;
+
+ args.Verbs.Add(new AlternativeVerb
+ {
+ Text = Loc.GetString(ent.Comp.Text),
+ Act = () => _trigger.Trigger(ent.Owner, user, ent.Comp.KeyOut),
+ Priority = 2 // should be above any timer settings
+ });
+ }
+}
--- /dev/null
+using Content.Shared.Trigger.Components.Triggers;
+using Content.Shared.StepTrigger.Systems;
+using Robust.Shared.Physics.Events;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed partial class TriggerSystem
+{
+ private void InitializeCollide()
+ {
+ SubscribeLocalEvent<TriggerOnCollideComponent, StartCollideEvent>(OnCollide);
+ SubscribeLocalEvent<TriggerOnStepTriggerComponent, StepTriggeredOffEvent>(OnStepTriggered);
+
+ SubscribeLocalEvent<TriggerOnTimedCollideComponent, StartCollideEvent>(OnTimedCollide);
+ SubscribeLocalEvent<TriggerOnTimedCollideComponent, EndCollideEvent>(OnTimedEndCollide);
+ SubscribeLocalEvent<TriggerOnTimedCollideComponent, ComponentShutdown>(OnTimedShutdown);
+ }
+
+ private void OnCollide(Entity<TriggerOnCollideComponent> ent, ref StartCollideEvent args)
+ {
+ if (args.OurFixtureId == ent.Comp.FixtureID && (!ent.Comp.IgnoreOtherNonHard || args.OtherFixture.Hard))
+ Trigger(ent.Owner, args.OtherEntity, ent.Comp.KeyOut);
+ }
+
+ private void OnStepTriggered(Entity<TriggerOnStepTriggerComponent> ent, ref StepTriggeredOffEvent args)
+ {
+ Trigger(ent, args.Tripper, ent.Comp.KeyOut);
+ }
+
+ private void OnTimedCollide(Entity<TriggerOnTimedCollideComponent> ent, ref StartCollideEvent args)
+ {
+ //Ensures the trigger entity will have an active component
+ EnsureComp<ActiveTriggerOnTimedCollideComponent>(ent);
+ var otherUID = args.OtherEntity;
+ if (ent.Comp.Colliding.ContainsKey(otherUID))
+ return;
+ ent.Comp.Colliding.Add(otherUID, _timing.CurTime + ent.Comp.Threshold);
+ Dirty(ent);
+ }
+
+ private void OnTimedEndCollide(Entity<TriggerOnTimedCollideComponent> ent, ref EndCollideEvent args)
+ {
+ var otherUID = args.OtherEntity;
+ ent.Comp.Colliding.Remove(otherUID);
+ Dirty(ent);
+
+ if (ent.Comp.Colliding.Count == 0)
+ RemComp<ActiveTriggerOnTimedCollideComponent>(ent);
+ }
+
+ private void OnTimedShutdown(Entity<TriggerOnTimedCollideComponent> ent, ref ComponentShutdown args)
+ {
+ RemComp<ActiveTriggerOnTimedCollideComponent>(ent);
+ }
+
+ private void UpdateTimedCollide()
+ {
+ var curTime = _timing.CurTime;
+ var query = EntityQueryEnumerator<ActiveTriggerOnTimedCollideComponent, TriggerOnTimedCollideComponent>();
+ while (query.MoveNext(out var uid, out _, out var triggerOnTimedCollide))
+ {
+ foreach (var (collidingEntity, collidingTime) in triggerOnTimedCollide.Colliding)
+ {
+ if (curTime > collidingTime)
+ {
+ triggerOnTimedCollide.Colliding[collidingEntity] += triggerOnTimedCollide.Threshold;
+ Dirty(uid, triggerOnTimedCollide);
+ Trigger(uid, collidingEntity, triggerOnTimedCollide.KeyOut);
+ }
+ }
+ }
+ }
+}
--- /dev/null
+using Content.Shared.Trigger.Components.Conditions;
+using Content.Shared.Verbs;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed partial class TriggerSystem
+{
+ private void InitializeCondition()
+ {
+ SubscribeLocalEvent<WhitelistTriggerConditionComponent, AttemptTriggerEvent>(OnWhitelistTriggerAttempt);
+
+ SubscribeLocalEvent<UseDelayTriggerConditionComponent, AttemptTriggerEvent>(OnUseDelayTriggerAttempt);
+
+ SubscribeLocalEvent<ToggleTriggerConditionComponent, AttemptTriggerEvent>(OnToggleTriggerAttempt);
+ SubscribeLocalEvent<ToggleTriggerConditionComponent, GetVerbsEvent<AlternativeVerb>>(OnToggleGetAltVerbs);
+ }
+
+ private void OnWhitelistTriggerAttempt(Entity<WhitelistTriggerConditionComponent> ent, ref AttemptTriggerEvent args)
+ {
+ if (args.Key == null || ent.Comp.Keys.Contains(args.Key))
+ args.Cancelled |= !_whitelist.CheckBoth(args.User, ent.Comp.UserBlacklist, ent.Comp.UserWhitelist);
+ }
+
+ private void OnUseDelayTriggerAttempt(Entity<UseDelayTriggerConditionComponent> ent, ref AttemptTriggerEvent args)
+ {
+ if (args.Key == null || ent.Comp.Keys.Contains(args.Key))
+ args.Cancelled |= _useDelay.IsDelayed(ent.Owner, ent.Comp.UseDelayId);
+ }
+
+ private void OnToggleTriggerAttempt(Entity<ToggleTriggerConditionComponent> ent, ref AttemptTriggerEvent args)
+ {
+ if (args.Key == null || ent.Comp.Keys.Contains(args.Key))
+ args.Cancelled |= !ent.Comp.Enabled;
+ }
+
+ private void OnToggleGetAltVerbs(Entity<ToggleTriggerConditionComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
+ {
+ if (!args.CanInteract || !args.CanAccess || args.Hands == null)
+ return;
+
+ var user = args.User;
+
+ args.Verbs.Add(new AlternativeVerb()
+ {
+ Text = Loc.GetString(ent.Comp.ToggleVerb),
+ Act = () => Toggle(ent, user)
+ });
+ }
+
+ private void Toggle(Entity<ToggleTriggerConditionComponent> ent, EntityUid user)
+ {
+ var msg = ent.Comp.Enabled ? ent.Comp.ToggleOff : ent.Comp.ToggleOn;
+ _popup.PopupPredicted(Loc.GetString(msg), ent.Owner, user);
+ ent.Comp.Enabled = !ent.Comp.Enabled;
+ Dirty(ent);
+ }
+}
--- /dev/null
+using Content.Shared.Interaction;
+using Content.Shared.Interaction.Events;
+using Content.Shared.Item.ItemToggle.Components;
+using Content.Shared.Trigger.Components.Triggers;
+using Content.Shared.Trigger.Components.Effects;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed partial class TriggerSystem
+{
+ private void InitializeInteraction()
+ {
+ SubscribeLocalEvent<TriggerOnActivateComponent, ActivateInWorldEvent>(OnActivate);
+ SubscribeLocalEvent<TriggerOnUseComponent, UseInHandEvent>(OnUse);
+
+ SubscribeLocalEvent<ItemToggleOnTriggerComponent, TriggerEvent>(HandleItemToggleOnTrigger);
+ SubscribeLocalEvent<AnchorOnTriggerComponent, TriggerEvent>(HandleAnchorOnTrigger);
+ SubscribeLocalEvent<UseDelayOnTriggerComponent, TriggerEvent>(HandleUseDelayOnTrigger);
+ }
+
+ private void OnActivate(Entity<TriggerOnActivateComponent> ent, ref ActivateInWorldEvent args)
+ {
+ if (args.Handled)
+ return;
+
+ if (ent.Comp.RequireComplex && !args.Complex)
+ return;
+
+ Trigger(ent.Owner, args.User, ent.Comp.KeyOut);
+ args.Handled = true;
+ }
+
+ private void OnUse(Entity<TriggerOnUseComponent> ent, ref UseInHandEvent args)
+ {
+ if (args.Handled)
+ return;
+
+ Trigger(ent.Owner, args.User, ent.Comp.KeyOut);
+ args.Handled = true;
+ }
+
+ private void HandleItemToggleOnTrigger(Entity<ItemToggleOnTriggerComponent> ent, ref TriggerEvent args)
+ {
+ if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+ return;
+
+ var target = ent.Comp.TargetUser ? args.User : ent.Owner;
+
+ if (!TryComp<ItemToggleComponent>(target, out var itemToggle))
+ return;
+
+ var handled = false;
+ if (itemToggle.Activated && ent.Comp.CanDeactivate)
+ handled = _itemToggle.TryDeactivate((target.Value, itemToggle), args.User, ent.Comp.Predicted, ent.Comp.ShowPopup);
+ else if (ent.Comp.CanActivate)
+ handled = _itemToggle.TryActivate((target.Value, itemToggle), args.User, ent.Comp.Predicted, ent.Comp.ShowPopup);
+
+ args.Handled |= handled;
+ }
+
+ private void HandleAnchorOnTrigger(Entity<AnchorOnTriggerComponent> ent, ref TriggerEvent args)
+ {
+ if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+ return;
+
+ var target = ent.Comp.TargetUser ? args.User : ent.Owner;
+
+ if (target == null)
+ return;
+
+ var xform = Transform(target.Value);
+
+ if (xform.Anchored && ent.Comp.CanUnanchor)
+ _transform.Unanchor(target.Value, xform);
+ else if (ent.Comp.CanAnchor)
+ _transform.AnchorEntity(target.Value, xform);
+
+ if (ent.Comp.RemoveOnTrigger)
+ RemCompDeferred<AnchorOnTriggerComponent>(target.Value);
+
+ args.Handled = true;
+ }
+
+ private void HandleUseDelayOnTrigger(Entity<UseDelayOnTriggerComponent> ent, ref TriggerEvent args)
+ {
+ if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+ return;
+
+ var target = ent.Comp.TargetUser ? args.User : ent.Owner;
+
+ if (target == null)
+ return;
+
+ args.Handled |= _useDelay.TryResetDelay(target.Value, ent.Comp.CheckDelayed);
+ }
+}
--- /dev/null
+using Content.Shared.Trigger.Components.Triggers;
+using Robust.Shared.Physics.Components;
+using Robust.Shared.Physics.Events;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed partial class TriggerSystem
+{
+ private void InitializeProximity()
+ {
+ SubscribeLocalEvent<TriggerOnProximityComponent, StartCollideEvent>(OnProximityStartCollide);
+ SubscribeLocalEvent<TriggerOnProximityComponent, EndCollideEvent>(OnProximityEndCollide);
+ SubscribeLocalEvent<TriggerOnProximityComponent, MapInitEvent>(OnMapInit);
+ // Shouldn't need re-anchoring.
+ SubscribeLocalEvent<TriggerOnProximityComponent, AnchorStateChangedEvent>(OnProximityAnchor);
+ }
+
+ private void OnProximityAnchor(Entity<TriggerOnProximityComponent> ent, ref AnchorStateChangedEvent args)
+ {
+ ent.Comp.Enabled = !ent.Comp.RequiresAnchored || args.Anchored;
+
+ SetProximityAppearance(ent);
+
+ if (!ent.Comp.Enabled)
+ {
+ ent.Comp.Colliding.Clear();
+ }
+ // Re-check for contacts as we cleared them.
+ else if (TryComp<PhysicsComponent>(ent, out var body))
+ {
+ _physics.RegenerateContacts((ent.Owner, body));
+ }
+
+ Dirty(ent);
+ }
+
+ private void OnMapInit(Entity<TriggerOnProximityComponent> ent, ref MapInitEvent args)
+ {
+ ent.Comp.Enabled = !ent.Comp.RequiresAnchored || Transform(ent).Anchored;
+
+ SetProximityAppearance(ent);
+
+ if (!TryComp<PhysicsComponent>(ent, out var body))
+ return;
+
+ _fixture.TryCreateFixture(
+ ent.Owner,
+ ent.Comp.Shape,
+ TriggerOnProximityComponent.FixtureID,
+ hard: false,
+ body: body,
+ collisionLayer: ent.Comp.Layer);
+
+ Dirty(ent);
+ }
+
+ private void OnProximityStartCollide(EntityUid uid, TriggerOnProximityComponent component, ref StartCollideEvent args)
+ {
+ if (args.OurFixtureId != TriggerOnProximityComponent.FixtureID)
+ return;
+
+ component.Colliding[args.OtherEntity] = args.OtherBody;
+ }
+
+ private static void OnProximityEndCollide(EntityUid uid, TriggerOnProximityComponent component, ref EndCollideEvent args)
+ {
+ if (args.OurFixtureId != TriggerOnProximityComponent.FixtureID)
+ return;
+
+ component.Colliding.Remove(args.OtherEntity);
+ }
+
+ private void SetProximityAppearance(Entity<TriggerOnProximityComponent> ent)
+ {
+ _appearance.SetData(ent.Owner, ProximityTriggerVisualState.State, ent.Comp.Enabled ? ProximityTriggerVisuals.Inactive : ProximityTriggerVisuals.Off);
+ }
+
+ private void Activate(Entity<TriggerOnProximityComponent> ent, EntityUid user)
+ {
+ var curTime = _timing.CurTime;
+
+ if (!ent.Comp.Repeating)
+ {
+ ent.Comp.Enabled = false;
+ ent.Comp.Colliding.Clear();
+ }
+ else
+ {
+ ent.Comp.NextTrigger = curTime + ent.Comp.Cooldown;
+ }
+
+ // Queue a visual update for when the animation is complete.
+ ent.Comp.NextVisualUpdate = curTime + ent.Comp.AnimationDuration;
+ Dirty(ent);
+
+ _appearance.SetData(ent.Owner, ProximityTriggerVisualState.State, ProximityTriggerVisuals.Active);
+
+ Trigger(ent.Owner, user, ent.Comp.KeyOut);
+ }
+
+ private void UpdateProximity()
+ {
+ var curTime = _timing.CurTime;
+
+ var query = EntityQueryEnumerator<TriggerOnProximityComponent>();
+ while (query.MoveNext(out var uid, out var trigger))
+ {
+ if (curTime >= trigger.NextVisualUpdate)
+ {
+ // Update the visual state once the animation is done.
+ trigger.NextVisualUpdate = TimeSpan.MaxValue;
+ Dirty(uid, trigger);
+ SetProximityAppearance((uid, trigger));
+ }
+
+ if (!trigger.Enabled)
+ continue;
+
+ if (curTime < trigger.NextTrigger)
+ // The trigger's on cooldown.
+ continue;
+
+ // Check for anything colliding and moving fast enough.
+ foreach (var (collidingUid, colliding) in trigger.Colliding)
+ {
+ if (TerminatingOrDeleted(collidingUid))
+ continue;
+
+ if (colliding.LinearVelocity.Length() < trigger.TriggerSpeed)
+ continue;
+
+ // Trigger!
+ Activate((uid, trigger), collidingUid);
+ break;
+ }
+ }
+ }
+}
--- /dev/null
+using Content.Shared.Trigger.Components.Triggers;
+using Content.Shared.Trigger.Components.Effects;
+using Content.Shared.DeviceLinking.Events;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed partial class TriggerSystem
+{
+ private void InitializeSignal()
+ {
+ SubscribeLocalEvent<SignalOnTriggerComponent, ComponentInit>(SignalOnTriggerInit);
+ SubscribeLocalEvent<TriggerOnSignalComponent, ComponentInit>(TriggerOnSignalInit);
+
+ SubscribeLocalEvent<SignalOnTriggerComponent, TriggerEvent>(HandleSignalOnTrigger);
+ SubscribeLocalEvent<TriggerOnSignalComponent, SignalReceivedEvent>(OnSignalReceived);
+ }
+
+ private void SignalOnTriggerInit(Entity<SignalOnTriggerComponent> ent, ref ComponentInit args)
+ {
+ _deviceLink.EnsureSourcePorts(ent.Owner, ent.Comp.Port);
+ }
+
+ private void TriggerOnSignalInit(Entity<TriggerOnSignalComponent> ent, ref ComponentInit args)
+ {
+ _deviceLink.EnsureSinkPorts(ent.Owner, ent.Comp.Port);
+ }
+
+ private void HandleSignalOnTrigger(Entity<SignalOnTriggerComponent> ent, ref TriggerEvent args)
+ {
+ if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+ return;
+
+ _deviceLink.InvokePort(ent.Owner, ent.Comp.Port);
+ args.Handled = true;
+ }
+
+ private void OnSignalReceived(Entity<TriggerOnSignalComponent> ent, ref SignalReceivedEvent args)
+ {
+ if (args.Port != ent.Comp.Port)
+ return;
+
+ Trigger(ent.Owner, args.Trigger, ent.Comp.KeyOut);
+ }
+}
--- /dev/null
+using Content.Shared.Trigger.Components.Effects;
+using Content.Shared.Trigger.Components.Triggers;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed partial class TriggerSystem
+{
+
+ private void InitializeSpawn()
+ {
+ SubscribeLocalEvent<TriggerOnSpawnComponent, MapInitEvent>(OnSpawnInit);
+
+ SubscribeLocalEvent<SpawnOnTriggerComponent, TriggerEvent>(HandleSpawnOnTrigger);
+ SubscribeLocalEvent<DeleteOnTriggerComponent, TriggerEvent>(HandleDeleteOnTrigger);
+ }
+
+ private void OnSpawnInit(Entity<TriggerOnSpawnComponent> ent, ref MapInitEvent args)
+ {
+ Trigger(ent.Owner, null, ent.Comp.KeyOut);
+ }
+
+ private void HandleSpawnOnTrigger(Entity<SpawnOnTriggerComponent> ent, ref TriggerEvent args)
+ {
+ if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+ return;
+
+ var target = ent.Comp.TargetUser ? args.User : ent.Owner;
+
+ if (target == null)
+ return;
+
+ var xform = Transform(target.Value);
+
+ if (ent.Comp.UseMapCoords)
+ {
+ var mapCoords = _transform.GetMapCoordinates(target.Value, xform);
+ if (ent.Comp.Predicted)
+ EntityManager.PredictedSpawn(ent.Comp.Proto, mapCoords);
+ else if (_net.IsServer)
+ Spawn(ent.Comp.Proto, mapCoords);
+
+ }
+ else
+ {
+ var coords = xform.Coordinates;
+ if (!coords.IsValid(EntityManager))
+ return;
+
+ if (ent.Comp.Predicted)
+ PredictedSpawnAttachedTo(ent.Comp.Proto, coords);
+ else if (_net.IsServer)
+ SpawnAttachedTo(ent.Comp.Proto, coords);
+
+ }
+ }
+
+ private void HandleDeleteOnTrigger(Entity<DeleteOnTriggerComponent> ent, ref TriggerEvent args)
+ {
+ if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+ return;
+
+ var target = ent.Comp.TargetUser ? args.User : ent.Owner;
+
+ if (target == null)
+ return;
+
+ PredictedQueueDel(target);
+ args.Handled = true;
+ }
+}
--- /dev/null
+using Content.Shared.Trigger.Components;
+using Content.Shared.Trigger.Components.Triggers;
+using Content.Shared.Examine;
+using Content.Shared.Verbs;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed partial class TriggerSystem
+{
+ private void InitializeTimer()
+ {
+ SubscribeLocalEvent<RepeatingTriggerComponent, MapInitEvent>(OnRepeatInit);
+ SubscribeLocalEvent<RandomTimerTriggerComponent, MapInitEvent>(OnRandomInit);
+ SubscribeLocalEvent<TimerTriggerComponent, ComponentShutdown>(OnTimerShutdown);
+ SubscribeLocalEvent<TimerTriggerComponent, ExaminedEvent>(OnTimerExamined);
+ SubscribeLocalEvent<TimerTriggerComponent, TriggerEvent>(OnTimerTriggered);
+ SubscribeLocalEvent<TimerTriggerComponent, GetVerbsEvent<AlternativeVerb>>(OnTimerGetAltVerbs);
+ }
+
+ // set the time of the first trigger after being spawned
+ private void OnRepeatInit(Entity<RepeatingTriggerComponent> ent, ref MapInitEvent args)
+ {
+ ent.Comp.NextTrigger = _timing.CurTime + ent.Comp.Delay;
+ Dirty(ent);
+ }
+
+ private void OnRandomInit(Entity<RandomTimerTriggerComponent> ent, ref MapInitEvent args)
+ {
+ if (_net.IsClient) // Nextfloat will mispredict, so we set it on the server and dirty it
+ return;
+
+ if (!TryComp<TimerTriggerComponent>(ent, out var timerTriggerComp))
+ return;
+
+ timerTriggerComp.Delay = TimeSpan.FromSeconds(_random.NextFloat(ent.Comp.Min, ent.Comp.Max));
+ Dirty(ent.Owner, timerTriggerComp);
+ }
+
+ private void OnTimerShutdown(Entity<TimerTriggerComponent> ent, ref ComponentShutdown args)
+ {
+ RemComp<ActiveTimerTriggerComponent>(ent);
+ }
+
+ private void OnTimerExamined(Entity<TimerTriggerComponent> ent, ref ExaminedEvent args)
+ {
+ if (args.IsInDetailsRange && ent.Comp.Examinable)
+ args.PushText(Loc.GetString("timer-trigger-examine", ("time", ent.Comp.Delay.TotalSeconds)));
+ }
+
+ private void OnTimerTriggered(Entity<TimerTriggerComponent> ent, ref TriggerEvent args)
+ {
+ if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+ return;
+
+ args.Handled |= ActivateTimerTrigger(ent.AsNullable(), args.User);
+ }
+
+ /// <summary>
+ /// Add an alt-click interaction that cycles through delays.
+ /// </summary>
+ private void OnTimerGetAltVerbs(Entity<TimerTriggerComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
+ {
+ if (!args.CanInteract || !args.CanAccess || args.Hands == null)
+ return;
+
+ if (ent.Comp.DelayOptions == null || ent.Comp.DelayOptions.Count == 1)
+ return;
+
+ var user = args.User;
+
+ args.Verbs.Add(new AlternativeVerb
+ {
+ Category = TimerOptions,
+ Text = Loc.GetString("timer-trigger-verb-cycle"),
+ Act = () => CycleDelay(ent, user),
+ Priority = 1
+ });
+
+ foreach (var option in ent.Comp.DelayOptions)
+ {
+ if (MathHelper.CloseTo(option.TotalSeconds, ent.Comp.Delay.TotalSeconds))
+ {
+ args.Verbs.Add(new AlternativeVerb
+ {
+ Category = TimerOptions,
+ Text = Loc.GetString("timer-trigger-verb-set-current", ("time", option.TotalSeconds)),
+ Disabled = true,
+ Priority = -100 * (int)option.TotalSeconds
+ });
+ }
+ else
+ {
+ args.Verbs.Add(new AlternativeVerb
+ {
+ Category = TimerOptions,
+ Text = Loc.GetString("timer-trigger-verb-set", ("time", option.TotalSeconds)),
+ Priority = -100 * (int)option.TotalSeconds,
+ Act = () =>
+ {
+ ent.Comp.Delay = option;
+ Dirty(ent);
+ _popup.PopupClient(Loc.GetString("timer-trigger-popup-set", ("time", option.TotalSeconds)), user, user);
+ }
+ });
+ }
+ }
+ }
+
+ public static readonly VerbCategory TimerOptions = new("verb-categories-timer", "/Textures/Interface/VerbIcons/clock.svg.192dpi.png");
+
+ /// <summary>
+ /// Select the next entry from the DelayOptions.
+ /// </summary>
+ private void CycleDelay(Entity<TimerTriggerComponent> ent, EntityUid? user)
+ {
+ if (ent.Comp.DelayOptions.Count <= 1)
+ return;
+
+ // This is somewhat inefficient, but its good enough. This is run rarely, and the lists should be short.
+
+ ent.Comp.DelayOptions.Sort();
+ Dirty(ent);
+
+ if (ent.Comp.DelayOptions[^1] <= ent.Comp.Delay)
+ {
+ ent.Comp.Delay = ent.Comp.DelayOptions[0];
+ _popup.PopupClient(Loc.GetString("timer-trigger-popup-set", ("time", ent.Comp.Delay)), ent.Owner, user);
+ return;
+ }
+
+ foreach (var option in ent.Comp.DelayOptions)
+ {
+ if (option > ent.Comp.Delay)
+ {
+ ent.Comp.Delay = option;
+ _popup.PopupClient(Loc.GetString("timer-trigger-popup-set", ("time", option)), ent.Owner, user);
+ return;
+ }
+ }
+ }
+
+ private void UpdateRepeat()
+ {
+ var curTime = _timing.CurTime;
+ var query = EntityQueryEnumerator<RepeatingTriggerComponent>();
+ while (query.MoveNext(out var uid, out var comp))
+ {
+ if (comp.NextTrigger > curTime)
+ continue;
+
+ comp.NextTrigger += comp.Delay;
+ Dirty(uid, comp);
+ Trigger(uid, null, comp.KeyOut);
+ }
+ }
+
+ private void UpdateTimer()
+ {
+ var curTime = _timing.CurTime;
+ var query = EntityQueryEnumerator<ActiveTimerTriggerComponent, TimerTriggerComponent>();
+ while (query.MoveNext(out var uid, out _, out var timer))
+ {
+ if (_net.IsServer && timer.BeepSound != null && timer.NextBeep <= curTime)
+ {
+ _audio.PlayPvs(timer.BeepSound, uid);
+ timer.NextBeep += timer.BeepInterval;
+ }
+
+ if (timer.NextTrigger <= curTime)
+ {
+ Trigger(uid, timer.User, timer.KeyOut);
+ // Remove after triggering to prevent it from starting the timer again
+ RemComp<ActiveTimerTriggerComponent>(uid);
+ if (TryComp<AppearanceComponent>(uid, out var appearance))
+ _appearance.SetData(uid, TriggerVisuals.VisualState, TriggerVisualState.Unprimed, appearance);
+ }
+ }
+ }
+}
--- /dev/null
+using Content.Shared.Trigger.Components.Triggers;
+using Content.Shared.Speech;
+using Content.Shared.Speech.Components;
+using Content.Shared.Database;
+using Content.Shared.Examine;
+using Content.Shared.Verbs;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed partial class TriggerSystem
+{
+ private void InitializeVoice()
+ {
+ SubscribeLocalEvent<TriggerOnVoiceComponent, ComponentInit>(OnVoiceInit);
+ SubscribeLocalEvent<TriggerOnVoiceComponent, ExaminedEvent>(OnVoiceExamine);
+ SubscribeLocalEvent<TriggerOnVoiceComponent, ListenEvent>(OnListen);
+ SubscribeLocalEvent<TriggerOnVoiceComponent, GetVerbsEvent<AlternativeVerb>>(OnVoiceGetAltVerbs);
+ }
+
+ private void OnVoiceInit(Entity<TriggerOnVoiceComponent> ent, ref ComponentInit args)
+ {
+ if (ent.Comp.IsListening)
+ EnsureComp<ActiveListenerComponent>(ent).Range = ent.Comp.ListenRange;
+ else
+ RemCompDeferred<ActiveListenerComponent>(ent);
+ }
+
+ private void OnVoiceExamine(Entity<TriggerOnVoiceComponent> ent, ref ExaminedEvent args)
+ {
+ if (args.IsInDetailsRange)
+ {
+ args.PushText(string.IsNullOrWhiteSpace(ent.Comp.KeyPhrase)
+ ? Loc.GetString("trigger-on-voice-uninitialized")
+ : Loc.GetString("trigger-on-voice-examine", ("keyphrase", ent.Comp.KeyPhrase)));
+ }
+ }
+ private void OnListen(Entity<TriggerOnVoiceComponent> ent, ref ListenEvent args)
+ {
+ var component = ent.Comp;
+ var message = args.Message.Trim();
+
+ if (component.IsRecording)
+ {
+ var ev = new ListenAttemptEvent(args.Source);
+ RaiseLocalEvent(ent, ev);
+
+ if (ev.Cancelled)
+ return;
+
+ if (message.Length >= component.MinLength && message.Length <= component.MaxLength)
+ FinishRecording(ent, args.Source, args.Message);
+ else if (message.Length > component.MaxLength)
+ _popup.PopupEntity(Loc.GetString("trigger-on-voice-record-failed-too-long"), ent);
+ else if (message.Length < component.MinLength)
+ _popup.PopupEntity(Loc.GetString("trigger-on-voice-record-failed-too-short"), ent);
+
+ return;
+ }
+
+ if (!string.IsNullOrWhiteSpace(component.KeyPhrase) && message.IndexOf(component.KeyPhrase, StringComparison.InvariantCultureIgnoreCase) is var index and >= 0)
+ {
+ _adminLogger.Add(LogType.Trigger, LogImpact.Medium,
+ $"A voice-trigger on {ToPrettyString(ent):entity} was triggered by {ToPrettyString(args.Source):speaker} speaking the key-phrase {component.KeyPhrase}.");
+ Trigger(ent, args.Source, ent.Comp.KeyOut);
+
+ var messageWithoutPhrase = message.Remove(index, component.KeyPhrase.Length).Trim();
+ var voice = new VoiceTriggeredEvent(args.Source, message, messageWithoutPhrase);
+ RaiseLocalEvent(ent, ref voice);
+ }
+ }
+
+ private void OnVoiceGetAltVerbs(Entity<TriggerOnVoiceComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
+ {
+ if (!args.CanInteract || !args.CanAccess)
+ return;
+
+ var user = args.User;
+ args.Verbs.Add(new AlternativeVerb
+ {
+ Text = Loc.GetString(ent.Comp.IsRecording ? "trigger-on-voice-stop" : "trigger-on-voice-record"),
+ Act = () =>
+ {
+ if (ent.Comp.IsRecording)
+ StopRecording(ent, user);
+ else
+ StartRecording(ent, user);
+ },
+ Priority = 1
+ });
+
+ if (string.IsNullOrWhiteSpace(ent.Comp.KeyPhrase))
+ return;
+
+ args.Verbs.Add(new AlternativeVerb
+ {
+ Text = Loc.GetString("trigger-on-voice-clear"),
+ Act = () =>
+ {
+ ClearRecording(ent);
+ }
+ });
+ }
+
+ /// <summary>
+ /// Start recording a new keyphrase.
+ /// </summary>
+ public void StartRecording(Entity<TriggerOnVoiceComponent> ent, EntityUid? user)
+ {
+ ent.Comp.IsRecording = true;
+ Dirty(ent);
+ EnsureComp<ActiveListenerComponent>(ent).Range = ent.Comp.ListenRange;
+
+ if (user == null)
+ _adminLogger.Add(LogType.Trigger, LogImpact.Low, $"A voice-trigger on {ToPrettyString(ent):entity} has started recording.");
+ else
+ _adminLogger.Add(LogType.Trigger, LogImpact.Low, $"A voice-trigger on {ToPrettyString(ent):entity} has started recording. User: {ToPrettyString(user.Value):user}");
+
+ _popup.PopupPredicted(Loc.GetString("trigger-on-voice-start-recording"), ent, user);
+ }
+
+ /// <summary>
+ /// Stop recording without setting a keyphrase.
+ /// </summary>
+ public void StopRecording(Entity<TriggerOnVoiceComponent> ent, EntityUid? user)
+ {
+ ent.Comp.IsRecording = false;
+ Dirty(ent);
+ if (string.IsNullOrWhiteSpace(ent.Comp.KeyPhrase))
+ RemComp<ActiveListenerComponent>(ent);
+
+ _popup.PopupPredicted(Loc.GetString("trigger-on-voice-stop-recording"), ent, user);
+ }
+
+
+ /// <summary>
+ /// Stop recording and set the current keyphrase message.
+ /// </summary>
+ public void FinishRecording(Entity<TriggerOnVoiceComponent> ent, EntityUid source, string message)
+ {
+ ent.Comp.KeyPhrase = message;
+ ent.Comp.IsRecording = false;
+ Dirty(ent);
+
+ _adminLogger.Add(LogType.Trigger, LogImpact.Low,
+ $"A voice-trigger on {ToPrettyString(ent):entity} has recorded a new keyphrase: '{ent.Comp.KeyPhrase}'. Recorded from {ToPrettyString(source):speaker}");
+
+ _popup.PopupEntity(Loc.GetString("trigger-on-voice-recorded", ("keyphrase", ent.Comp.KeyPhrase)), ent);
+ }
+
+ /// <summary>
+ /// Resets the key phrase and stops recording.
+ /// </summary>
+ public void ClearRecording(Entity<TriggerOnVoiceComponent> ent)
+ {
+ ent.Comp.KeyPhrase = null;
+ ent.Comp.IsRecording = false;
+ Dirty(ent);
+ RemComp<ActiveListenerComponent>(ent);
+ }
+}
--- /dev/null
+using Content.Shared.Administration.Logs;
+using Content.Shared.Database;
+using Content.Shared.DeviceLinking;
+using Content.Shared.Item.ItemToggle;
+using Content.Shared.Popups;
+using Content.Shared.Timing;
+using Content.Shared.Trigger.Components;
+using Content.Shared.Whitelist;
+using Robust.Shared.Network;
+using Robust.Shared.Physics.Systems;
+using Robust.Shared.Timing;
+using Robust.Shared.Random;
+using Robust.Shared.Audio.Systems;
+
+
+namespace Content.Shared.Trigger.Systems;
+
+/// <summary>
+/// System containing the basic trigger API.
+/// </summary>
+/// <remarks>
+/// If you add a custom trigger subscription or effect then don't put them here.
+/// Put them into a separate system so we don't end up with a giant list of imports.
+/// </remarks>
+public sealed partial class TriggerSystem : EntitySystem
+{
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly SharedTransformSystem _transform = default!;
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
+ [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
+ [Dependency] private readonly SharedPhysicsSystem _physics = default!;
+ [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+ [Dependency] private readonly FixtureSystem _fixture = default!;
+ [Dependency] private readonly INetManager _net = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
+ [Dependency] private readonly UseDelaySystem _useDelay = default!;
+ [Dependency] private readonly EntityWhitelistSystem _whitelist = default!;
+ [Dependency] private readonly ItemToggleSystem _itemToggle = default!;
+ [Dependency] private readonly SharedDeviceLinkSystem _deviceLink = default!;
+
+ public const string DefaultTriggerKey = "trigger";
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ InitializeCollide();
+ InitializeCondition();
+ InitializeInteraction();
+ InitializeProximity();
+ InitializeSignal();
+ InitializeTimer();
+ InitializeSpawn();
+ InitializeVoice();
+ }
+
+ /// <summary>
+ /// Trigger the given entity.
+ /// </summary>
+ /// <param name="trigger">The entity that has the components that should be triggered.</param>
+ /// <param name="user">The user of the trigger. Some effects may target the user instead of the trigger entity.</param>
+ /// <param name="key">A key string to allow multiple, independent triggers on the same entity. If null then all triggers will activate.</param>
+ /// <returns>Whether or not the trigger has sucessfully activated an effect.</returns>
+ public bool Trigger(EntityUid trigger, EntityUid? user = null, string? key = null)
+ {
+ var attemptTriggerEvent = new AttemptTriggerEvent(user, key);
+ RaiseLocalEvent(trigger, ref attemptTriggerEvent);
+ if (attemptTriggerEvent.Cancelled)
+ return false;
+
+ var triggerEvent = new TriggerEvent(user, key);
+ RaiseLocalEvent(trigger, ref triggerEvent, true);
+ return triggerEvent.Handled;
+ }
+
+ /// <summary>
+ /// Activate a timer trigger on an entity with <see cref="TimerTriggerComponent"/>.
+ /// </summary>
+ /// <returns>Whether or not a timer was activated.</returns>
+ public bool ActivateTimerTrigger(Entity<TimerTriggerComponent?> ent, EntityUid? user = null)
+ {
+ if (!Resolve(ent, ref ent.Comp))
+ return false;
+
+ if (HasComp<ActiveTimerTriggerComponent>(ent))
+ return false; // already activated
+
+ if (user != null)
+ {
+ _adminLogger.Add(LogType.Trigger,
+ $"{ToPrettyString(user.Value):user} started a {ent.Comp.Delay} second timer trigger on entity {ToPrettyString(ent.Owner):timer}");
+ }
+ else
+ {
+ _adminLogger.Add(LogType.Trigger,
+ $"{ent.Comp.Delay} second timer trigger started on entity {ToPrettyString(ent.Owner):timer}");
+ }
+
+ if (ent.Comp.Popup != null)
+ _popup.PopupPredicted(Loc.GetString(ent.Comp.Popup.Value, ("device", ent.Owner)), ent.Owner, user);
+
+ AddComp<ActiveTimerTriggerComponent>(ent);
+ var curTime = _timing.CurTime;
+ ent.Comp.NextTrigger = curTime + ent.Comp.Delay;
+ var delay = ent.Comp.InitialBeepDelay ?? ent.Comp.BeepInterval;
+ ent.Comp.NextBeep = curTime + delay;
+ Dirty(ent);
+
+ var ev = new ActiveTimerTriggerEvent(user);
+ RaiseLocalEvent(ent.Owner, ref ev);
+
+ if (TryComp<AppearanceComponent>(ent, out var appearance))
+ _appearance.SetData(ent.Owner, TriggerVisuals.VisualState, TriggerVisualState.Primed, appearance);
+
+ return true;
+ }
+
+ /// <summary>
+ /// Stop a timer trigger on an entity with <see cref="TimerTriggerComponent"/>.
+ /// </summary>
+ /// <returns>Whether or not a timer was stopped.</returns>
+ public bool StopTimerTrigger(Entity<TimerTriggerComponent?> ent)
+ {
+ if (!Resolve(ent, ref ent.Comp))
+ return false;
+
+ if (!HasComp<ActiveTimerTriggerComponent>(ent))
+ return false; // the timer is not active
+
+ RemComp<ActiveTimerTriggerComponent>(ent);
+ if (TryComp<AppearanceComponent>(ent.Owner, out var appearance))
+ _appearance.SetData(ent.Owner, TriggerVisuals.VisualState, TriggerVisualState.Unprimed, appearance);
+
+ _adminLogger.Add(LogType.Trigger, $"A timer trigger was stopped before triggering on entity {ToPrettyString(ent.Owner):timer}");
+ return true;
+ }
+
+ /// <summary>
+ /// Delay an active timer trigger.
+ /// Returns false if not active.
+ /// </summary>
+ /// <param name="amount">The time to add.</param>
+ public bool TryDelay(Entity<TimerTriggerComponent?> ent, TimeSpan amount)
+ {
+ if (!Resolve(ent, ref ent.Comp, false) || !HasComp<ActiveTimerTriggerComponent>(ent))
+ return false;
+
+ ent.Comp.NextTrigger += amount;
+ Dirty(ent);
+ return true;
+ }
+
+ /// <summary>
+ /// Setter for the Delay datafield.
+ /// </summary>
+ public void SetDelay(Entity<TimerTriggerComponent?> ent, TimeSpan delay)
+ {
+ if (!Resolve(ent, ref ent.Comp))
+ return;
+
+ ent.Comp.Delay = delay;
+ Dirty(ent);
+ }
+
+ /// <summary>
+ /// Gets the remaining time until the trigger will activate.
+ /// Returns null if the trigger is not currently active.
+ /// </summary>
+ public TimeSpan? GetRemainingTime(Entity<TimerTriggerComponent?> ent)
+ {
+ if (!Resolve(ent, ref ent.Comp, false) || !HasComp<ActiveTimerTriggerComponent>(ent))
+ return null; // not a timer or not currently active
+
+ return ent.Comp.NextTrigger - _timing.CurTime;
+ }
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ UpdateTimer();
+ UpdateRepeat();
+ UpdateProximity();
+ UpdateTimedCollide();
+ }
+}
--- /dev/null
+using Robust.Shared.Timing;
+using Content.Shared.Trigger.Components;
+
+namespace Content.Shared.Trigger.Systems;
+
+public sealed class TwoStageTriggerSystem : EntitySystem
+{
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly TriggerSystem _triggerSystem = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<TwoStageTriggerComponent, TriggerEvent>(OnTrigger);
+ }
+
+ private void OnTrigger(Entity<TwoStageTriggerComponent> ent, ref TriggerEvent args)
+ {
+ if (ent.Comp.Triggered)
+ return; // already triggered
+
+ if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
+ return;
+
+ EntityManager.AddComponents(ent, ent.Comp.Components);
+ EnsureComp<ActiveTwoStageTriggerComponent>(ent);
+ ent.Comp.Triggered = true;
+ ent.Comp.NextTriggerTime = _timing.CurTime + ent.Comp.TriggerDelay;
+ ent.Comp.User = args.User;
+ Dirty(ent);
+
+ args.Handled = true;
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ var curTime = _timing.CurTime;
+ var enumerator = EntityQueryEnumerator<ActiveTwoStageTriggerComponent, TwoStageTriggerComponent>();
+ while (enumerator.MoveNext(out var uid, out _, out var component))
+ {
+ if (curTime < component.NextTriggerTime)
+ continue;
+
+ RemComp<ActiveTwoStageTriggerComponent>(uid);
+ _triggerSystem.Trigger(uid, component.User, component.KeyOut);
+ }
+ }
+}
--- /dev/null
+namespace Content.Shared.Trigger;
+
+/// <summary>
+/// Raised whenever something is Triggered on the entity.
+/// </summary>
+/// <param name="User">The entity that activated the trigger.</param>
+/// <param name="Key">
+/// Allows to have multiple independent triggers on the same entity.
+/// Setting this to null will activate all triggers.
+/// </param>
+/// <param name="Handled">Marks the event as handled if at least one trigger effect was activated.</param>
+[ByRefEvent]
+public record struct TriggerEvent(EntityUid? User = null, string? Key = null, bool Handled = false);
+
+/// <summary>
+/// Raised before a trigger is activated.
+/// Cancelling prevents it from triggering.
+/// </summary>
+/// <param name="User">The entity that activated the trigger.</param>
+/// <param name="Key">
+/// Allows to have multiple independent triggers on the same entity.
+/// Setting this to null will activate all triggers.
+/// </param>
+/// <param name="Handled">Marks the event as handled if at least one trigger effect was activated.</param>
+[ByRefEvent]
+public record struct AttemptTriggerEvent(EntityUid? User, string? Key = null, bool Cancelled = false);
+
+/// <summary>
+/// Raised when a timer trigger becomes active.
+/// </summary>
+/// <param name="User">The entity that activated the trigger.</param>
+[ByRefEvent]
+public readonly record struct ActiveTimerTriggerEvent(EntityUid? User);
using Robust.Shared.Serialization;
-namespace Content.Shared.Trigger
+namespace Content.Shared.Trigger;
+
+[Serializable, NetSerializable]
+public enum ProximityTriggerVisuals : byte
{
- [Serializable, NetSerializable]
- public enum ProximityTriggerVisuals : byte
- {
- Off,
- Inactive,
- Active,
- }
+ Off,
+ Inactive,
+ Active,
+}
- [Serializable, NetSerializable]
- public enum ProximityTriggerVisualState : byte
- {
- State,
- }
+[Serializable, NetSerializable]
+public enum ProximityTriggerVisualState : byte
+{
+ State,
+}
- [Serializable, NetSerializable]
- public enum TriggerVisuals : byte
- {
- VisualState,
- }
+[Serializable, NetSerializable]
+public enum TriggerVisuals : byte
+{
+ VisualState,
+}
- [Serializable, NetSerializable]
- public enum TriggerVisualState : byte
- {
- Primed,
- Unprimed,
- }
+[Serializable, NetSerializable]
+public enum TriggerVisualState : byte
+{
+ Primed,
+ Unprimed,
}
--- /dev/null
+namespace Content.Shared.Trigger;
+
+/// <summary>
+/// Raised when a voice trigger is activated, containing the message that triggered it.
+/// </summary>
+/// <param name="Source"> The EntityUid of the entity sending the message</param>
+/// <param name="Message"> The contents of the message</param>
+/// <param name="MessageWithoutPhrase"> The message without the phrase that triggered it.</param>
+[ByRefEvent]
+public readonly record struct VoiceTriggeredEvent(EntityUid Source, string? Message, string MessageWithoutPhrase);
/// Raised directed on the gun when trying to fire it while it's out of ammo
/// </summary>
[ByRefEvent]
-public record struct OnEmptyGunShotEvent(EntityUid EmptyGun);
+public record struct OnEmptyGunShotEvent(EntityUid User);
if (ev.Ammo.Count <= 0)
{
// triggers effects on the gun if it's empty
- var emptyGunShotEvent = new OnEmptyGunShotEvent();
+ var emptyGunShotEvent = new OnEmptyGunShotEvent(user);
RaiseLocalEvent(gunUid, ref emptyGunShotEvent);
gun.BurstActivated = false;
signal-port-name-doorbolt = Door bolt
signal-port-description-doorbolt = Bolts door when HIGH.
-signal-port-name-trigger = Trigger
-signal-port-description-trigger = Triggers some mechanism on the device.
-
-signal-port-name-timer = Timer
-signal-port-description-timer = Starts the timer countdown of the device.
+signal-port-name-trigger-receiver = Trigger
+signal-port-description-trigger-receiver = Triggers some mechanism on the device.
signal-port-name-order-sender = Order sender
signal-port-description-order-sender = Cargo console order sender
signal-port-name-middle = Middle
signal-port-description-middle = This port is invoked whenever the lever is moved to the neutral position.
-signal-port-name-timer-trigger = Timer Trigger
-signal-port-description-timer-trigger = This port is invoked whenever the timer triggers.
+signal-port-name-trigger-sender = Trigger
+signal-port-description-trigger-sender = This port is invoked whenever the device triggers.
+
+signal-port-name-timer-trigger = Timer
+signal-port-description-timer-trigger = This port is invoked whenever the timer is up.
signal-port-name-timer-start = Timer Start
signal-port-description-timer-start = This port is invoked whenever the timer starts.
--- /dev/null
+ghost-kick-on-trigger-default = Tripped over a kick mine, crashed through the fourth wall.
--- /dev/null
+
+timer-trigger-verb-set = {$time} Seconds
+timer-trigger-verb-set-current = {$time} Seconds (current)
+timer-trigger-verb-cycle = Cycle Time Delay
+
+timer-trigger-examine = The timer is set to {$time} seconds.
+
+timer-trigger-popup-set = Timer set to {$time} seconds.
+
+timer-trigger-activated = You activate {THE($device)}.
--- /dev/null
+toggle-trigger-condition-default-verb = Toggle device
+toggle-trigger-condition-default-on = Device enabled.
+toggle-trigger-condition-default-off = Device disabled.
+
+toggle-trigger-condition-stick-verb = Toggle auto-activation
+toggle-trigger-condition-stick-on = The device will now activate automatically when planted.
+toggle-trigger-condition-stick-off = The device will no longer activate automatically when planted.
--- /dev/null
+trigger-on-verb-default = Trigger
+trigger-on-verb-detonation = Start detonation
\ No newline at end of file
--- /dev/null
+trigger-on-voice-examine = The display reads: "{$keyphrase}"
+trigger-on-voice-uninitialized = The display reads: Uninitialized...
+
+trigger-on-voice-record = Record
+trigger-on-voice-stop = Stop
+trigger-on-voice-clear = Clear recording
+
+trigger-on-voice-start-recording = Started recording.
+trigger-on-voice-stop-recording = Stopped recording.
+trigger-on-voice-record-failed-too-long = Message too long, try again.
+trigger-on-voice-record-failed-too-short = Message too short, try again.
+trigger-on-voice-recorded = Recorded successfully!
+++ /dev/null
-
-verb-trigger-timer-set = {$time} Seconds
-verb-trigger-timer-set-current = {$time} Seconds (current)
-verb-trigger-timer-cycle = Cycle Time Delay
-
-examine-trigger-timer = The timer is set to {$time} seconds.
-
-popup-trigger-timer-set = Timer set to {$time} seconds.
-
-verb-start-detonation = Start detonation
-
-verb-toggle-start-on-stick = Toggle auto-activation
-popup-start-on-stick-off = The device will no longer activate automatically when planted
-popup-start-on-stick-on = The device will now activate automatically when planted
-
-trigger-activated = You activate {THE($device)}.
+++ /dev/null
-examine-trigger-voice = The display reads: "{$keyphrase}"
-trigger-voice-uninitialized = The display reads: Uninitialized...
-
-verb-trigger-voice-record = Record
-verb-trigger-voice-stop = Stop
-verb-trigger-voice-clear = Clear recording
-
-popup-trigger-voice-start-recording = Started recording
-popup-trigger-voice-stop-recording = Stopped recording
-popup-trigger-voice-record-failed-too-long = Message too long, try again
-popup-trigger-voice-record-failed-too-short = Message too short, try again
-popup-trigger-voice-recorded = Recorded successfully
intensity: 50
type: SingularityDistortion
- type: ExplodeOnTrigger
+ keysIn:
+ - stageTwo
triggerDelay: 500
- proto: GravityGeneratorMini
entities:
[bold]Crew of the NT-Quark[/bold]
- [bold]Nuclear containment breach detected on long range
- halcyon scanners. Seek immediate shelter. Avoid
+ [bold]Nuclear containment breach detected on long range
+ halcyon scanners. Seek immediate shelter. Avoid
contaminated materials. Do not panic.[/bold]
[color=red][bold]Alpha decay rates indicate estimated
- type: sinkPort
id: Trigger
- name: signal-port-name-trigger
- description: signal-port-description-trigger
+ name: signal-port-name-trigger-receiver
+ description: signal-port-description-trigger-receiver
- type: sinkPort
id: Timer
name: signal-port-name-power-discharging
description: signal-port-description-power-discharging
+- type: sourcePort
+ id: Trigger
+ name: signal-port-name-trigger-sender
+ description: signal-port-description-trigger-sender
+ defaultLinks: [ AutoClose, On, Open, Forward, Trigger, Timer ]
+
- type: sourcePort
id: ItemDetected
name: signal-port-name-item-detected
equipDelay: 5 # to avoid accidentally falling into the trap associated with SelfUnremovableClothing
- type: SelfUnremovableClothing
- type: ShockOnTrigger
+ targetContainer: true
damage: 5
duration: 3
- cooldown: 4
- type: TriggerOnSignal
- type: DeviceLinkSink
ports:
sprite: /Textures/Objects/Fun/goldbikehorn.rsi
visible: false
state: icon
- - type: TriggerOnSpawn
+ - type: TriggerOnSpawn
- type: TimedDespawn
lifetime: 5
suffix: BluespaceFlash
parent: AdminInstantEffectBase
components:
- - type: SpawnOnTrigger
+ - type: SpawnOnTrigger
proto: EffectFlashBluespace
- type: entity
components:
- type: FlashOnTrigger
range: 7
- - type: SpawnOnTrigger
+ - type: SpawnOnTrigger
proto: GrenadeFlashEffect
-
+
- type: entity
id: AdminInstantEffectSmoke3
suffix: Smoke (03 sec)
- type: SmokeOnTrigger
duration: 3
spreadAmount: 1
- - type: SoundOnTrigger
+ - type: EmitSoundOnTrigger
sound: /Audio/Effects/smoke.ogg
- type: TimerTriggerVisuals
primingSound:
path: /Audio/Effects/Smoke-grenade.ogg
-
+
- type: entity
id: AdminInstantEffectSmoke10
suffix: Smoke (10 sec)
- type: SmokeOnTrigger
duration: 10
spreadAmount: 30
- - type: SoundOnTrigger
+ - type: EmitSoundOnTrigger
sound: /Audio/Effects/smoke.ogg
- type: TimerTriggerVisuals
primingSound:
path: /Audio/Effects/Smoke-grenade.ogg
-
+
- type: entity
id: AdminInstantEffectSmoke30
suffix: Smoke (30 sec)
- type: SmokeOnTrigger
duration: 30
spreadAmount: 50
- - type: SoundOnTrigger
+ - type: EmitSoundOnTrigger
sound: /Audio/Effects/smoke.ogg
- type: TimerTriggerVisuals
primingSound:
id: AdminInstantEffectGravityWell
suffix: Gravity Well
parent: AdminInstantEffectBase
- components:
- - type: SoundOnTrigger
- removeOnTrigger: true
+ components:
+ - type: EmitSoundOnTrigger
sound:
path: /Audio/Effects/Grenades/Supermatter/supermatter_start.ogg
- volume: 5
+ params:
+ volume: 5
- type: AmbientSound
enabled: true
volume: -5
- type: SingularityDistortion
intensity: 10
falloffPower: 1.5
-
+
receiveFrequencyId: CyborgControl
transmitFrequencyId: RoboticsConsole
savableAddress: false
- - type: OnUseTimerTrigger
+ - type: TimerTrigger
delay: 10
examinable: false
beepSound:
params:
volume: -4
# prevent any funnies if someone makes a cyborg item...
- - type: AutomatedTimer
- type: ExplodeOnTrigger
+ keysIn:
+ - timer
# explosion does most of its damage in the center and less at the edges
- type: Explosive
explosionType: Minibomb
- type: Item
size: Normal
sprite: Mobs/Animals/grenadepenguin.rsi
- - type: OnUseTimerTrigger
+ - type: TriggerOnUse
+ - type: TimerTrigger
delay: 10
beepSound:
path: /Audio/Weapons/Guns/MagOut/pistol_magout.ogg #funny sfx use
intensitySlope: 20
totalIntensity: 225
- type: ExplodeOnTrigger
+ keysIn:
+ - timer
- type: Destructible
thresholds:
- trigger:
critThreshold: 120
- type: Destructible
thresholds:
- - trigger:
- !type:DamageTrigger
- damage: 100
- behaviors:
- - !type:TriggerBehavior
- trigger:
!type:DamageTrigger
damage: 120
payloadSlot:
whitelist:
components:
- - OnUseTimerTrigger
+ - TimerTrigger
insertSound:
path: /Audio/Weapons/Guns/Empty/empty.ogg
ejectSound:
- type: IgnitionSource
temperature: 800
- type: IgniteOnTrigger
+ - type: EmitSoundOnTrigger
+ sound:
+ collection: WelderOn
+ - type: UseDelayOnTrigger
+ - type: UseDelayTriggerCondition
- type: TriggerOnSignal
- type: DeviceNetwork
deviceNetId: Wireless
sprite: Objects/Devices/signaller.rsi
state: signaller
- type: Signaller
+ - type: SignalOnTrigger
+ port: Pressed
+ - type: UseDelayOnTrigger
+ - type: UseDelayTriggerCondition
- type: UseDelay
- type: StaticPrice
price: 40
- type: WirelessNetworkConnection
range: 50
- type: StaticPrice
- price: 30
\ No newline at end of file
+ price: 30
price: 40
- type: PayloadTrigger
components:
- - type: OnUseTimerTrigger
+ - type: TriggerOnUse
+ keyOut: startTimer
+ - type: TimerTrigger
+ keysIn:
+ - startTimer
+ # use the default trigger key so it can activate any inserted payload
+ keyOut: trigger
delay: 5
delayOptions: [3, 5, 10, 15, 30]
initialBeepDelay: 0
- SignalTrigger
- type: PayloadTrigger
components:
- - type: TimerStartOnSignal
- type: DeviceNetwork
deviceNetId: Wireless
receiveFrequencyId: BasicDevice
- type: WirelessNetworkConnection
range: 200
- type: DeviceLinkSink
- - type: OnUseTimerTrigger
+ - type: TriggerOnSignal
+ keyOut: startTimer
+ - type: TimerTrigger
+ keysIn:
+ - startTimer
+ # use the default trigger key so it can activate any inserted payload
+ keyOut: trigger
delay: 3
initialBeepDelay: 0
beepSound:
state: icon
- type: TriggerOnUse
- type: PolymorphOnTrigger
+ targetUser: true
polymorph: VoidPocket
- type: UseDelay
delay: 220 # long delay to ensure it can't be spammed, use it wisely
sprite: Objects/Devices/mousetrap.rsi
drawdepth: SmallMobs # if mice can hide under tables, so can mousetraps
layers:
- - state: mousetrap
- map: ["base"]
+ - state: mousetrap
+ map: ["enum.ToggleableVisuals.Layer"]
- type: StepTrigger
intersectRatio: 0.2
requiredTriggeredSpeed: 2
- type: Mousetrap
+ - type: ItemToggle
+ soundActivate: "/Audio/Items/Handcuffs/cuff_end.ogg"
+ soundDeactivate: "/Audio/Items/snap.ogg"
+ popupActivate: mousetrap-on-activate
+ popupDeactivate: mousetrap-on-deactivate
+ - type: UseDelay
+ - type: ItemToggleOnTrigger
+ canActivate: false
+ showPopup: false # only show the popup when arming/disarming the trap in your hand
- type: TriggerOnStepTrigger
- type: PreventableStepTrigger
- - type: DamageUserOnTrigger
+ - type: DamageOnTrigger
+ targetUser: true
damage:
types:
Blunt: 2 # base damage, scales based on mass
- - type: EmitSoundOnUse
- sound: "/Audio/Items/Handcuffs/cuff_end.ogg"
- handle: false
- - type: EmitSoundOnTrigger
- sound: "/Audio/Items/snap.ogg"
- type: Item
sprite: Objects/Devices/mousetrap.rsi
- type: Appearance
- type: GenericVisualizer
visuals:
- enum.MousetrapVisuals.Visual:
- base:
- Armed: { state: mousetraparmed }
- Unarmed: { state: mousetrap }
+ enum.ToggleableVisuals.Enabled:
+ enum.ToggleableVisuals.Layer:
+ True: {state: mousetraparmed}
+ False: {state: mousetrap}
- type: Physics
bodyType: Dynamic
- type: CollisionWake
- type: Sprite
layers:
- state: mousetraparmed
- map: ["base"]
- - type: Mousetrap
- isActive: true
+ map: ["enum.ToggleableVisuals.Layer"]
+ - type: ItemToggle
+ activated: true
- type: EmitSoundOnTrigger
sound:
path: "/Audio/Effects/flash_bang.ogg"
+ positional: true
- MobMover
- type: SpawnOnTrigger
proto: EffectGravityPulse
+ predicted: true
path: /Audio/Effects/glass_step.ogg
slipData:
launchForwardsMultiplier: 0
- - type: DamageUserOnTrigger
+ - type: DamageOnTrigger
+ targetUser: true
damage:
types:
Piercing: 5
- Figurine
- type: UseDelay
delay: 5
+ - type: UseDelayOnTrigger
+ - type: UseDelayTriggerCondition # prevent spam
- type: TriggerOnActivate
- type: TriggerOnSignal
- type: Speech
- type: StepTrigger
intersectRatio: 0.2
requiredTriggeredSpeed: 2
- - type: TriggerOnStepTrigger
+ - type: TriggerOnStepTrigger # for payloads
- type: Appearance
- type: CollisionWake
enabled: false
slipData:
launchForwardsMultiplier: 0
- type: TriggerOnStepTrigger
- - type: DamageUserOnTrigger
+ - type: DamageOnTrigger
+ targetUser: true
damage:
types:
Piercing: 5
- type: ToolRefinable
refineResult:
- id: SheetGlass1
- - type: DamageUserOnTrigger
+ - type: DamageOnTrigger
damage:
types:
Piercing: 4.5
refineResult:
- id: SheetGlass1
- id: PartRodMetal1
- - type: DamageUserOnTrigger
+ - type: DamageOnTrigger
damage:
types:
Piercing: 5.5
refineResult:
- id: SheetGlass1
- id: SheetPlasma1
- - type: DamageUserOnTrigger
+ - type: DamageOnTrigger
damage:
types:
Piercing: 6.5
refineResult:
- id: SheetGlass1
- id: SheetUranium1
- - type: DamageUserOnTrigger
+ - type: DamageOnTrigger
damage:
types:
Piercing: 5
refineResult:
- id: SheetGlass1
- id: SheetBrass1
- - type: DamageUserOnTrigger
+ - type: DamageOnTrigger
damage:
types:
Piercing: 5
name: kick mine
parent: BaseLandMine
components:
- - type: GhostKickUserOnTrigger
+ - type: GhostKickOnTrigger
+ targetUser: true
- type: DeleteOnTrigger
- type: entity
description: This implant lets the user honk anywhere at any time.
categories: [ HideSpawnMenu ]
components:
- - type: SubdermalImplant
- implantAction: ActionActivateHonkImplant
- - type: TriggerImplantAction
- - type: EmitSoundOnTrigger
- sound:
- collection: BikeHorn
- params:
- variation: 0.125
- - type: Tag
- tags:
- - BikeHorn
+ - type: SubdermalImplant
+ implantAction: ActionActivateHonkImplant
+ - type: TriggerOnActivateImplant
+ - type: EmitSoundOnTrigger
+ predicted: true
+ sound:
+ collection: BikeHorn
+ params:
+ variation: 0.125
+ - type: Tag
+ tags:
+ - BikeHorn
#Security implants
description: This implant creates an electromagnetic pulse when activated.
categories: [ HideSpawnMenu ]
components:
- - type: SubdermalImplant
- implantAction: ActionActivateEmpImplant
- - type: TriggerImplantAction
- - type: EmpOnTrigger
- range: 2.75
- energyConsumption: 50000
- disableDuration: 10
+ - type: SubdermalImplant
+ implantAction: ActionActivateEmpImplant
+ - type: TriggerOnActivateImplant
+ - type: EmpOnTrigger
+ range: 2.75
+ energyConsumption: 50000
+ disableDuration: 10
- type: entity
parent: BaseSubdermalImplant
description: This implant randomly teleports the user within a large radius when activated.
categories: [ HideSpawnMenu ]
components:
- - type: SubdermalImplant
- implantAction: ActionActivateScramImplant
- - type: TriggerImplantAction
- - type: ScramImplant
+ - type: SubdermalImplant
+ implantAction: ActionActivateScramImplant
+ - type: TriggerOnActivateImplant
+ - type: ScramImplant
- type: entity
parent: BaseSubdermalImplant
description: This implant detonates the user upon activation or upon death.
categories: [ HideSpawnMenu ]
components:
- - type: SubdermalImplant
- permanent: true
- implantAction: ActionActivateMicroBomb
- - type: TriggerOnMobstateChange
- mobState:
- - Dead
- - type: TriggerImplantAction
- - type: ExplodeOnTrigger
- - type: GibOnTrigger
- deleteItems: true
- - type: Explosive
- explosionType: MicroBomb
- totalIntensity: 120
- intensitySlope: 5
- maxIntensity: 30
- canCreateVacuum: false
- - type: Tag
- tags:
- - SubdermalImplant
- - HideContextMenu
- - MicroBomb
+ - type: SubdermalImplant
+ permanent: true
+ implantAction: ActionActivateMicroBomb
+ - type: TriggerOnMobstateChange
+ mobState:
+ - Dead
+ - type: TriggerOnActivateImplant
+ - type: ExplodeOnTrigger
+ - type: GibOnTrigger
+ targetUser: true
+ deleteItems: true
+ - type: Explosive
+ explosionType: MicroBomb
+ totalIntensity: 120
+ intensitySlope: 5
+ maxIntensity: 30
+ canCreateVacuum: false
+ - type: Tag
+ tags:
+ - SubdermalImplant
+ - HideContextMenu
+ - MicroBomb
- type: entity
components:
- type: SubdermalImplant
permanent: true
- - type: TriggerOnMobstateChange #Chains with OnUseTimerTrigger
+ - type: TriggerOnMobstateChange #activates the timer
mobState:
- Dead
preventSuicide: true
- - type: OnUseTimerTrigger
+ - type: TimerTrigger
delay: 7
- initialBeepDelay: 0
beepSound:
path: /Audio/Machines/Nuke/general_beep.ogg
params:
volume: -2
- type: ExplodeOnTrigger
+ keysIn:
+ - timer
- type: GibOnTrigger
+ targetUser: true
deleteItems: true
+ keysIn:
+ - timer
- type: Explosive
explosionType: Default
totalIntensity: 3500
- type: TriggerOnMobstateChange
mobState:
- Dead
- - type: TriggerImplantAction
+ - type: TriggerOnActivateImplant
- type: GibOnTrigger
+ targetUser: true
deleteItems: true
- type: SpawnOnTrigger
proto: Acidifier
+ predicted: true
- type: Tag
tags:
- SubdermalImplant
mobState:
- Critical
- Dead
- - type: Rattle
+ - type: RattleOnTrigger
+ targetUser: true
- type: entity
parent: BaseSubdermalImplant
description: This implant will inform the Centcomm radio channel should the user fall into critical condition or die.
categories: [ HideSpawnMenu ]
components:
- - type: Rattle
+ - type: RattleOnTrigger
radioChannel: CentCom
path: /Audio/Effects/beep_landmine.ogg
params:
maxDistance: 10
- - type: ExplodeOnTrigger
- type: Explosive
explosionType: HardBomb # normally Default and max 5 total 60
maxIntensity: 10 # about a ~67.5 total damage
totalIntensity: 30 # about a ~3 tile radius
canCreateVacuum: false
+ - type: ExplodeOnTrigger
+ keysIn:
+ - timer
+ - trigger # directly explode from the landmine
- type: DeleteOnTrigger
- - type: OnUseTimerTrigger
- useVerbInstead: true
+ keysIn:
+ - timer
+ - trigger
+ - type: TimerTrigger
beepInterval: .25
beepSound:
path: /Audio/Items/Janitor/floor_sign_beep.ogg
params:
volume: 1
examinable: false
+ - type: TriggerOnVerb
+ text: trigger-on-verb-detonation
- type: Tag
tags: # ignore "WhitelistChameleon" tag
- - WetFloorSign
+ - WetFloorSign
- type: entity
name: plunger
- type: Slippery
- type: StepTrigger
intersectRatio: 0.04
+ - type: TriggerOnStepTrigger
- type: Item
heldPrefix: syndie
- type: Fixtures
- ItemMask
- type: DeleteOnTrigger
- type: EmitSoundOnTrigger
+ predicted: true
+ positional: true
sound:
path: "/Audio/Effects/Fluids/splat.ogg"
params:
- volume: -20
+ volume: -8
- type: entity
name: soap
- state: wires
- type: Item
sprite: Objects/Weapons/Bombs/ied.rsi
- - type: OnUseTimerTrigger
+ - type: TriggerOnUse
+ - type: TimerTrigger
delay: 5
examinable: false
initialBeepDelay: 0
maxIntensity: 3
canCreateVacuum: false
- type: ExplodeOnTrigger
+ keysIn:
+ - timer
- type: Appearance
- type: AnimationPlayer
- type: TimerTriggerVisuals
damage:
types:
Blunt: 5
- - type: OnUseTimerTrigger
+ - type: TriggerOnUse
+ - type: TimerTrigger
delay: 120
beepSound:
path: /Audio/Machines/Nuke/general_beep.ogg
params:
volume: -2
- type: ExplodeOnTrigger
+ keysIn:
+ - timer
- type: Explosive
explosionType: Default
maxIntensity: 50
totalIntensity: 100
canCreateVacuum: false
- type: DeleteOnTrigger
+ keysIn:
+ - timer
- type: HotPotato
- type: DamageOnHolding
enabled: false
enum.Trigger.TriggerVisuals.VisualState:
base:
Primed: { state: activated }
- Unprimed: { state: complete }
+ Unprimed: { state: icon }
- type: entity
id: HotPotatoEffect
description: A dark ink pen.
id: PenExploding
components:
- - type: OnUseTimerTrigger
+ - type: TimerTrigger
delay: 4
examinable: false
- type: Explosive
canCreateVacuum: false
- type: ActivateOnPaperOpened
- type: ExplodeOnTrigger
+ keysIn:
+ - timer
+ - type: TriggerOnUse
- type: TriggerOnSignal
- - type: DeviceLinkSink # This should be changed into separate behavior where triggering via signal makes beep, while triggering manually is quiet, when that functionality is supported.
+ - type: DeviceLinkSink # This should be changed into separate behavior where triggering via signal makes beep, while triggering manually is quiet.
ports:
- Trigger
- type: EmitSoundOnUse
- state: base
map: ["enum.TriggerVisualLayers.Base"]
- state: wires
- - type: OnUseTimerTrigger # todo: make it activate through welder/lighter/fire instead
+ - type: TriggerOnActivate # todo: make it activate through welder/lighter/fire instead
+ - type: TimerTrigger
delay: 5
examinable: false
initialBeepDelay: 0
min: 1
max: 10
- type: ExplodeOnTrigger
+ keysIn:
+ - timer
- type: Explosive # Weak explosion in a very small radius. Doesn't break underplating.
explosionType: Default
totalIntensity: 50
enum.Trigger.TriggerVisuals.VisualState:
base:
Primed: { state: primed }
- Unprimed: { state: complete }
+ Unprimed: { state: icon }
- type: entity
name: composition C-4
quickEquip: false
equipDelay: 3
unequipDelay: 6
- - type: OnUseTimerTrigger
+ - type: TriggerOnActivate
+ - type: TriggerOnSignal
+ - type: TriggerOnStuck
+ keyOut: stuck
+ - type: ToggleTriggerCondition # for toggling the start on stuck ability
+ keys:
+ - stuck
+ toggleVerb: toggle-trigger-condition-stick-verb
+ toggleOn: toggle-trigger-condition-stick-on
+ toggleOff: toggle-trigger-condition-stick-off
+ - type: TimerTrigger
+ keysIn:
+ - trigger
+ - stuck
delay: 10
delayOptions: [10, 30, 60, 120, 300]
initialBeepDelay: 0
beepSound: /Audio/Machines/Nuke/general_beep.ogg
- startOnStick: true
- canToggleStartOnStick: true
- - type: TimerStartOnSignal
- type: DeviceLinkSink
ports:
- - Timer
+ - Trigger
- type: Explosive # Powerful explosion in a very small radius. Doesn't break underplating.
explosionType: DemolitionCharge
totalIntensity: 60
maxIntensity: 30
canCreateVacuum: false
- type: ExplodeOnTrigger
+ keysIn:
+ - timer
- type: HolidayVisuals
holidays:
festive:
layers:
- state: icon
map: ["base"]
- - type: OnUseTimerTrigger
+ - type: TriggerOnActivate
+ - type: TriggerOnSignal
+ - type: TriggerOnStuck
+ keyOut: stuck
+ - type: ToggleTriggerCondition # for toggling the start on stuck ability
+ keys:
+ - stuck
+ toggleVerb: toggle-trigger-condition-stick-verb
+ toggleOn: toggle-trigger-condition-stick-on
+ toggleOff: toggle-trigger-condition-stick-off
+ - type: TimerTrigger
+ keysIn:
+ - trigger
+ - stuck
delay: 5
delayOptions: [5, 10, 15, 20]
initialBeepDelay: 0
path: /Audio/Effects/Cargo/buzz_two.ogg
params:
volume: -6
- startOnStick: false
- canToggleStartOnStick: true
- - type: TimerStartOnSignal
- type: DeviceLinkSink
ports:
- - Timer
+ - Trigger
- type: Explosive
explosionType: Cryo
totalIntensity: 120
maxIntensity: 30
canCreateVacuum: false
- type: ExplodeOnTrigger
+ keysIn:
+ - timer
sprite: Objects/Weapons/Bombs/spidercharge.rsi
size: Small
- type: SpiderCharge
- - type: OnUseTimerTrigger
+ - type: TriggerOnStuck
+ - type: TimerTrigger
delay: 10
delayOptions: [5, 10, 30, 60]
initialBeepDelay: 0
beepSound: /Audio/Machines/Nuke/general_beep.ogg
- startOnStick: true
- - type: AutomatedTimer
- type: Sticky
stickDelay: 5
stickPopupStart: comp-sticky-start-stick-bomb
maxIntensity: 120
canCreateVacuum: true
- type: ExplodeOnTrigger
+ keysIn:
+ - timer
- type: StickyVisualizer
- type: Appearance
- type: GenericVisualizer
enum.Trigger.TriggerVisuals.VisualState:
base:
Primed: { state: primed }
- Unprimed: { state: complete }
+ Unprimed: { state: icon }
- type: entity
id: ProjectilePolyboltBase
+ abstract: true
parent: BaseBullet
categories: [ HideSpawnMenu ]
components:
Poison: 5
- type: TriggerOnCollide
fixtureID: projectile
+ - type: PolymorphOnTrigger
+ targetUser: true
- type: entity
id: ProjectilePolyboltCarp
components:
- type: PolymorphOnTrigger
polymorph: WizardForcedCarp
- - type: TriggerWhitelist
- whitelist:
+ - type: WhitelistTriggerCondition
+ userWhitelist:
components:
- Body
components:
- type: PolymorphOnTrigger
polymorph: WizardForcedMonkey
- - type: TriggerWhitelist
- whitelist:
+ - type: WhitelistTriggerCondition
+ userWhitelist:
components:
- Body
color: brown
- type: PolymorphOnTrigger
polymorph: WizardWallDoor
- - type: TriggerWhitelist
- whitelist:
+ - type: WhitelistTriggerCondition
+ userWhitelist:
components:
- Airlock
- Firelock
components:
- type: PolymorphOnTrigger
polymorph: WizardForcedCluwne
- - type: TriggerWhitelist
- whitelist:
+ - type: WhitelistTriggerCondition
+ userWhitelist:
components:
- Body
components:
- type: PolymorphOnTrigger
polymorph: BreadMorph
- - type: TriggerWhitelist
- whitelist:
+ - type: WhitelistTriggerCondition
+ userWhitelist:
components:
- Body
range: 7
- type: SpawnOnTrigger
proto: GrenadeFlashEffect
- - type: ActiveTimerTrigger
- timeRemaining: 0.3
- type: DeleteOnTrigger
- type: entity
sprite: Objects/Weapons/Guns/Projectiles/projectiles2.rsi
layers:
- state: cleanade
- - type: ActiveTimerTrigger
- timeRemaining: 0.3
- type: SmokeOnTrigger
duration: 3.5
spreadAmount: 30
reagents:
- ReagentId: SpaceCleaner
Quantity: 30
- - type: SoundOnTrigger
+ - type: EmitSoundOnTrigger
sound: /Audio/Items/smoke_grenade_smoke.ogg
- type: ExplodeOnTrigger
- type: Explosive
- type: Repairable
qualityNeeded: "Anchoring"
doAfterDelay: 3
- - type: TriggerWhenEmpty
+ - type: TriggerOnEmptyGunshot
- type: ExplodeOnTrigger
- type: Explosive
explosionType: Default
interactSuccessString: petting-success-generic
interactFailureString: petting-failure-generic
interactSuccessSound:
- path: /Audio/Animals/snake_hiss.ogg
\ No newline at end of file
+ path: /Audio/Animals/snake_hiss.ogg
quickEquip: false
slots:
- Belt
- - type: OnUseTimerTrigger
+ - type: TriggerOnUse
+ - type: TimerTrigger
delay: 3
- type: Damageable
damageContainer: Inorganic
damage: 10
behaviors:
- !type:TriggerBehavior
+ keyOut: timer # explode immediately
- !type:DoActsBehavior
acts: ["Destruction"]
- type: Appearance
id: ExGrenade
components:
- type: ExplodeOnTrigger
+ keysIn:
+ - timer
- type: Explosive
explosionType: Default
maxIntensity: 10
intensitySlope: 3
totalIntensity: 120 # about a ~4 tile radius
canCreateVacuum: false
- - type: OnUseTimerTrigger
+ - type: TimerTrigger
beepSound:
path: "/Audio/Effects/beep1.ogg"
params:
- type: Sprite
sprite: Objects/Weapons/Grenades/flashbang.rsi
- type: FlashOnTrigger
+ keysIn:
+ - timer
range: 7
- - type: SoundOnTrigger
+ - type: EmitSoundOnTrigger
+ keysIn:
+ - timer
sound:
path: "/Audio/Effects/flash_bang.ogg"
- type: DeleteOnTrigger
+ keysIn:
+ - timer
- type: SpawnOnTrigger
+ keysIn:
+ - timer
proto: GrenadeFlashEffect
+ predicted: true
- type: Appearance
- type: TimerTriggerVisuals
primingSound:
damage: 45
behaviors:
- !type:TriggerBehavior
+ keyOut: timer # immediately explode
- !type:DoActsBehavior
acts: ["Destruction"]
- - type: OnUseTimerTrigger
+ - type: TimerTrigger
delay: 5
- type: ExplodeOnTrigger
+ keysIn:
+ - timer
- type: Explosive
explosionType: Minibomb
totalIntensity: 200
categories: [ HideSpawnMenu ]
components:
- type: ExplodeOnTrigger
+ keysIn:
+ - timer
- type: Explosive
explosionType: Minibomb
totalIntensity: 400
intensitySlope: 30
maxIntensity: 125
canCreateVacuum: true
- - type: OnUseTimerTrigger
+ - type: TimerTrigger
delay: 4.5
beepSound:
path: /Audio/Effects/Grenades/SelfDestruct/SDS_Charge2.ogg
components:
- type: Sprite
sprite: Objects/Weapons/Grenades/singularitygrenade.rsi
- - type: OnUseTimerTrigger
+ - type: TimerTrigger
delay: 3
beepInterval: 0.46
beepSound:
path: /Audio/Effects/Grenades/Supermatter/smbeep.ogg
params:
volume: -5
- - type: SoundOnTrigger
- removeOnTrigger: true
+ - type: EmitSoundOnTrigger
+ keysIn:
+ - timer
sound:
path: /Audio/Effects/Grenades/Supermatter/supermatter_start.ogg
- volume: 5
+ params:
+ volume: 5
- type: AnchorOnTrigger
+ keysIn:
+ - timer
removeOnTrigger: true
- type: TwoStageTrigger
+ keysIn:
+ - timer
triggerDelay: 10.45
components:
- type: AmbientSound
radius: 6
softness: 1
offset: "0, 0"
- - type: SoundOnTrigger
+ - type: EmitSoundOnTrigger
sound:
path: /Audio/Effects/Grenades/Supermatter/supermatter_end.ogg
params:
volume: 5
+ keysIn:
+ - stageTwo
- type: DeleteOnTrigger
+ keysIn:
+ - stageTwo
- type: StaticPrice
price: 1000
components:
- type: Sprite
sprite: Objects/Weapons/Grenades/whiteholegrenade.rsi
- - type: OnUseTimerTrigger
+ - type: TimerTrigger
delay: 3
beepInterval: 0.69
- - type: SoundOnTrigger
- removeOnTrigger: true
+ - type: EmitSoundOnTrigger
+ keysIn:
+ - timer
sound:
path: /Audio/Effects/Grenades/Supermatter/whitehole_start.ogg
- volume: 5
+ params:
+ volume: 5
+ - type: AnchorOnTrigger
+ keysIn:
+ - timer
+ removeOnTrigger: true
- type: TwoStageTrigger
triggerDelay: 11.14
components:
radius: 6
softness: 1
offset: "0, 0"
- - type: SoundOnTrigger
+ - type: EmitSoundOnTrigger
+ keysIn:
+ - stageTwo
sound:
path: /Audio/Effects/Grenades/Supermatter/supermatter_end.ogg
params:
volume: 15
+ positional: true
- type: DeleteOnTrigger
+ keysIn:
+ - stageTwo
- type: StaticPrice
price: 1000
components:
- type: Sprite
sprite: Objects/Weapons/Grenades/nukenade.rsi
- - type: OnUseTimerTrigger
+ - type: TimerTrigger
delay: 5
- type: ExplodeOnTrigger
+ keysIn:
+ - timer
- type: Explosive
explosionType: Default
totalIntensity: 20000 # ~15 tile radius.
damage: 50
behaviors:
- !type:TriggerBehavior
+ keyOut: timer # immediately explode
- !type:DoActsBehavior
acts: ["Destruction"]
- type: Appearance
- type: Sprite
sprite: Objects/Weapons/Grenades/empgrenade.rsi
- type: EmpOnTrigger
+ keysIn:
+ - timer
range: 5.5
energyConsumption: 50000
- type: DeleteOnTrigger
+ keysIn:
+ - timer
- type: Appearance
- type: TimerTriggerVisuals
primingSound:
- type: Sprite
sprite: Objects/Weapons/Grenades/holyhandgrenade.rsi
- type: ExplodeOnTrigger
+ keysIn:
+ - timer
- type: Explosive
explosionType: Default # same as macrobomb
totalIntensity: 3500
intensitySlope: 15
maxIntensity: 70
canCreateVacuum: true
- - type: OnUseTimerTrigger
+ - type: TimerTrigger
delay: 3 # by canon
- type: PointLight
radius: 7
- type: Sprite
sprite: Objects/Weapons/Grenades/smoke.rsi
- type: SmokeOnTrigger
+ keysIn:
+ - timer
duration: 30
spreadAmount: 50
- - type: SoundOnTrigger
+ - type: EmitSoundOnTrigger
+ keysIn:
+ - timer
sound: /Audio/Items/smoke_grenade_smoke.ogg
- type: DeleteOnTrigger
+ keysIn:
+ - timer
- type: TimerTriggerVisuals
primingSound:
path: /Audio/Items/smoke_grenade_prime.ogg
- type: Sprite
sprite: Objects/Weapons/Grenades/janitor.rsi
- type: SmokeOnTrigger
+ keysIn:
+ - timer
duration: 15
spreadAmount: 50
smokePrototype: Foam
- type: Sprite
sprite: Objects/Weapons/Grenades/tear_gas.rsi
- type: SmokeOnTrigger
+ keysIn:
+ - timer
duration: 10
spreadAmount: 30
smokePrototype: TearGasSmokeWhite
components:
- type: Sprite
sprite: Objects/Weapons/Grenades/metalfoam.rsi
- - type: OnUseTimerTrigger
+ - type: TimerTrigger
delay: 5
- type: SmokeOnTrigger
+ keysIn:
+ - timer
duration: 10
spreadAmount: 20
smokePrototype: AluminiumMetalFoam
components:
- type: Sprite
sprite: Objects/Weapons/Grenades/airboom.rsi
- - type: SoundOnTrigger
+ - type: EmitSoundOnTrigger
+ keysIn:
+ - timer
sound: /Audio/Items/smoke_grenade_smoke.ogg
- type: TimerTriggerVisuals
primingSound:
path: /Audio/Items/smoke_grenade_prime.ogg
- - type: OnUseTimerTrigger
+ - type: TimerTrigger
delay: 3
- type: ReleaseGasOnTrigger
+ keysIn:
+ - timer
removeFraction: 0.25
air:
volume: 1000
- type: Sprite
sprite: Objects/Weapons/Grenades/grenade.rsi
- type: DeleteOnTrigger
+ keysIn:
+ - timer
- type: SpawnOnTrigger
+ keysIn:
+ - timer
proto: GrenadeFlashEffect
- - type: SoundOnTrigger
+ predicted: true
+ - type: EmitSoundOnTrigger
+ keysIn:
+ - timer
sound:
path: /Audio/Effects/Emotes/parp1.ogg
- type: Appearance
components:
- type: Sprite
sprite: Objects/Weapons/Grenades/syndgrenade.rsi
- - type: OnUseTimerTrigger
+ - type: TimerTrigger
delay: 5
- - type: SoundOnTrigger
+ - type: EmitSoundOnTrigger
+ keysIn:
+ - timer
sound:
path: /Audio/Effects/Emotes/parp1.ogg
- type: Appearance
- type: Damageable
damageContainer: Inorganic
- type: DeleteOnTrigger
+ keysIn:
+ - timer
- type: Destructible
thresholds:
- trigger:
damage: 10
behaviors:
- !type:TriggerBehavior
+ keyOut: timer # explode immediately
- type: ContainerContainer
containers:
cluster-payload: !type:Container
fillPrototype: PelletClusterRubber
capacity: 30
- type: EmitSoundOnTrigger
+ keysIn:
+ - timer
sound:
path: "/Audio/Effects/flash_bang.ogg"
- type: SpawnOnTrigger
+ keysIn:
+ - timer
proto: GrenadeFlashEffect
- - type: OnUseTimerTrigger
+ predicted: true
+ - type: TriggerOnUse
+ - type: TimerTrigger
initialBeepDelay: 0
beepInterval: 2
delay: 3.5
- type: ProjectileGrenade
fillPrototype: PelletClusterIncendiary
capacity: 30
- - type: OnUseTimerTrigger
+ - type: TriggerOnUse
+ - type: TimerTrigger
beepSound:
path: "/Audio/Effects/beep1.ogg"
params:
beepInterval: 2
delay: 3.5
- type: EmitSoundOnTrigger
+ keysIn:
+ - timer
sound:
path: "/Audio/Weapons/Guns/Gunshots/batrifle.ogg"
- type: StaticPrice
- type: ProjectileGrenade
fillPrototype: PelletClusterLethal
capacity: 30
- - type: OnUseTimerTrigger
+ - type: TriggerOnUse
+ - type: TimerTrigger
beepSound:
path: "/Audio/Effects/beep1.ogg"
params:
beepInterval: 2
delay: 3.5
- type: EmitSoundOnTrigger
+ keysIn:
+ - timer
sound:
path: "/Audio/Weapons/Guns/Gunshots/batrifle.ogg"
- type: StaticPrice
damage: 10
behaviors:
- !type:TriggerBehavior
+ keyOut: timer # explode immediately
- type: ScatteringGrenade
- - type: OnUseTimerTrigger
+ - type: TriggerOnUse
+ - type: TimerTrigger
delay: 3
- type: Tag
tags:
Primed: { state: primed }
Unprimed: { state: icon }
- type: EmitSoundOnTrigger
+ keysIn:
+ - timer
sound:
path: "/Audio/Machines/door_lock_off.ogg"
- type: ScatteringGrenade
fillPrototype: ExGrenade
distance: 4
- - type: OnUseTimerTrigger
+ - type: TimerTrigger
beepSound:
path: "/Audio/Effects/beep1.ogg"
params:
initialBeepDelay: 0
beepInterval: 0.5
- type: EmitSoundOnTrigger
+ keysIn:
+ - timer
sound:
path: "/Audio/Machines/door_lock_off.ogg"
- type: StaticPrice
types:
Blunt: 10
- type: EmitSoundOnTrigger
+ keysIn:
+ - timer
sound:
path: "/Audio/Items/bikehorn.ogg"
types:
Blunt: 10
- type: EmitSoundOnTrigger
+ keysIn:
+ - timer
sound:
path: "/Audio/Effects/flash_bang.ogg"
- type: StaticPrice
fillPrototype: BulletFoam
capacity: 30
velocity: 30
- - type: OnUseTimerTrigger
+ - type: TimerTrigger
beepSound:
path: "/Audio/Effects/beep1.ogg"
params:
initialBeepDelay: 0
beepInterval: 2
- type: EmitSoundOnTrigger
+ keysIn:
+ - timer
sound:
path: "/Audio/Weapons/Guns/Gunshots/batrifle.ogg"
description: An ultrabright flashbulb with a proximity trigger, useful for making an area security-only.
components:
- type: EmitSoundOnTrigger
+ predicted: true
sound:
path: /Audio/Weapons/flash.ogg
- type: FlashOnTrigger
intensitySlope: 5
maxIntensity: 4
- type: ExplodeOnTrigger
+ keysIn:
+ - timer
# If you nerf the syndicate bomb in any major way, this should probably drop down to at least 100s (not 90s to compensate for slower movement speed & less lag in SS14)
# Unless, of course, you want the 90 seconds regardless. I can't stop you.
- - type: OnUseTimerTrigger
+ - type: TimerTrigger
delay: 180
delayOptions: [180, 240, 300, 600, 900]
initialBeepDelay: 0
components:
- type: Defusable
disposable: true
- - type: OnUseTimerTrigger
+ - type: TimerTrigger
delay: 10
delayOptions: [10, 20, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300]
Unprimed:
state: gibtonite_inactive
visible: false
- - type: OnUseTimerTrigger
+ - type: TimerTrigger
examinable: false
beepInterval: 0.4
beepSound:
min: 8
max: 10
- type: ExplodeOnTrigger
+ keysIn:
+ - timer
- type: Explosive
explosionType: DemolitionCharge
totalIntensity: 450
- !type:DoActsBehavior
acts: ["Destruction"]
- !type:TriggerBehavior
+ keyOut: timer # explode immediately
# Ore veins
- type: entity
To arm a bomb, you can either [color=yellow]right click[/color] and click [color=yellow]Begin countdown[/click], or [color=yellow]alt-click[/color] the bomb. It will begin beeping.
## Time
- A bomb has a limited time, at a minimum of [protodata="SyndicateBomb" comp="OnUseTimerTrigger" member="ShortestDelayOption"/] seconds and a maximum of [protodata="SyndicateBomb" comp="OnUseTimerTrigger" member="LongestDelayOption"/] seconds. You can view the timer by examining it, unless the Proceed wire is cut. Once the timer hits zero, the bomb will detonate.
+ A bomb has a limited time, at a minimum of [protodata="SyndicateBomb" comp="TimerTrigger" member="ShortestDelayOption"/] seconds and a maximum of [protodata="SyndicateBomb" comp="TimerTrigger" member="LongestDelayOption"/] seconds. You can view the timer by examining it, unless the Proceed wire is cut. Once the timer hits zero, the bomb will detonate.
## Bolts
By default, once armed, a bomb will bolt itself to the ground. You must find the BOLT wire and cut it to disable the bolts, after which you can unwrench it and throw it into space.