-
Notifications
You must be signed in to change notification settings - Fork 718
Expand file tree
/
Copy pathSharedContainerSystem.Insert.cs
More file actions
250 lines (208 loc) · 11.7 KB
/
Copy pathSharedContainerSystem.Insert.cs
File metadata and controls
250 lines (208 loc) · 11.7 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
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics.Components;
using Robust.Shared.Utility;
using System;
using System.Numerics;
namespace Robust.Shared.Containers;
public abstract partial class SharedContainerSystem
{
/// <summary>
/// Attempts to insert the entity into this container.
/// </summary>
/// <remarks>
/// If the insertion is successful, the inserted entity will end up parented to the
/// container entity, and the inserted entity's local position will be set to the zero vector.
/// </remarks>
/// <param name="toInsert">The entity to insert.</param>
/// <param name="container">The container to insert into.</param>
/// <param name="containerXform">The container's transform component.</param>
/// <param name="force">Whether to bypass normal insertion checks.</param>
/// <returns>False if the entity could not be inserted.</returns>
/// <exception cref="InvalidOperationException">
/// Thrown if this container is a child of the entity,
/// which would cause infinite loops.
/// </exception>
public bool Insert(Entity<TransformComponent?, MetaDataComponent?, PhysicsComponent?> toInsert,
BaseContainer container,
TransformComponent? containerXform = null,
bool force = false)
{
var (uid, transform, meta, physics) = toInsert;
// Cannot Use Resolve(ref toInsert) as the physics component is optional
if (!Resolve(uid, ref transform, ref meta))
return false;
DebugTools.AssertOwner(container.Owner, containerXform);
DebugTools.AssertOwner(toInsert, physics);
DebugTools.Assert(!container.ExpectedEntities.Contains(GetNetEntity(toInsert)), "entity is expected");
DebugTools.Assert(container.Manager.Containers.ContainsKey(container.ID), "manager does not own the container");
// If someone is attempting to insert an entity into a container that is getting deleted, then we will
// automatically delete that entity. I.e., the insertion automatically "succeeds" and both entities get deleted.
// This is consistent with what happens if you attempt to attach an entity to a terminating parent.
if (!TryComp(container.Owner, out MetaDataComponent? ownerMeta))
{
Log.Error($"Attempted to insert an entity {ToPrettyString(toInsert)} into a non-existent entity. Trace: {Environment.StackTrace}");
QueueDel(toInsert);
return false;
}
if (ownerMeta.EntityLifeStage >= EntityLifeStage.Terminating)
{
Log.Error($"Attempted to insert an entity {ToPrettyString(toInsert)} into an entity that is terminating. Entity: {ToPrettyString(container.Owner)}. Trace: {Environment.StackTrace}");
QueueDel(toInsert);
return false;
}
//Verify we can insert into this container
if (!force && !CanInsert(uid, container, containerXform: containerXform))
return false;
if (meta.EntityLifeStage >= EntityLifeStage.Terminating)
{
Log.Error($"Attempted to insert a terminating entity {ToPrettyString(uid)} into a container {container.ID} in entity: {ToPrettyString(container.Owner)}. Trace: {Environment.StackTrace}");
return false;
}
// remove from any old containers.
if ((meta.Flags & MetaDataFlags.InContainer) != 0 &&
TryComp(transform.ParentUid, out ContainerManagerComponent? oldManager) &&
TryGetContainingContainer(transform.ParentUid, toInsert, out var oldContainer, oldManager) &&
!Remove((uid, transform, meta), oldContainer, reparent: false, force: false))
{
// failed to remove from container --> cannot insert.
return false;
}
// Update metadata first, so that parent change events can check IsInContainer.
DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) == 0, "invalid metadata flags before insertion");
meta.Flags |= MetaDataFlags.InContainer;
// Remove the entity and any children from broadphases.
// This is done before changing can collide to avoid unecceary updates.
// TODO maybe combine with RecursivelyUpdatePhysics to avoid fetching components and iterating parents twice?
_lookup.RemoveFromEntityTree(toInsert, transform);
DebugTools.Assert(transform.Broadphase == null || !transform.Broadphase.Value.IsValid(), "invalid broadphase");
// Avoid unnecessary broadphase updates while unanchoring, changing physics collision, and re-parenting.
var old = transform.Broadphase;
transform.Broadphase = BroadphaseData.Invalid;
// Unanchor the entity (without changing physics body types).
_transform.Unanchor(toInsert, transform, false);
// Next, update physics. Note that this cannot just be done in the physics system via parent change events,
// because the insertion may not result in a parent change. This could alternatively be done via a
// got-inserted event, but really that event should run after the entity was actually inserted (so that
// parent/map have updated). But we are better of disabling collision before doing map/parent changes.
PhysicsQuery.Resolve(toInsert, ref physics, logMissing: false);
RecursivelyUpdatePhysics((toInsert, transform, physics));
// Attach to new parent
var oldParent = transform.ParentUid;
_transform.SetCoordinates(toInsert, transform, new EntityCoordinates(container.Owner, Vector2.Zero), Angle.Zero);
transform.Broadphase = old;
// the transform.AttachParent() could previously result in the flag being unset, so check that this hasn't happened.
DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) != 0, "invalid metadata flags after insertion");
// Implementation specific insert logic
container.InternalInsert(toInsert, EntityManager);
// Update any relevant joint relays
// Can't be done above as the container flag isn't set yet.
RecursivelyUpdateJoints((toInsert, transform));
// Raise container events (after re-parenting and internal remove).
RaiseLocalEvent(container.Owner, new EntInsertedIntoContainerMessage(toInsert, oldParent, container), true);
RaiseLocalEvent(toInsert, new EntGotInsertedIntoContainerMessage(toInsert, container), true);
// The sheer number of asserts tells you about how little I trust container and parenting code.
DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) != 0, "invalid metadata flags after events");
DebugTools.Assert(!transform.Anchored, "entity is anchored");
DebugTools.AssertEqual(transform.ParentUid, container.Owner, "Wrong parent");
DebugTools.AssertEqual(transform.LocalPosition, Vector2.Zero);
DebugTools.Assert(MathHelper.CloseTo(transform.LocalRotation.Theta, Angle.Zero), "Angle is not zero");
DebugTools.Assert(!PhysicsQuery.TryGetComponent(toInsert, out var phys) || (!phys.Awake && !phys.CanCollide), "Invalid physics");
Dirty(container.Owner, container.Manager);
return true;
}
/// <inheritdoc cref="InsertOrDrop(Entity{TransformComponent?,MetaDataComponent?,PhysicsComponent?},BaseContainer,TransformComponent?)"/>
public bool InsertOrDrop(Entity<ContainerManagerComponent?, TransformComponent?> container, Entity<TransformComponent?, MetaDataComponent?, PhysicsComponent?> toInsert,
string containerId)
{
if (TryGetContainer(container, containerId, out var baseContainer, container))
return InsertOrDrop(toInsert, baseContainer, container.Comp2);
_transform.DropNextTo(toInsert, (container.Owner, container.Comp2));
return false;
}
/// <summary>
/// Attempts to insert an entity into a container. If it fails, it will instead drop the entity next to the
/// container entity.
/// </summary>
/// <returns>Whether or not the entity was successfully inserted</returns>
public bool InsertOrDrop(Entity<TransformComponent?, MetaDataComponent?, PhysicsComponent?> toInsert,
BaseContainer container,
TransformComponent? containerXform = null)
{
if (!Resolve(toInsert.Owner, ref toInsert.Comp1) || !Resolve(container.Owner, ref containerXform))
return false;
if (Insert(toInsert, container, containerXform))
return true;
_transform.DropNextTo(toInsert, (container.Owner, containerXform));
return false;
}
/// <summary>
/// Checks if the entity can be inserted into the given container.
/// </summary>
/// <param name="assumeEmpty">If true, this will check whether the entity could be inserted if the container were
/// empty.</param>
public bool CanInsert(
EntityUid toInsert,
BaseContainer container,
bool assumeEmpty = false,
TransformComponent? containerXform = null)
{
if (container.Owner == toInsert)
return false;
if (!assumeEmpty && container.Contains(toInsert))
return false;
if (!container.CanInsert(toInsert, assumeEmpty, EntityManager))
return false;
// no, you can't put maps or grids into containers
if (_mapQuery.HasComponent(toInsert) || _gridQuery.HasComponent(toInsert))
return false;
// Prevent circular insertion.
if (_transform.ContainsEntity(toInsert, (container.Owner, containerXform)))
return false;
var insertAttemptEvent = new ContainerIsInsertingAttemptEvent(container, toInsert, assumeEmpty);
RaiseLocalEvent(container.Owner, insertAttemptEvent, true);
if (insertAttemptEvent.Cancelled)
return false;
var gettingInsertedAttemptEvent = new ContainerGettingInsertedAttemptEvent(container, toInsert, assumeEmpty);
RaiseLocalEvent(toInsert, gettingInsertedAttemptEvent, true);
return !gettingInsertedAttemptEvent.Cancelled;
}
private void RecursivelyUpdatePhysics(Entity<TransformComponent, PhysicsComponent?> entity)
{
if (entity.Comp2 is { } physics)
{
// TODO CONTAINER
// Is this actually needed?
// I.e., shouldn't this just do a if (_timing.ApplyingState) return
// Here we intentionally don't dirty the physics comp. Client-side state handling will apply these same
// changes. This also ensures that the server doesn't have to send the physics comp state to every
// player for any entity inside of a container during init.
_physics.SetLinearVelocity(entity, Vector2.Zero, false, body: physics);
_physics.SetAngularVelocity(entity, 0, false, body: physics);
_physics.SetCanCollide(entity, false, false, body: physics);
}
foreach (var child in entity.Comp1._children)
{
var childXform = TransformQuery.GetComponent(child);
PhysicsQuery.TryGetComponent(child, out var childPhysics);
RecursivelyUpdatePhysics((child, childXform, childPhysics));
}
}
internal void RecursivelyUpdateJoints(Entity<TransformComponent> entity)
{
if (_timing.ApplyingState)
return;
if (JointQuery.TryGetComponent(entity, out var jointComp))
{
// TODO: This is going to be going up while joints going down, although these aren't too common
// in SS14 atm.
_joint.RefreshRelay(entity, jointComp);
}
foreach (var child in entity.Comp._children)
{
var childXform = TransformQuery.GetComponent(child);
RecursivelyUpdateJoints((child, childXform));
}
}
}