Trigger Refactor (#39034)

This commit is contained in:
slarticodefast
2025-08-03 21:20:37 +02:00
committed by GitHub
parent 777e89ab3e
commit 2c40a950f7
256 changed files with 3987 additions and 2892 deletions

View File

@@ -1,7 +0,0 @@
using Content.Shared.Explosion.EntitySystems;
namespace Content.Client.Explosion;
public sealed class SmokeOnTriggerSystem : SharedSmokeOnTriggerSystem
{
}

View File

@@ -1,7 +0,0 @@
using Content.Shared.Explosion;
using Content.Shared.Explosion.Components;
namespace Content.Client.Explosion;
[RegisterComponent, Access(typeof(TriggerSystem))]
public sealed partial class TriggerOnProximityComponent : SharedTriggerOnProximityComponent {}

View File

@@ -1,10 +0,0 @@
namespace Content.Client.Explosion;
public sealed partial class TriggerSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
InitializeProximity();
}
}

View File

@@ -1,5 +1,6 @@
using Content.Shared.HotPotato; using Content.Shared.HotPotato;
using Robust.Shared.Random; using Robust.Shared.Random;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing; using Robust.Shared.Timing;
namespace Content.Client.HotPotato; namespace Content.Client.HotPotato;
@@ -10,6 +11,9 @@ public sealed class HotPotatoSystem : SharedHotPotatoSystem
[Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly SharedTransformSystem _transform = default!;
private readonly EntProtoId _hotPotatoEffectId = "HotPotatoEffect";
// TODO: particle system
public override void Update(float frameTime) public override void Update(float frameTime)
{ {
base.Update(frameTime); base.Update(frameTime);
@@ -23,7 +27,7 @@ public sealed class HotPotatoSystem : SharedHotPotatoSystem
if (_timing.CurTime < comp.TargetTime) if (_timing.CurTime < comp.TargetTime)
continue; continue;
comp.TargetTime = _timing.CurTime + TimeSpan.FromSeconds(comp.EffectCooldown); 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)));
} }
} }
} }

View File

@@ -1,7 +1,8 @@
using Content.Client.Trigger.Systems;
using Robust.Client.Animations; using Robust.Client.Animations;
using Robust.Shared.Audio; using Robust.Shared.Audio;
namespace Content.Client.Trigger; namespace Content.Client.Trigger.Components;
[RegisterComponent] [RegisterComponent]
[Access(typeof(TimerTriggerVisualizerSystem))] [Access(typeof(TimerTriggerVisualizerSystem))]
@@ -16,28 +17,27 @@ public sealed partial class TimerTriggerVisualsComponent : Component
/// <summary> /// <summary>
/// The RSI state used while the device has not been primed. /// The RSI state used while the device has not been primed.
/// </summary> /// </summary>
[DataField("unprimedSprite")] [DataField]
[ViewVariables(VVAccess.ReadWrite)]
public string UnprimedSprite = "icon"; public string UnprimedSprite = "icon";
/// <summary> /// <summary>
/// The RSI state used when the device is primed. /// 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. /// Not VVWrite-able because it's only used at component init to construct the priming animation.
/// </summary> /// </summary>
[DataField("primingSprite")] [DataField]
public string PrimingSprite = "primed"; public string PrimingSprite = "primed";
/// <summary> /// <summary>
/// The sound played when the device is primed. /// The sound played when the device is primed.
/// Not VVWrite-able because it's only used at component init to construct the priming animation. /// Not VVWrite-able because it's only used at component init to construct the priming animation.
/// </summary> /// </summary>
[DataField("primingSound")] [DataField, ViewVariables]
public SoundSpecifier? PrimingSound; public SoundSpecifier? PrimingSound;
/// <summary> /// <summary>
/// The actual priming animation. /// The actual priming animation.
/// Constructed at component init from the sprite and sound. /// Constructed at component init from the sprite and sound.
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables]
public Animation PrimingAnimation = default!; public Animation PrimingAnimation = default!;
} }

View File

