-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathStepTriggerSystem.cs
More file actions
265 lines (216 loc) · 8.58 KB
/
StepTriggerSystem.cs
File metadata and controls
265 lines (216 loc) · 8.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
using Content.Shared.Gravity;
using Content.Shared.StepTrigger.Components;
using Content.Shared.Whitelist;
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 partial class StepTriggerSystem : EntitySystem
{
[Dependency] private EntityLookupSystem _entityLookup = default!;
[Dependency] private SharedGravitySystem _gravity = default!;
[Dependency] private SharedMapSystem _map = default!;
[Dependency] private EntityWhitelistSystem _whitelistSystem = default!;
[Dependency] private EntityQuery<PhysicsComponent> _physicsquery = 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 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))
{
continue;
}
RemCompDeferred(uid, active);
}
}
private bool Update(EntityUid uid, StepTriggerComponent component, TransformComponent transform)
{
if (!component.Active ||
component.Colliding.Count == 0)
{
return true;
}
if (component.Blacklist != null && TryComp<MapGridComponent>(transform.GridUid, out var grid))
{
var positon = _map.LocalToTile(transform.GridUid.Value, grid, transform.Coordinates);
var anch = _map.GetAnchoredEntitiesEnumerator(uid, grid, positon);
while (anch.MoveNext(out var ent))
{
if (ent == uid)
continue;
if (_whitelistSystem.IsWhitelistPass(component.Blacklist, ent.Value))
{
return false;
}
}
}
foreach (var otherUid in component.Colliding)
{
UpdateColliding(uid, component, transform, otherUid);
}
return false;
}
private void UpdateColliding(EntityUid uid, StepTriggerComponent component, TransformComponent ownerXform, EntityUid otherUid)
{
if (!_physicsquery.TryComp(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)))
return false;
var msg = new StepTriggerAttemptEvent { Source = uid, Tripper = otherUid };
RaiseLocalEvent(uid, ref msg);
RaiseLocalEvent(otherUid, ref msg); // Goobstation edit
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);