mirror of
https://github.com/WWhiteDreamProject/wwdpublic.git
synced 2026-04-17 13:37:47 +03:00
## Mirror of PR #22962: [Landmine stepoff](https://github.com/space-wizards/space-station-14/pull/22962) from <img src="https://avatars.githubusercontent.com/u/10567778?v=4" alt="space-wizards" width="22"/> [space-wizards](https://github.com/space-wizards)/[space-station-14](https://github.com/space-wizards/space-station-14) ###### `54dd273f660d6d8d523d0771bb8d8437373b082e` PR opened by <img src="https://avatars.githubusercontent.com/u/59531932?v=4" width="16"/><a href="https://github.com/YuriyKiss"> YuriyKiss</a> at 2023-12-25 12:38:55 UTC --- PR changed 13 files with 95 additions and 47 deletions. The PR had the following labels: - Status: Awaiting Changes --- <details open="true"><summary><h1>Original Body</h1></summary> > ## About the PR > Landmine will explode when player steps off it. Landmine will make a sound effect when player steps on it > Close #21658 (Issue has some other suggestions, turn them into separate issues if relevant) > > Requires https://github.com/space-wizards/RobustToolbox/pull/4883 > > ## Why / Balance > IRL some landmines will only trigger after you release the pressure put on them. > > ## Technical details > There are two landmine modes now, one will make landmine trigger immediately after player steps on them another one will only trigger after player steps off the landmine (this is the default now). > > ## Media > Landmine stepoff in action: > > https://github.com/space-wizards/space-station-14/assets/59531932/6e884619-7217-4301-bd78-6e843e0d78f1 > > - [X] I have added screenshots/videos to this PR showcasing its changes ingame, **or** this PR does not require an ingame showcase > > ## Breaking changes > > > **Changelog** > - tweak: landmines will trigger after you step off them > - add: landmine trigger sound > </details> Signed-off-by: VMSolidus <evilexecutive@gmail.com> Co-authored-by: SimpleStation14 <Unknown> Co-authored-by: VMSolidus <evilexecutive@gmail.com>
262 lines
8.5 KiB
C#
262 lines
8.5 KiB
C#
using Content.Shared.Gravity;
|
|
using Content.Shared.StepTrigger.Components;
|
|
using Robust.Shared.Map.Components;
|
|
using Robust.Shared.Physics;
|
|
using Robust.Shared.Physics.Components;
|
|
using Robust.Shared.Physics.Events;
|
|
|
|
namespace Content.Shared.StepTrigger.Systems;
|
|
|
|
public sealed class StepTriggerSystem : EntitySystem
|
|
{
|
|
[Dependency] private readonly EntityLookupSystem _entityLookup = default!;
|
|
[Dependency] private readonly SharedGravitySystem _gravity = default!;
|
|
[Dependency] private readonly SharedMapSystem _map = default!;
|
|
|
|
public override void Initialize()
|
|
{
|
|
UpdatesOutsidePrediction = true;
|
|
SubscribeLocalEvent<StepTriggerComponent, AfterAutoHandleStateEvent>(TriggerHandleState);
|
|
|
|
SubscribeLocalEvent<StepTriggerComponent, StartCollideEvent>(OnStartCollide);
|
|
SubscribeLocalEvent<StepTriggerComponent, EndCollideEvent>(OnEndCollide);
|
|
#if DEBUG
|
|
SubscribeLocalEvent<StepTriggerComponent, ComponentStartup>(OnStartup);
|
|
}
|
|
private void OnStartup(EntityUid uid, StepTriggerComponent component, ComponentStartup args)
|
|
{
|
|
if (!component.Active)
|
|
return;
|
|
|
|
if (!TryComp(uid, out FixturesComponent? fixtures) || fixtures.FixtureCount == 0)
|
|
Log.Warning($"{ToPrettyString(uid)} has an active step trigger without any fixtures.");
|
|
#endif
|
|
}
|
|
|
|
public override void Update(float frameTime)
|
|
{
|
|
var query = GetEntityQuery<PhysicsComponent>();
|
|
var enumerator = EntityQueryEnumerator<StepTriggerActiveComponent, StepTriggerComponent, TransformComponent>();
|
|
|
|
while (enumerator.MoveNext(out var uid, out var active, out var trigger, out var transform))
|
|
{
|
|
if (!Update(uid, trigger, transform, query))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
RemCompDeferred(uid, active);
|
|
}
|
|
}
|
|
|
|
private bool Update(EntityUid uid, StepTriggerComponent component, TransformComponent transform, EntityQuery<PhysicsComponent> query)
|
|
{
|
|
if (!component.Active ||
|
|
component.Colliding.Count == 0)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (component.Blacklist != null && TryComp<MapGridComponent>(transform.GridUid, out var grid))
|
|
{
|
|
var positon = _map.LocalToTile(uid, grid, transform.Coordinates);
|
|
var anch = _map.GetAnchoredEntitiesEnumerator(uid, grid, positon);
|
|
|
|
while (anch.MoveNext(out var ent))
|
|
{
|
|
if (ent == uid)
|
|
continue;
|
|
|
|
if (component.Blacklist.IsValid(ent.Value, EntityManager) == true)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (var otherUid in component.Colliding)
|
|
{
|
|
UpdateColliding(uid, component, transform, otherUid, query);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private void UpdateColliding(EntityUid uid, StepTriggerComponent component, TransformComponent ownerXform, EntityUid otherUid, EntityQuery<PhysicsComponent> query)
|
|
{
|
|
if (!query.TryGetComponent(otherUid, out var otherPhysics))
|
|
return;
|
|
|
|
var otherXform = Transform(otherUid);
|
|
// TODO: This shouldn't be calculating based on world AABBs.
|
|
var ourAabb = _entityLookup.GetAABBNoContainer(uid, ownerXform.LocalPosition, ownerXform.LocalRotation);
|
|
var otherAabb = _entityLookup.GetAABBNoContainer(otherUid, otherXform.LocalPosition, otherXform.LocalRotation);
|
|
|
|
if (!ourAabb.Intersects(otherAabb))
|
|
{
|
|
if (component.CurrentlySteppedOn.Remove(otherUid))
|
|
{
|
|
Dirty(uid, component);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// max 'area of enclosure' between the two aabbs
|
|
// this is hard to explain
|
|
var intersect = Box2.Area(otherAabb.Intersect(ourAabb));
|
|
var ratio = Math.Max(intersect / Box2.Area(otherAabb), intersect / Box2.Area(ourAabb));
|
|
if (otherPhysics.LinearVelocity.Length() < component.RequiredTriggeredSpeed
|
|
|| component.CurrentlySteppedOn.Contains(otherUid)
|
|
|| ratio < component.IntersectRatio
|
|
|| !CanTrigger(uid, otherUid, component))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (component.StepOn)
|
|
{
|
|
var evStep = new StepTriggeredOnEvent(uid, otherUid);
|
|
RaiseLocalEvent(uid, ref evStep);
|
|
}
|
|
else
|
|
{
|
|
var evStep = new StepTriggeredOffEvent(uid, otherUid);
|
|
RaiseLocalEvent(uid, ref evStep);
|
|
}
|
|
|
|
component.CurrentlySteppedOn.Add(otherUid);
|
|
Dirty(uid, component);
|
|
}
|
|
|
|
private bool CanTrigger(EntityUid uid, EntityUid otherUid, StepTriggerComponent component)
|
|
{
|
|
if (!component.Active || component.CurrentlySteppedOn.Contains(otherUid))
|
|
return false;
|
|
|
|
// Can't trigger if we don't ignore weightless entities
|
|
// and the entity is flying or currently weightless
|
|
// Makes sense simulation wise to have this be part of steptrigger directly IMO
|
|
if (!component.IgnoreWeightless && TryComp<PhysicsComponent>(otherUid, out var physics) &&
|
|
(physics.BodyStatus == BodyStatus.InAir || _gravity.IsWeightless(otherUid, physics)))
|
|
return false;
|
|
|
|
var msg = new StepTriggerAttemptEvent { Source = uid, Tripper = otherUid };
|
|
|
|
RaiseLocalEvent(uid, ref msg);
|
|
|
|
return msg.Continue && !msg.Cancelled;
|
|
}
|
|
|
|
private void OnStartCollide(EntityUid uid, StepTriggerComponent component, ref StartCollideEvent args)
|
|
{
|
|
var otherUid = args.OtherEntity;
|
|
|
|
if (!args.OtherFixture.Hard)
|
|
return;
|
|
|
|
if (!CanTrigger(uid, otherUid, component))
|
|
return;
|
|
|
|
EnsureComp<StepTriggerActiveComponent>(uid);
|
|
|
|
if (component.Colliding.Add(otherUid))
|
|
{
|
|
Dirty(uid, component);
|
|
}
|
|
}
|
|
|
|
private void OnEndCollide(EntityUid uid, StepTriggerComponent component, ref EndCollideEvent args)
|
|
{
|
|
var otherUid = args.OtherEntity;
|
|
|
|
if (!component.Colliding.Remove(otherUid))
|
|
return;
|
|
|
|
component.CurrentlySteppedOn.Remove(otherUid);
|
|
Dirty(uid, component);
|
|
|
|
if (component.StepOn)
|
|
{
|
|
var evStepOff = new StepTriggeredOffEvent(uid, otherUid);
|
|
RaiseLocalEvent(uid, ref evStepOff);
|
|
}
|
|
|
|
if (component.Colliding.Count == 0)
|
|
{
|
|
RemCompDeferred<StepTriggerActiveComponent>(uid);
|
|
}
|
|
}
|
|
|
|
private void TriggerHandleState(EntityUid uid, StepTriggerComponent component, ref AfterAutoHandleStateEvent args)
|
|
{
|
|
if (component.Colliding.Count > 0)
|
|
{
|
|
EnsureComp<StepTriggerActiveComponent>(uid);
|
|
}
|
|
else
|
|
{
|
|
RemCompDeferred<StepTriggerActiveComponent>(uid);
|
|
}
|
|
}
|
|
|
|
public void SetIntersectRatio(EntityUid uid, float ratio, StepTriggerComponent? component = null)
|
|
{
|
|
if (!Resolve(uid, ref component))
|
|
return;
|
|
|
|
if (MathHelper.CloseToPercent(component.IntersectRatio, ratio))
|
|
return;
|
|
|
|
component.IntersectRatio = ratio;
|
|
Dirty(uid, component);
|
|
}
|
|
|
|
public void SetRequiredTriggerSpeed(EntityUid uid, float speed, StepTriggerComponent? component = null)
|
|
{
|
|
if (!Resolve(uid, ref component))
|
|
return;
|
|
|
|
if (MathHelper.CloseToPercent(component.RequiredTriggeredSpeed, speed))
|
|
return;
|
|
|
|
component.RequiredTriggeredSpeed = speed;
|
|
Dirty(uid, component);
|
|
}
|
|
|
|
public void SetActive(EntityUid uid, bool active, StepTriggerComponent? component = null)
|
|
{
|
|
if (!Resolve(uid, ref component))
|
|
return;
|
|
|
|
if (active == component.Active)
|
|
return;
|
|
|
|
component.Active = active;
|
|
Dirty(uid, component);
|
|
}
|
|
}
|
|
|
|
[ByRefEvent]
|
|
public struct StepTriggerAttemptEvent
|
|
{
|
|
public EntityUid Source;
|
|
public EntityUid Tripper;
|
|
public bool Continue;
|
|
/// <summary>
|
|
/// Set by systems which wish to cancel the step trigger event, regardless of event ordering.
|
|
/// </summary>
|
|
public bool Cancelled;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raised when an entity stands on a steptrigger initially (assuming it has both on and off states).
|
|
/// </summary>
|
|
[ByRefEvent]
|
|
public readonly record struct StepTriggeredOnEvent(EntityUid Source, EntityUid Tripper);
|
|
|
|
/// <summary>
|
|
/// Raised when an entity leaves a steptrigger if it has on and off states OR when an entity intersects a steptrigger.
|
|
/// </summary>
|
|
[ByRefEvent]
|
|
public readonly record struct StepTriggeredOffEvent(EntityUid Source, EntityUid Tripper);
|