@@ -1,11 +1,12 @@
using Content.Shared.Trigger; using Content.Shared.Trigger;
using Content.Shared.Trigger.Components.Triggers;
using Robust.Client.Animations; using Robust.Client.Animations;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Shared.Animations; 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 AnimationPlayerSystem _player = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
@@ -18,7 +19,7 @@ public sealed partial class TriggerSystem
private const string AnimKey = "proximity"; private const string AnimKey = "proximity";
private static readonly Animation _flasherAnimation = new Animation private static readonly Animation FlasherAnimation = new Animation
{ {
Length = TimeSpan.FromSeconds(0.6f), Length = TimeSpan.FromSeconds(0.6f),
AnimationTracks = { AnimationTracks = {
@@ -42,8 +43,10 @@ public sealed partial class TriggerSystem
} }
}; };
private void InitializeProximity() public override void Initialize()
{ {
base.Initialize();
SubscribeLocalEvent<TriggerOnProximityComponent, ComponentInit>(OnProximityInit); SubscribeLocalEvent<TriggerOnProximityComponent, ComponentInit>(OnProximityInit);
SubscribeLocalEvent<TriggerOnProximityComponent, AppearanceChangeEvent>(OnProxAppChange); SubscribeLocalEvent<TriggerOnProximityComponent, AppearanceChangeEvent>(OnProxAppChange);
SubscribeLocalEvent<TriggerOnProximityComponent, AnimationCompletedEvent>(OnProxAnimation); SubscribeLocalEvent<TriggerOnProximityComponent, AnimationCompletedEvent>(OnProxAnimation);
@@ -94,7 +97,7 @@ public sealed partial class TriggerSystem
break; break;
case ProximityTriggerVisuals.Active: case ProximityTriggerVisuals.Active:
if (_player.HasRunningAnimation(uid, player, AnimKey)) return; if (_player.HasRunningAnimation(uid, player, AnimKey)) return;
_player.Play((uid, player), _flasherAnimation, AnimKey); _player.Play((uid, player), FlasherAnimation, AnimKey);
break; break;
case ProximityTriggerVisuals.Off: case ProximityTriggerVisuals.Off:
default: default:

View File

@@ -0,0 +1,5 @@
using Content.Shared.Trigger.Systems;
namespace Content.Client.Trigger.Systems;
public sealed class ReleaseGasOnTriggerSystem : SharedReleaseGasOnTriggerSystem;

View File

@@ -1,11 +1,10 @@
using Content.Client.Trigger.Components;
using Content.Shared.Trigger; using Content.Shared.Trigger;
using Robust.Client.Animations; using Robust.Client.Animations;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems; using Robust.Shared.Audio.Systems;
using Robust.Shared.GameObjects;
namespace Content.Client.Trigger; namespace Content.Client.Trigger.Systems;
public sealed class TimerTriggerVisualizerSystem : VisualizerSystem<TimerTriggerVisualsComponent> public sealed class TimerTriggerVisualizerSystem : VisualizerSystem<TimerTriggerVisualsComponent>
{ {
@@ -17,25 +16,26 @@ public sealed class TimerTriggerVisualizerSystem : VisualizerSystem<TimerTrigger
SubscribeLocalEvent<TimerTriggerVisualsComponent, ComponentInit>(OnComponentInit); 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, Length = TimeSpan.MaxValue,
AnimationTracks = { AnimationTracks = {
new AnimationTrackSpriteFlick() { new AnimationTrackSpriteFlick()
{
LayerKey = TriggerVisualLayers.Base, 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() new AnimationTrackPlaySound()
{ {
KeyFrames = { new AnimationTrackPlaySound.KeyFrame(_audioSystem.ResolveSound(comp.PrimingSound), 0) } KeyFrames = { new AnimationTrackPlaySound.KeyFrame(_audioSystem.ResolveSound(ent.Comp.PrimingSound), 0) }
} }
); );
} }

View File

@@ -1,6 +1,6 @@
using Content.IntegrationTests.Tests.Interaction; using Content.IntegrationTests.Tests.Interaction;
using Content.Server.Explosion.Components; using Content.Shared.Trigger.Components;
using Content.Shared.Explosion.Components; using Content.Shared.Trigger.Systems;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
@@ -25,19 +25,19 @@ public sealed class ModularGrenadeTests : InteractionTest
await InteractUsing(Cable); await InteractUsing(Cable);
// Insert & remove trigger // Insert & remove trigger
AssertComp<OnUseTimerTriggerComponent>(false); AssertComp<TimerTriggerComponent>(false);
await InteractUsing(Trigger); await InteractUsing(Trigger);
AssertComp<OnUseTimerTriggerComponent>(); AssertComp<TimerTriggerComponent>();
await FindEntity(Trigger, LookupFlags.Uncontained, shouldSucceed: false); await FindEntity(Trigger, LookupFlags.Uncontained, shouldSucceed: false);
await InteractUsing(Pry); await InteractUsing(Pry);
AssertComp<OnUseTimerTriggerComponent>(false); AssertComp<TimerTriggerComponent>(false);
// Trigger was dropped to floor, not deleted. // Trigger was dropped to floor, not deleted.
await FindEntity(Trigger, LookupFlags.Uncontained); await FindEntity(Trigger, LookupFlags.Uncontained);
// Re-insert // Re-insert
await InteractUsing(Trigger); await InteractUsing(Trigger);
AssertComp<OnUseTimerTriggerComponent>(); AssertComp<TimerTriggerComponent>();
// Insert & remove payload. // Insert & remove payload.
await InteractUsing(Payload); await InteractUsing(Payload);
@@ -56,13 +56,14 @@ public sealed class ModularGrenadeTests : InteractionTest
await Pickup(); await Pickup();
AssertComp<ActiveTimerTriggerComponent>(false); AssertComp<ActiveTimerTriggerComponent>(false);
await UseInHand(); await UseInHand();
AssertComp<ActiveTimerTriggerComponent>(true);
// So uhhh grenades in hands don't destroy themselves when exploding. Maybe that will be fixed eventually. // So uhhh grenades in hands don't destroy themselves when exploding. Maybe that will be fixed eventually.
await Drop(); await Drop();
// Wait until grenade explodes // Wait until grenade explodes
var timer = Comp<ActiveTimerTriggerComponent>(); var triggerSys = SEntMan.System<TriggerSystem>();
while (timer.TimeRemaining >= 0) while (Target != null && triggerSys.GetRemainingTime(SEntMan.GetEntity(Target.Value))?.TotalSeconds >= 0.0)
{ {
await RunTicks(10); await RunTicks(10);
} }

View File

@@ -1,33 +0,0 @@
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;
}

View File

@@ -5,13 +5,13 @@ using Content.Server.Animals.Components;
using Content.Server.Mind; using Content.Server.Mind;
using Content.Server.Popups; using Content.Server.Popups;
using Content.Server.Radio; using Content.Server.Radio;
using Content.Server.Speech;
using Content.Server.Speech.Components;
using Content.Server.Vocalization.Systems; using Content.Server.Vocalization.Systems;
using Content.Shared.Animals.Components; using Content.Shared.Animals.Components;
using Content.Shared.Animals.Systems; using Content.Shared.Animals.Systems;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.Mobs.Systems; using Content.Shared.Mobs.Systems;
using Content.Shared.Speech;
using Content.Shared.Speech.Components;
using Content.Shared.Whitelist; using Content.Shared.Whitelist;
using Robust.Shared.Network; using Robust.Shared.Network;
using Robust.Shared.Random; using Robust.Shared.Random;

View File

@@ -1,17 +0,0 @@
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;
}

View File

@@ -67,6 +67,9 @@ public sealed class SuicideSystem : EntitySystem
if (!suicideGhostEvent.Handled || _tagSystem.HasTag(victim, CannotSuicideTag)) if (!suicideGhostEvent.Handled || _tagSystem.HasTag(victim, CannotSuicideTag))
return false; 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); var suicideEvent = new SuicideEvent(victim);
RaiseLocalEvent(victim, suicideEvent); RaiseLocalEvent(victim, suicideEvent);

View File

@@ -60,11 +60,6 @@ public sealed partial class ChatSystem : SharedChatSystem
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!; [Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
[Dependency] private readonly ExamineSystemShared _examineSystem = 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 _loocEnabled = true;
private bool _deadLoocEnabled; private bool _deadLoocEnabled;
private bool _critLoocEnabled; private bool _critLoocEnabled;

View File

@@ -1,50 +0,0 @@
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;
}
}

View File

