Trigger Refactor (#39034)
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using Content.Server.Explosion.Components;
|
||||
using Content.Server.Weapons.Ranged.Systems;
|
||||
using Content.Shared.Trigger;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map;
|
||||
@@ -45,6 +46,9 @@ public sealed class ProjectileGrenadeSystem : EntitySystem
|
||||
/// </summary>
|
||||
private void OnFragTrigger(Entity<ProjectileGrenadeComponent> entity, ref TriggerEvent args)
|
||||
{
|
||||
if (args.Key != entity.Comp.TriggerKey)
|
||||
return;
|
||||
|
||||
FragmentIntoProjectiles(entity.Owner, entity.Comp);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
using Content.Shared.Explosion.Components;
|
||||
using Content.Shared.Throwing;
|
||||
using Content.Shared.Trigger;
|
||||
using Content.Shared.Trigger.Systems;
|
||||
using Content.Shared.Trigger.Components;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map;
|
||||
@@ -15,6 +18,7 @@ public sealed class ScatteringGrenadeSystem : SharedScatteringGrenadeSystem
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly ThrowingSystem _throwingSystem = default!;
|
||||
[Dependency] private readonly TransformSystem _transformSystem = default!;
|
||||
[Dependency] private readonly TriggerSystem _trigger = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -30,6 +34,9 @@ public sealed class ScatteringGrenadeSystem : SharedScatteringGrenadeSystem
|
||||
/// </summary>
|
||||
private void OnScatteringTrigger(Entity<ScatteringGrenadeComponent> entity, ref TriggerEvent args)
|
||||
{
|
||||
if (args.Key != entity.Comp.TriggerKey)
|
||||
return;
|
||||
|
||||
entity.Comp.IsTriggered = true;
|
||||
args.Handled = true;
|
||||
}
|
||||
@@ -76,13 +83,12 @@ public sealed class ScatteringGrenadeSystem : SharedScatteringGrenadeSystem
|
||||
|
||||
_throwingSystem.TryThrow(contentUid, direction, component.Velocity);
|
||||
|
||||
if (component.TriggerContents)
|
||||
if (component.TriggerContents && TryComp<TimerTriggerComponent>(contentUid, out var contentTimer))
|
||||
{
|
||||
additionalIntervalDelay += _random.NextFloat(component.IntervalBetweenTriggersMin, component.IntervalBetweenTriggersMax);
|
||||
var contentTimer = EnsureComp<ActiveTimerTriggerComponent>(contentUid);
|
||||
contentTimer.TimeRemaining = component.DelayBeforeTriggerContents + additionalIntervalDelay;
|
||||
var ev = new ActiveTimerTriggerEvent(contentUid, uid);
|
||||
RaiseLocalEvent(contentUid, ref ev);
|
||||
|
||||
_trigger.SetDelay((contentUid, contentTimer), TimeSpan.FromSeconds(component.DelayBeforeTriggerContents + additionalIntervalDelay));
|
||||
_trigger.ActivateTimerTrigger((contentUid, contentTimer));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
using Content.Server.Explosion.Components;
|
||||
using Content.Shared.Explosion.Components;
|
||||
using Content.Shared.Implants;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Mobs;
|
||||
|
||||
namespace Content.Server.Explosion.EntitySystems;
|
||||
|
||||
public sealed partial class TriggerSystem
|
||||
{
|
||||
private void InitializeMobstate()
|
||||
{
|
||||
SubscribeLocalEvent<TriggerOnMobstateChangeComponent, MobStateChangedEvent>(OnMobStateChanged);
|
||||
SubscribeLocalEvent<TriggerOnMobstateChangeComponent, SuicideEvent>(OnSuicide);
|
||||
|
||||
SubscribeLocalEvent<TriggerOnMobstateChangeComponent, ImplantRelayEvent<SuicideEvent>>(OnSuicideRelay);
|
||||
SubscribeLocalEvent<TriggerOnMobstateChangeComponent, ImplantRelayEvent<MobStateChangedEvent>>(OnMobStateRelay);
|
||||
}
|
||||
|
||||
private void OnMobStateChanged(EntityUid uid, TriggerOnMobstateChangeComponent component, MobStateChangedEvent args)
|
||||
{
|
||||
if (!component.MobState.Contains(args.NewMobState))
|
||||
return;
|
||||
|
||||
//This chains Mobstate Changed triggers with OnUseTimerTrigger if they have it
|
||||
//Very useful for things that require a mobstate change and a timer
|
||||
if (TryComp<OnUseTimerTriggerComponent>(uid, out var timerTrigger))
|
||||
{
|
||||
HandleTimerTrigger(
|
||||
uid,
|
||||
args.Origin,
|
||||
timerTrigger.Delay,
|
||||
timerTrigger.BeepInterval,
|
||||
timerTrigger.InitialBeepDelay,
|
||||
timerTrigger.BeepSound);
|
||||
}
|
||||
else
|
||||
Trigger(uid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the user has any implants that prevent suicide to avoid some cheesy strategies
|
||||
/// Prevents suicide by handling the event without killing the user
|
||||
/// </summary>
|
||||
private void OnSuicide(EntityUid uid, TriggerOnMobstateChangeComponent component, SuicideEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
if (!component.PreventSuicide)
|
||||
return;
|
||||
|
||||
_popupSystem.PopupEntity(Loc.GetString("suicide-prevented"), args.Victim, args.Victim);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnSuicideRelay(EntityUid uid, TriggerOnMobstateChangeComponent component, ImplantRelayEvent<SuicideEvent> args)
|
||||
{
|
||||
OnSuicide(uid, component, args.Event);
|
||||
}
|
||||
|
||||
private void OnMobStateRelay(EntityUid uid, TriggerOnMobstateChangeComponent component, ImplantRelayEvent<MobStateChangedEvent> args)
|
||||
{
|
||||
OnMobStateChanged(uid, component, args.Event);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user