@@ -1,5 +1,4 @@
using Content.Server.Defusable.Components; using Content.Server.Defusable.Components;
using Content.Server.Explosion.Components;
using Content.Server.Explosion.EntitySystems; using Content.Server.Explosion.EntitySystems;
using Content.Server.Popups; using Content.Server.Popups;
using Content.Server.Wires; using Content.Server.Wires;
@@ -8,13 +7,13 @@ using Content.Shared.Construction.Components;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.Defusable; using Content.Shared.Defusable;
using Content.Shared.Examine; using Content.Shared.Examine;
using Content.Shared.Explosion.Components;
using Content.Shared.Explosion.Components.OnTrigger;
using Content.Shared.Popups; 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.Verbs;
using Content.Shared.Wires; using Content.Shared.Wires;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems; using Robust.Shared.Audio.Systems;
namespace Content.Server.Defusable.Systems; namespace Content.Server.Defusable.Systems;
@@ -74,12 +73,13 @@ public sealed class DefusableSystem : SharedDefusableSystem
{ {
args.PushMarkup(Loc.GetString("defusable-examine-defused", ("name", uid))); 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), args.PushMarkup(Loc.GetString("defusable-examine-live", ("name", uid),
("time", MathF.Floor(activeComp.TimeRemaining)))); ("time", Math.Floor(remaining.Value.TotalSeconds))));
} }
else else
{ {
@@ -139,16 +139,9 @@ public sealed class DefusableSystem : SharedDefusableSystem
SetActivated(comp, true); SetActivated(comp, true);
_popup.PopupEntity(Loc.GetString("defusable-popup-begun", ("name", uid)), uid); _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( _trigger.ActivateTimerTrigger((uid, timerTrigger));
uid,
user,
timerTrigger.Delay,
timerTrigger.BeepInterval,
timerTrigger.InitialBeepDelay,
timerTrigger.BeepSound
);
} }
RaiseLocalEvent(uid, new BombArmedEvent(uid)); RaiseLocalEvent(uid, new BombArmedEvent(uid));
@@ -168,7 +161,7 @@ public sealed class DefusableSystem : SharedDefusableSystem
RaiseLocalEvent(uid, new BombDetonatedEvent(uid)); RaiseLocalEvent(uid, new BombDetonatedEvent(uid));
_explosion.TriggerExplosive(uid, user:detonator); _explosion.TriggerExplosive(uid, user: detonator);
QueueDel(uid); QueueDel(uid);
_appearance.SetData(uid, DefusableVisuals.Active, comp.Activated); _appearance.SetData(uid, DefusableVisuals.Active, comp.Activated);
@@ -188,7 +181,7 @@ public sealed class DefusableSystem : SharedDefusableSystem
{ {
SetUsable(comp, false); SetUsable(comp, false);
RemComp<ExplodeOnTriggerComponent>(uid); RemComp<ExplodeOnTriggerComponent>(uid);
RemComp<OnUseTimerTriggerComponent>(uid); RemComp<TimerTriggerComponent>(uid);
} }
RemComp<ActiveTimerTriggerComponent>(uid); RemComp<ActiveTimerTriggerComponent>(uid);
@@ -246,7 +239,7 @@ public sealed class DefusableSystem : SharedDefusableSystem
if (comp is not { Activated: true, DelayWireUsed: false }) if (comp is not { Activated: true, DelayWireUsed: false })
return; 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); _popup.PopupEntity(Loc.GetString("defusable-popup-wire-chirp", ("name", wire.Owner)), wire.Owner);
comp.DelayWireUsed = true; comp.DelayWireUsed = true;
} }
@@ -268,7 +261,7 @@ public sealed class DefusableSystem : SharedDefusableSystem
if (comp is { Activated: true, ProceedWireUsed: false }) if (comp is { Activated: true, ProceedWireUsed: false })
{ {
comp.ProceedWireUsed = true; 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); _popup.PopupEntity(Loc.GetString("defusable-popup-wire-proceed-pulse", ("name", wire.Owner)), wire.Owner);
@@ -298,7 +291,7 @@ public sealed class DefusableSystem : SharedDefusableSystem
{ {
if (!comp.ActivatedWireUsed) 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); _popup.PopupEntity(Loc.GetString("defusable-popup-wire-chirp", ("name", wire.Owner)), wire.Owner);
comp.ActivatedWireUsed = true; comp.ActivatedWireUsed = true;
} }

View File

@@ -1,4 +1,5 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Server.Administration.Logs; using Content.Server.Administration.Logs;
using Content.Server.Atmos.EntitySystems; using Content.Server.Atmos.EntitySystems;
using Content.Server.Body.Systems; using Content.Server.Body.Systems;
@@ -14,15 +15,13 @@ using Content.Shared.Damage;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.Destructible; using Content.Shared.Destructible;
using Content.Shared.FixedPoint; using Content.Shared.FixedPoint;
using Content.Shared.Humanoid;
using Content.Shared.Trigger.Systems;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Server.Audio; using Robust.Server.Audio;
using Robust.Server.GameObjects;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Random; using Robust.Shared.Random;
using System.Linq;
using Content.Shared.Humanoid;
using Robust.Shared.Player;
namespace Content.Server.Destructible namespace Content.Server.Destructible
{ {

View File

@@ -5,6 +5,6 @@ public sealed partial class TimerStartBehavior : IThresholdBehavior
{ {
public void Execute(EntityUid owner, DestructibleSystem system, EntityUid? cause = null) public void Execute(EntityUid owner, DestructibleSystem system, EntityUid? cause = null)
{ {
system.TriggerSystem.StartTimer(owner, cause); system.TriggerSystem.ActivateTimerTrigger(owner, cause);
} }
} }

View File

@@ -1,10 +1,18 @@
namespace Content.Server.Destructible.Thresholds.Behaviors; using Content.Shared.Trigger.Systems;
namespace Content.Server.Destructible.Thresholds.Behaviors;
[DataDefinition] [DataDefinition]
public sealed partial class TriggerBehavior : IThresholdBehavior 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) public void Execute(EntityUid owner, DestructibleSystem system, EntityUid? cause = null)
{ {
system.TriggerSystem.Trigger(owner, cause); system.TriggerSystem.Trigger(owner, cause, KeyOut);
} }
} }

View File

@@ -1,4 +1,6 @@
using Content.Server.DeviceLinking.Components; using Content.Server.DeviceLinking.Components;
using Content.Server.DeviceNetwork;
using Content.Server.DeviceNetwork.Components;
using Content.Server.DeviceNetwork.Systems; using Content.Server.DeviceNetwork.Systems;
using Content.Shared.DeviceLinking; using Content.Shared.DeviceLinking;
using Content.Shared.DeviceLinking.Events; using Content.Shared.DeviceLinking.Events;

View File

@@ -1,5 +1,4 @@
using Content.Server.DeviceLinking.Components; using Content.Server.DeviceLinking.Components;
using Content.Server.DeviceNetwork;
using Content.Shared.DeviceLinking; using Content.Shared.DeviceLinking;
using Content.Shared.DeviceLinking.Events; using Content.Shared.DeviceLinking.Events;
using Content.Shared.DeviceNetwork; using Content.Shared.DeviceNetwork;

View File

@@ -1,6 +1,5 @@
using Content.Server.Administration.Logs; using Content.Server.Administration.Logs;
using Content.Server.DeviceLinking.Components; using Content.Server.DeviceLinking.Components;
using Content.Server.Explosion.EntitySystems;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.Interaction.Events; using Content.Shared.Interaction.Events;
using Content.Shared.Timing; using Content.Shared.Timing;
@@ -10,7 +9,6 @@ namespace Content.Server.DeviceLinking.Systems;
public sealed class SignallerSystem : EntitySystem public sealed class SignallerSystem : EntitySystem
{ {
[Dependency] private readonly DeviceLinkSystem _link = default!; [Dependency] private readonly DeviceLinkSystem _link = default!;
[Dependency] private readonly UseDelaySystem _useDelay = default!;
[Dependency] private readonly IAdminLogManager _adminLogger = default!; [Dependency] private readonly IAdminLogManager _adminLogger = default!;
public override void Initialize() public override void Initialize()
@@ -19,7 +17,6 @@ public sealed class SignallerSystem : EntitySystem
SubscribeLocalEvent<SignallerComponent, ComponentInit>(OnInit); SubscribeLocalEvent<SignallerComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<SignallerComponent, UseInHandEvent>(OnUseInHand); SubscribeLocalEvent<SignallerComponent, UseInHandEvent>(OnUseInHand);
SubscribeLocalEvent<SignallerComponent, TriggerEvent>(OnTrigger);
} }
private void OnInit(EntityUid uid, SignallerComponent component, ComponentInit args) private void OnInit(EntityUid uid, SignallerComponent component, ComponentInit args)
@@ -36,16 +33,4 @@ public sealed class SignallerSystem : EntitySystem
_link.InvokePort(uid, component.Port); _link.InvokePort(uid, component.Port);
args.Handled = true; 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;
}
} }

View File

@@ -58,7 +58,7 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
[Dependency] private readonly MetaDataSystem _metaData = default!; [Dependency] private readonly MetaDataSystem _metaData = default!;
[Dependency] private readonly TurfSystem _turf = 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<DamageTypePrototype> DamageType = "Shock";
private static readonly ProtoId<TagPrototype> WindowTag = "Window"; private static readonly ProtoId<TagPrototype> WindowTag = "Window";
@@ -386,12 +386,12 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
} }
if (!Resolve(uid, ref statusEffects, false) || if (!Resolve(uid, ref statusEffects, false) ||
!_statusEffects.CanApplyEffect(uid, StatusEffectKey, statusEffects)) !_statusEffects.CanApplyEffect(uid, StatusKeyIn, statusEffects))
{ {
return false; return false;
} }
if (!_statusEffects.TryAddStatusEffect<ElectrocutedComponent>(uid, StatusEffectKey, time, refresh, statusEffects)) if (!_statusEffects.TryAddStatusEffect<ElectrocutedComponent>(uid, StatusKeyIn, time, refresh, statusEffects))
return false; return false;
var shouldStun = siemensCoefficient > 0.5f; var shouldStun = siemensCoefficient > 0.5f;

View File

@@ -1,24 +0,0 @@
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;
}

View File

@@ -1,4 +1,3 @@
using Content.Server.Explosion.EntitySystems;
using Content.Server.Power.EntitySystems; using Content.Server.Power.EntitySystems;
using Content.Server.Radio; using Content.Server.Radio;
using Content.Server.SurveillanceCamera; using Content.Server.SurveillanceCamera;
@@ -20,7 +19,6 @@ public sealed class EmpSystem : SharedEmpSystem
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<EmpDisabledComponent, ExaminedEvent>(OnExamine); SubscribeLocalEvent<EmpDisabledComponent, ExaminedEvent>(OnExamine);
SubscribeLocalEvent<EmpOnTriggerComponent, TriggerEvent>(HandleEmpTrigger);
SubscribeLocalEvent<EmpDisabledComponent, RadioSendAttemptEvent>(OnRadioSendAttempt); SubscribeLocalEvent<EmpDisabledComponent, RadioSendAttemptEvent>(OnRadioSendAttempt);
SubscribeLocalEvent<EmpDisabledComponent, RadioReceiveAttemptEvent>(OnRadioReceiveAttempt); SubscribeLocalEvent<EmpDisabledComponent, RadioReceiveAttemptEvent>(OnRadioReceiveAttempt);
@@ -28,14 +26,7 @@ public sealed class EmpSystem : SharedEmpSystem
SubscribeLocalEvent<EmpDisabledComponent, SurveillanceCameraSetActiveAttemptEvent>(OnCameraSetActive); SubscribeLocalEvent<EmpDisabledComponent, SurveillanceCameraSetActiveAttemptEvent>(OnCameraSetActive);
} }
/// <summary> public override void EmpPulse(MapCoordinates coordinates, float range, float energyConsumption, float duration)
/// 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)
{ {
foreach (var uid in _lookup.GetEntitiesInRange(coordinates, range)) foreach (var uid in _lookup.GetEntitiesInRange(coordinates, range))
{ {
@@ -118,12 +109,6 @@ public sealed class EmpSystem : SharedEmpSystem
args.PushMarkup(Loc.GetString("emp-disabled-comp-on-examine")); 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) private void OnRadioSendAttempt(EntityUid uid, EmpDisabledComponent component, ref RadioSendAttemptEvent args)
{ {
args.Cancelled = true; args.Cancelled = true;

View File

@@ -1,4 +0,0 @@
namespace Content.Server.Explosion.Components;
[RegisterComponent]
public sealed partial class ActiveTriggerOnTimedCollideComponent : Component { }

View File

@@ -1,9 +0,0 @@
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
{
}

View File

@@ -1,13 +0,0 @@
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;
}

View File

@@ -1,11 +0,0 @@
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
{
}

View File

@@ -1,16 +0,0 @@
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;
}

View File

@@ -1,17 +0,0 @@
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");
}

View File

@@ -1,34 +0,0 @@
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;
}

View File

@@ -45,4 +45,10 @@ public sealed partial class ProjectileGrenadeComponent : Component
/// </summary> /// </summary>
[DataField] [DataField]
public float MaxVelocity = 6f; public float MaxVelocity = 6f;
/// <summary>
/// The trigger key that will activate the grenade.
/// </summary>
[DataField]
public string TriggerKey = "timer";
} }

View File

@@ -1,37 +0,0 @@
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;
}

View File

@@ -1,24 +0,0 @@
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;
}

View File

@@ -1,15 +0,0 @@
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";
}
}

View File

@@ -1,7 +0,0 @@
namespace Content.Server.Explosion.Components;
/// <summary>
/// Triggers on click.
/// </summary>
[RegisterComponent]
public sealed partial class TriggerOnActivateComponent : Component { }

View File

@@ -1,20 +0,0 @@
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;
}

View File

@@ -1,24 +0,0 @@
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;
}

View File

@@ -1,93 +0,0 @@
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);
}
}

View File

@@ -1,15 +0,0 @@
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";
}
}

View File

@@ -1,6 +0,0 @@
namespace Content.Server.Explosion.Components;
[RegisterComponent]
public sealed partial class TriggerOnSlipComponent : Component
{
}

View File

@@ -1,9 +0,0 @@
namespace Content.Server.Explosion.Components;
/// <summary>
/// calls the trigger when the object is initialized
/// </summary>
[RegisterComponent]
public sealed partial class TriggerOnSpawnComponent : Component
{
}

View File

@@ -1,10 +0,0 @@
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
{
}

View File

@@ -1,18 +0,0 @@
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();
}

View File

@@ -1,7 +0,0 @@
namespace Content.Server.Explosion.Components;
/// <summary>
/// Triggers on use in hand.
/// </summary>
[RegisterComponent]
public sealed partial class TriggerOnUseComponent : Component { }

View File

@@ -1,28 +0,0 @@
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;
}
}

View File

@@ -1,9 +0,0 @@
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
{
}

View File

@@ -1,23 +0,0 @@
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;
}

View File

@@ -1,5 +1,6 @@
using Content.Server.Explosion.Components; using Content.Server.Explosion.Components;
using Content.Server.Weapons.Ranged.Systems; using Content.Server.Weapons.Ranged.Systems;
using Content.Shared.Trigger;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using Robust.Shared.Map; using Robust.Shared.Map;
@@ -45,6 +46,9 @@ public sealed class ProjectileGrenadeSystem : EntitySystem
/// </summary> /// </summary>
private void OnFragTrigger(Entity<ProjectileGrenadeComponent> entity, ref TriggerEvent args) private void OnFragTrigger(Entity<ProjectileGrenadeComponent> entity, ref TriggerEvent args)
{ {
if (args.Key != entity.Comp.TriggerKey)
return;
FragmentIntoProjectiles(entity.Owner, entity.Comp); FragmentIntoProjectiles(entity.Owner, entity.Comp);
args.Handled = true; args.Handled = true;
} }

View File

@@ -1,79 +0,0 @@
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);
}
}

View File

@@ -1,29 +0,0 @@
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);
}
}

View File

@@ -1,5 +1,8 @@
using Content.Shared.Explosion.Components; using Content.Shared.Explosion.Components;
using Content.Shared.Throwing; using Content.Shared.Throwing;
using Content.Shared.Trigger;
using Content.Shared.Trigger.Systems;
using Content.Shared.Trigger.Components;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using Robust.Shared.Map; using Robust.Shared.Map;
@@ -15,6 +18,7 @@ public sealed class ScatteringGrenadeSystem : SharedScatteringGrenadeSystem
[Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly ThrowingSystem _throwingSystem = default!; [Dependency] private readonly ThrowingSystem _throwingSystem = default!;
[Dependency] private readonly TransformSystem _transformSystem = default!; [Dependency] private readonly TransformSystem _transformSystem = default!;
[Dependency] private readonly TriggerSystem _trigger = default!;
public override void Initialize() public override void Initialize()
{ {
@@ -30,6 +34,9 @@ public sealed class ScatteringGrenadeSystem : SharedScatteringGrenadeSystem
/// </summary> /// </summary>
private void OnScatteringTrigger(Entity<ScatteringGrenadeComponent> entity, ref TriggerEvent args) private void OnScatteringTrigger(Entity<ScatteringGrenadeComponent> entity, ref TriggerEvent args)
{ {
if (args.Key != entity.Comp.TriggerKey)
return;
entity.Comp.IsTriggered = true; entity.Comp.IsTriggered = true;
args.Handled = true; args.Handled = true;
} }
@@ -76,13 +83,12 @@ public sealed class ScatteringGrenadeSystem : SharedScatteringGrenadeSystem
_throwingSystem.TryThrow(contentUid, direction, component.Velocity); _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); additionalIntervalDelay += _random.NextFloat(component.IntervalBetweenTriggersMin, component.IntervalBetweenTriggersMax);
var contentTimer = EnsureComp<ActiveTimerTriggerComponent>(contentUid);
contentTimer.TimeRemaining = component.DelayBeforeTriggerContents + additionalIntervalDelay; _trigger.SetDelay((contentUid, contentTimer), TimeSpan.FromSeconds(component.DelayBeforeTriggerContents + additionalIntervalDelay));
var ev = new ActiveTimerTriggerEvent(contentUid, uid); _trigger.ActivateTimerTrigger((contentUid, contentTimer));
RaiseLocalEvent(contentUid, ref ev);
} }
} }

View File

@@ -1,57 +0,0 @@
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);
}
}

View File

@@ -1,170 +0,0 @@
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");
}

View File

@@ -1,154 +0,0 @@
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;
}
}
}
}

View File

@@ -1,43 +0,0 @@
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);
}
}
}

View File

@@ -1,59 +0,0 @@
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;
}
}
}
}
}

View File

@@ -1,154 +0,0 @@
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);

View File

@@ -1,454 +0,0 @@
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);
}
}
}
}

View File

@@ -1,64 +0,0 @@
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);
}
}
}

View File

@@ -1,7 +0,0 @@
namespace Content.Server.GhostKick;
[RegisterComponent]
public sealed partial class GhostKickUserOnTriggerComponent : Component
{
}

View File

@@ -1,26 +0,0 @@
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;
}
}

View File

@@ -1,7 +1,6 @@
using Content.Server.Chat.Systems; using Content.Server.Chat.Systems;
using Content.Server.Popups; using Content.Server.Popups;
using Content.Server.Power.EntitySystems; using Content.Server.Power.EntitySystems;
using Content.Server.Speech.Components;
using Content.Server.Telephone; using Content.Server.Telephone;
using Content.Shared.Access.Systems; using Content.Shared.Access.Systems;
using Content.Shared.Audio; using Content.Shared.Audio;
@@ -12,6 +11,7 @@ using Content.Shared.Labels.Components;
using Content.Shared.Power; using Content.Shared.Power;
using Content.Shared.Silicons.StationAi; using Content.Shared.Silicons.StationAi;
using Content.Shared.Speech; using Content.Shared.Speech;
using Content.Shared.Speech.Components;
using Content.Shared.Telephone; using Content.Shared.Telephone;
using Content.Shared.UserInterface; using Content.Shared.UserInterface;
using Content.Shared.Verbs; using Content.Shared.Verbs;

View File

@@ -1,61 +1,5 @@
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.HotPotato;
using Content.Shared.Popups;
using Content.Shared.Weapons.Melee.Events;
namespace Content.Server.HotPotato; namespace Content.Server.HotPotato;
public sealed class HotPotatoSystem : SharedHotPotatoSystem 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);
}
}

View File

@@ -1,30 +0,0 @@
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");
}

View File

@@ -1,9 +1,9 @@
using Content.Server.Explosion.EntitySystems;
using Content.Shared.Armable; using Content.Shared.Armable;
using Content.Shared.Item.ItemToggle.Components; using Content.Shared.Item.ItemToggle.Components;
using Content.Shared.LandMines; using Content.Shared.LandMines;
using Content.Shared.Popups; using Content.Shared.Popups;
using Content.Shared.StepTrigger.Systems; using Content.Shared.StepTrigger.Systems;
using Content.Shared.Trigger.Systems;
using Robust.Shared.Audio.Systems; using Robust.Shared.Audio.Systems;
namespace Content.Server.LandMines; namespace Content.Server.LandMines;
@@ -44,7 +44,8 @@ public sealed class LandMineSystem : EntitySystem
/// </summary> /// </summary>
private void HandleStepOffTriggered(EntityUid uid, LandMineComponent component, ref StepTriggeredOffEvent args) 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> /// <summary>

View File

@@ -1,79 +0,0 @@
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);
}
}

View File

@@ -1,4 +1,3 @@
using Content.Server.Explosion.EntitySystems;
using Content.Server.Mind; using Content.Server.Mind;
using Content.Server.Objectives.Components; using Content.Server.Objectives.Components;
using Content.Server.Popups; using Content.Server.Popups;
@@ -7,6 +6,7 @@ using Content.Shared.Ninja.Components;
using Content.Shared.Ninja.Systems; using Content.Shared.Ninja.Systems;
using Content.Shared.Roles; using Content.Shared.Roles;
using Content.Shared.Sticky; using Content.Shared.Sticky;
using Content.Shared.Trigger;
namespace Content.Server.Ninja.Systems; namespace Content.Server.Ninja.Systems;
@@ -80,6 +80,9 @@ public sealed class SpiderChargeSystem : SharedSpiderChargeSystem
/// </summary> /// </summary>
private void OnExplode(EntityUid uid, SpiderChargeComponent comp, TriggerEvent args) private void OnExplode(EntityUid uid, SpiderChargeComponent comp, TriggerEvent args)
{ {
if (args.Key != comp.TriggerKey)
return;
if (!TryComp<SpaceNinjaComponent>(comp.Planter, out var ninja)) if (!TryComp<SpaceNinjaComponent>(comp.Planter, out var ninja))
return; return;

View File

@@ -1,15 +1,15 @@
using Content.Server.Explosion.EntitySystems;
using Content.Server.Fluids.EntitySystems; using Content.Server.Fluids.EntitySystems;
using Content.Server.Nutrition.Components; using Content.Server.Nutrition.Components;
using Content.Server.Popups; using Content.Server.Popups;
using Content.Shared.Containers.ItemSlots; using Content.Shared.Containers.ItemSlots;
using Content.Shared.Explosion.Components;
using Content.Shared.IdentityManagement; using Content.Shared.IdentityManagement;
using Content.Shared.Nutrition; using Content.Shared.Nutrition;
using Content.Shared.Nutrition.Components; using Content.Shared.Nutrition.Components;
using Content.Shared.Nutrition.EntitySystems; using Content.Shared.Nutrition.EntitySystems;
using Content.Shared.Rejuvenate; using Content.Shared.Rejuvenate;
using Content.Shared.Throwing; using Content.Shared.Throwing;
using Content.Shared.Trigger.Components;
using Content.Shared.Trigger.Systems;
using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Chemistry.EntitySystems;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Audio; using Robust.Shared.Audio;
@@ -77,15 +77,9 @@ namespace Content.Server.Nutrition.EntitySystems
{ {
if (_itemSlots.TryEject(uid, itemSlot, user: null, out var item)) 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( _trigger.ActivateTimerTrigger((item.Value, timerTrigger));
item.Value,
null,
timerTrigger.Delay,
timerTrigger.BeepInterval,
timerTrigger.InitialBeepDelay,
timerTrigger.BeepSound);
} }
} }
} }

View File

@@ -1,10 +1,10 @@
using Content.Server.Administration.Logs; using Content.Server.Administration.Logs;
using Content.Server.Explosion.EntitySystems;
using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Components;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.Examine; using Content.Shared.Examine;
using Content.Shared.Payload.Components; using Content.Shared.Payload.Components;
using Content.Shared.Tag; using Content.Shared.Tag;
using Content.Shared.Trigger;
using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Chemistry.EntitySystems;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using Robust.Shared.Serialization.Manager; using Robust.Shared.Serialization.Manager;
@@ -54,18 +54,22 @@ public sealed class PayloadSystem : EntitySystem
private void OnCaseTriggered(EntityUid uid, PayloadCaseComponent component, TriggerEvent args) private void OnCaseTriggered(EntityUid uid, PayloadCaseComponent component, TriggerEvent args)
{ {
// TODO: Adjust to the new trigger system
if (!TryComp(uid, out ContainerManagerComponent? contMan)) if (!TryComp(uid, out ContainerManagerComponent? contMan))
return; return;
// Pass trigger event onto all contained payloads. Payload capacity configurable by construction graphs. // Pass trigger event onto all contained payloads. Payload capacity configurable by construction graphs.
foreach (var ent in GetAllPayloads(uid, contMan)) 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) private void OnTriggerTriggered(EntityUid uid, PayloadTriggerComponent component, TriggerEvent args)
{ {
// TODO: Adjust to the new trigger system
if (!component.Active) if (!component.Active)
return; return;
@@ -75,7 +79,7 @@ public sealed class PayloadSystem : EntitySystem
// Ensure we don't enter a trigger-loop // Ensure we don't enter a trigger-loop
DebugTools.Assert(!_tagSystem.HasTag(uid, PayloadTag)); DebugTools.Assert(!_tagSystem.HasTag(uid, PayloadTag));
RaiseLocalEvent(parent, args, false); RaiseLocalEvent(parent, ref args);
} }
private void OnEntityInserted(EntityUid uid, PayloadCaseComponent _, EntInsertedIntoContainerMessage args) private void OnEntityInserted(EntityUid uid, PayloadCaseComponent _, EntInsertedIntoContainerMessage args)
@@ -146,6 +150,7 @@ public sealed class PayloadSystem : EntitySystem
private void HandleChemicalPayloadTrigger(Entity<ChemicalPayloadComponent> entity, ref TriggerEvent 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 if (entity.Comp.BeakerSlotA.Item is not EntityUid beakerA
|| entity.Comp.BeakerSlotB.Item is not EntityUid beakerB || entity.Comp.BeakerSlotB.Item is not EntityUid beakerB
|| !TryComp(beakerA, out FitsInDispenserComponent? compA) || !TryComp(beakerA, out FitsInDispenserComponent? compA)

View File

@@ -1,18 +0,0 @@
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;
}

View File

@@ -1,41 +0,0 @@
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);
}
}
}

View File

@@ -58,7 +58,6 @@ public sealed partial class PolymorphSystem : EntitySystem
SubscribeLocalEvent<PolymorphedEntityComponent, DestructionEventArgs>(OnDestruction); SubscribeLocalEvent<PolymorphedEntityComponent, DestructionEventArgs>(OnDestruction);
InitializeMap(); InitializeMap();
InitializeTrigger();
} }
public override void Update(float frameTime) public override void Update(float frameTime)
@@ -85,8 +84,6 @@ public sealed partial class PolymorphSystem : EntitySystem
Revert((uid, comp)); Revert((uid, comp));
} }
} }
UpdateTrigger();
} }
private void OnComponentStartup(Entity<PolymorphableComponent> ent, ref ComponentStartup args) private void OnComponentStartup(Entity<PolymorphableComponent> ent, ref ComponentStartup args)

View File

@@ -2,15 +2,14 @@ using System.Linq;
using Content.Server.Chat.Systems; using Content.Server.Chat.Systems;
using Content.Server.Interaction; using Content.Server.Interaction;
using Content.Server.Popups; using Content.Server.Popups;
using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems; using Content.Server.Power.EntitySystems;
using Content.Server.Radio.Components; using Content.Server.Radio.Components;
using Content.Server.Speech;
using Content.Server.Speech.Components;
using Content.Shared.Examine; using Content.Shared.Examine;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Power; using Content.Shared.Power;
using Content.Shared.Radio; using Content.Shared.Radio;
using Content.Shared.Speech;
using Content.Shared.Speech.Components;
using Content.Shared.Chat; using Content.Shared.Chat;
using Content.Shared.Radio.Components; using Content.Shared.Radio.Components;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;

View File

@@ -119,7 +119,7 @@ public sealed partial class BorgSystem
var message = Loc.GetString(ent.Comp.DestroyingPopup, ("name", Name(ent))); var message = Loc.GetString(ent.Comp.DestroyingPopup, ("name", Name(ent)));
Popup.PopupEntity(message, ent); Popup.PopupEntity(message, ent);
_trigger.StartTimer(ent.Owner, user: null); _trigger.ActivateTimerTrigger(ent.Owner);
// prevent a shitter borg running into people // prevent a shitter borg running into people
RemComp<InputMoverComponent>(ent); RemComp<InputMoverComponent>(ent);

View File

@@ -4,7 +4,6 @@ using Content.Server.Actions;
using Content.Server.Administration.Logs; using Content.Server.Administration.Logs;
using Content.Server.Administration.Managers; using Content.Server.Administration.Managers;
using Content.Server.DeviceNetwork.Systems; using Content.Server.DeviceNetwork.Systems;
using Content.Server.Explosion.EntitySystems;
using Content.Server.Hands.Systems; using Content.Server.Hands.Systems;
using Content.Server.PowerCell; using Content.Server.PowerCell;
using Content.Shared.Alert; using Content.Shared.Alert;
@@ -25,6 +24,7 @@ using Content.Shared.Roles;
using Content.Shared.Silicons.Borgs; using Content.Shared.Silicons.Borgs;
using Content.Shared.Silicons.Borgs.Components; using Content.Shared.Silicons.Borgs.Components;
using Content.Shared.Throwing; using Content.Shared.Throwing;
using Content.Shared.Trigger.Systems;
using Content.Shared.Whitelist; using Content.Shared.Whitelist;
using Content.Shared.Wires; using Content.Shared.Wires;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;

View File

@@ -1,13 +0,0 @@
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
{
}
}

View File

@@ -1,6 +1,3 @@
using Content.Server.Explosion.EntitySystems;
using Content.Server.Sound.Components;
using Content.Shared.UserInterface;
using Content.Shared.Sound; using Content.Shared.Sound;
using Content.Shared.Sound.Components; using Content.Shared.Sound.Components;
using Robust.Shared.Timing; using Robust.Shared.Timing;
@@ -38,16 +35,9 @@ public sealed class EmitSoundSystem : SharedEmitSoundSystem
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<EmitSoundOnTriggerComponent, TriggerEvent>(HandleEmitSoundOnTrigger);
SubscribeLocalEvent<SpamEmitSoundComponent, MapInitEvent>(HandleSpamEmitSoundMapInit); 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) private void HandleSpamEmitSoundMapInit(Entity<SpamEmitSoundComponent> entity, ref MapInitEvent args)
{ {
SpamEmitSoundReset(entity); SpamEmitSoundReset(entity);

View File

@@ -1,13 +0,0 @@
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;
}

View File

@@ -1,4 +1,5 @@
using Content.Server.Speech.Components; using Content.Server.Speech.Components;
using Content.Shared.Speech;
namespace Content.Server.Speech.EntitySystems; namespace Content.Server.Speech.EntitySystems;

View File

@@ -1,5 +1,6 @@
using Content.Server.Chat.Systems; using Content.Server.Chat.Systems;
using Content.Server.Speech.Components; using Content.Shared.Speech;
using Content.Shared.Speech.Components;
namespace Content.Server.Speech.EntitySystems; namespace Content.Server.Speech.EntitySystems;

View File

@@ -1,6 +1,6 @@
using Content.Server.Chat.Systems; using Content.Server.Chat.Systems;
using Content.Server.Speech; using Content.Shared.Speech;
using Content.Server.Speech.Components; using Content.Shared.Speech.Components;
using Content.Shared.Whitelist; using Content.Shared.Whitelist;
using Robust.Shared.Player; using Robust.Shared.Player;
using static Content.Server.Chat.Systems.ChatSystem; using static Content.Server.Chat.Systems.ChatSystem;

View File

@@ -3,8 +3,6 @@ using Content.Server.Administration.Logs;
using Content.Server.Chat.Systems; using Content.Server.Chat.Systems;
using Content.Server.Interaction; using Content.Server.Interaction;
using Content.Server.Power.EntitySystems; using Content.Server.Power.EntitySystems;
using Content.Server.Speech;
using Content.Server.Speech.Components;
using Content.Shared.Chat; using Content.Shared.Chat;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.Labels.Components; using Content.Shared.Labels.Components;
@@ -13,6 +11,7 @@ using Content.Shared.Power;
using Content.Shared.Silicons.StationAi; using Content.Shared.Silicons.StationAi;
using Content.Shared.Silicons.Borgs.Components; using Content.Shared.Silicons.Borgs.Components;
using Content.Shared.Speech; using Content.Shared.Speech;
using Content.Shared.Speech.Components;
using Content.Shared.Telephone; using Content.Shared.Telephone;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.Audio.Systems; using Robust.Shared.Audio.Systems;

View File

@@ -1,8 +1,9 @@
using Content.Server.AlertLevel; using Content.Server.AlertLevel;
using Content.Server.Explosion.EntitySystems; using Content.Shared.Trigger;
using Content.Shared.Trigger.Components.Effects;
using Content.Server.Station.Systems; using Content.Server.Station.Systems;
namespace Content.Server.AlertLevel.Systems; namespace Content.Server.Trigger.Systems;
public sealed class AlertLevelChangeOnTriggerSystem : EntitySystem public sealed class AlertLevelChangeOnTriggerSystem : EntitySystem
{ {
@@ -18,10 +19,14 @@ public sealed class AlertLevelChangeOnTriggerSystem : EntitySystem
private void OnTrigger(Entity<AlertLevelChangeOnTriggerComponent> ent, ref TriggerEvent args) 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); var stationUid = _station.GetOwningStation(ent.Owner);
if (!stationUid.HasValue) if (stationUid == null)
return; return;
_alertLevelSystem.SetLevel(stationUid.Value, ent.Comp.Level, ent.Comp.PlaySound, ent.Comp.Announce, ent.Comp.Force); _alertLevelSystem.SetLevel(stationUid.Value, ent.Comp.Level, ent.Comp.PlaySound, ent.Comp.Announce, ent.Comp.Force);
args.Handled = true;
} }
} }

View File

@@ -0,0 +1,38 @@
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;
}
}

View File

@@ -1,10 +1,9 @@
using Content.Server.Explosion.EntitySystems;
using Content.Shared.IgnitionSource; using Content.Shared.IgnitionSource;
using Content.Shared.Timing; using Content.Shared.Trigger;
using Robust.Shared.Audio.Systems; using Content.Shared.Trigger.Components.Effects;
using Robust.Shared.Timing; using Robust.Shared.Timing;
namespace Content.Server.IgnitionSource; namespace Content.Server.Trigger.Systems;
/// <summary> /// <summary>
/// Handles igniting when triggered and stopping ignition after the delay. /// Handles igniting when triggered and stopping ignition after the delay.
@@ -13,8 +12,6 @@ public sealed class IgniteOnTriggerSystem : EntitySystem
{ {
[Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedIgnitionSourceSystem _source = default!; [Dependency] private readonly SharedIgnitionSourceSystem _source = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly UseDelaySystem _useDelay = default!;
public override void Initialize() public override void Initialize()
{ {
@@ -23,6 +20,8 @@ public sealed class IgniteOnTriggerSystem : EntitySystem
SubscribeLocalEvent<IgniteOnTriggerComponent, TriggerEvent>(OnTrigger); SubscribeLocalEvent<IgniteOnTriggerComponent, TriggerEvent>(OnTrigger);
} }
// TODO: move this into ignition source component
// it already has an update loop
public override void Update(float deltaTime) public override void Update(float deltaTime)
{ {
base.Update(deltaTime); base.Update(deltaTime);
@@ -42,14 +41,18 @@ public sealed class IgniteOnTriggerSystem : EntitySystem
private void OnTrigger(Entity<IgniteOnTriggerComponent> ent, ref TriggerEvent args) private void OnTrigger(Entity<IgniteOnTriggerComponent> ent, ref TriggerEvent args)
{ {
// prevent spamming sound and ignition if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
if (!TryComp(ent.Owner, out UseDelayComponent? useDelay) || _useDelay.IsDelayed((ent.Owner, useDelay)))
return; return;
_source.SetIgnited(ent.Owner); var target = ent.Comp.TargetUser ? args.User : ent.Owner;
_audio.PlayPvs(ent.Comp.IgniteSound, ent);
_useDelay.TryResetDelay((ent.Owner, useDelay)); if (target == null)
return;
_source.SetIgnited(target.Value);
ent.Comp.IgnitedUntil = _timing.CurTime + ent.Comp.IgnitedTime; ent.Comp.IgnitedUntil = _timing.CurTime + ent.Comp.IgnitedTime;
Dirty(ent);
args.Handled = true;
} }
} }

View File

@@ -0,0 +1,51 @@
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);
}
}
}

View File

@@ -0,0 +1,49 @@
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);
}
}

View File

@@ -0,0 +1,48 @@
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);
}
}
}
}

View File

@@ -0,0 +1,68 @@
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;
}
}

View File

@@ -1,13 +1,13 @@
using Content.Server.Explosion.EntitySystems; using Content.Server.Chat.Systems;
using Content.Shared.Timing; using Content.Shared.Trigger;
using Content.Shared.Trigger.Components.Effects;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Random; using Robust.Shared.Random;
namespace Content.Server.Chat.Systems; namespace Content.Server.Trigger.Systems;
public sealed class SpeakOnTriggerSystem : EntitySystem public sealed class SpeakOnTriggerSystem : EntitySystem
{ {
[Dependency] private readonly UseDelaySystem _useDelay = default!;
[Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly ChatSystem _chat = default!; [Dependency] private readonly ChatSystem _chat = default!;
@@ -15,32 +15,34 @@ public sealed class SpeakOnTriggerSystem : EntitySystem
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<SpeakOnTriggerComponent, TriggerEvent>(OnTrigger); SubscribeLocalEvent<SpeakOnTriggerComponent, TriggerEvent>(OnTrigger);
} }
private void OnTrigger(Entity<SpeakOnTriggerComponent> ent, ref TriggerEvent args) private void OnTrigger(Entity<SpeakOnTriggerComponent> ent, ref TriggerEvent args)
{ {
TrySpeak(ent); if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
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)))
return; return;
var target = ent.Comp.TargetUser ? args.User : ent.Owner;
if (target == null)
return;
string message;
if (ent.Comp.Text != null)
message = Loc.GetString(ent.Comp.Text);
else
{
if (!_prototypeManager.TryIndex(ent.Comp.Pack, out var messagePack)) if (!_prototypeManager.TryIndex(ent.Comp.Pack, out var messagePack))
return; return;
message = Loc.GetString(_random.Pick(messagePack.Values));
var message = Loc.GetString(_random.Pick(messagePack.Values)); }
// Chatcode moment: messages starting with "." are considered radio messages. // Chatcode moment: messages starting with "." are considered radio messages.
// Prepending ">" forces the message to be spoken instead. // Prepending ">" forces the message to be spoken instead.
// TODO chat refactor: remove this // TODO chat refactor: remove this
message = '>' + message; message = '>' + message;
_chat.TrySendInGameICMessage(ent.Owner, message, InGameICChatType.Speak, true); _chat.TrySendInGameICMessage(target.Value, message, InGameICChatType.Speak, true);
args.Handled = true;
if (useDelay != null)
_useDelay.TryResetDelay((ent.Owner, useDelay));
} }
} }

View File

@@ -5,6 +5,7 @@ using Content.Shared.Database;
using Content.Shared.Inventory; using Content.Shared.Inventory;
using Content.Shared.Popups; using Content.Shared.Popups;
using Content.Shared.Storage; using Content.Shared.Storage;
using Content.Shared.Trigger;
using Robust.Server.Containers; using Robust.Server.Containers;
namespace Content.Server.VoiceTrigger; namespace Content.Server.VoiceTrigger;

View File

@@ -1,9 +1,9 @@
using Content.Shared.Explosion.Components.OnTrigger; using Content.Shared.Explosion.Components;
namespace Content.Server.Xenoarchaeology.Artifact.XAE.Components; namespace Content.Server.Xenoarchaeology.Artifact.XAE.Components;
/// <summary> /// <summary>
/// Activates 'trigger' for <see cref="ExplodeOnTriggerComponent"/>. /// Activates <see cref="ExplosiveComponent"/> to explode.
/// </summary> /// </summary>
[RegisterComponent, Access(typeof(XAETriggerExplosivesSystem))] [RegisterComponent, Access(typeof(XAETriggerExplosivesSystem))]
public sealed partial class XAETriggerExplosivesComponent : Component; public sealed partial class XAETriggerExplosivesComponent : Component;

View File

@@ -24,6 +24,11 @@ public abstract class SharedChatSystem : EntitySystem
public const char WhisperPrefix = ','; public const char WhisperPrefix = ',';
public const char DefaultChannelKey = 'h'; 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 ProtoId<RadioChannelPrototype> CommonChannel = "Common";
public static readonly string DefaultChannelPrefix = $"{RadioChannelPrefix}{DefaultChannelKey}"; public static readonly string DefaultChannelPrefix = $"{RadioChannelPrefix}{DefaultChannelKey}";

View File

@@ -1,10 +0,0 @@
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!;
}

View File

@@ -1,3 +1,4 @@
using Robust.Shared.Map;
using Robust.Shared.Timing; using Robust.Shared.Timing;
namespace Content.Shared.Emp; namespace Content.Shared.Emp;
@@ -7,4 +8,15 @@ public abstract class SharedEmpSystem : EntitySystem
[Dependency] protected readonly IGameTiming Timing = default!; [Dependency] protected readonly IGameTiming Timing = default!;
protected const string EmpDisabledEffectPrototype = "EffectEmpDisabled"; 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)
{
}
} }

View File

@@ -1,21 +0,0 @@
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;
}

View File

@@ -1,11 +0,0 @@
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
{
}

View File

@@ -1,66 +0,0 @@
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
}
}

Some files were not shown because too many files have changed in this diff Show More