forked from Rxup/space-station-14
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathActionContainerSystem.cs
368 lines (308 loc) · 13.8 KB
/
ActionContainerSystem.cs
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
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Shared.Ghost;
using Content.Shared.Mind;
using Content.Shared.Mind.Components;
using Robust.Shared.Containers;
using Robust.Shared.Network;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Shared.Actions;
/// <summary>
/// Handles storing & spawning action entities in a container.
/// </summary>
public sealed class ActionContainerSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly INetManager _netMan = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedMindSystem _mind = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ActionsContainerComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<ActionsContainerComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<ActionsContainerComponent, EntRemovedFromContainerMessage>(OnEntityRemoved);
SubscribeLocalEvent<ActionsContainerComponent, EntInsertedIntoContainerMessage>(OnEntityInserted);
SubscribeLocalEvent<ActionsContainerComponent, ActionAddedEvent>(OnActionAdded);
SubscribeLocalEvent<ActionsContainerComponent, MindAddedMessage>(OnMindAdded);
SubscribeLocalEvent<ActionsContainerComponent, MindRemovedMessage>(OnMindRemoved);
}
private void OnMindAdded(EntityUid uid, ActionsContainerComponent component, MindAddedMessage args)
{
if (!_mind.TryGetMind(uid, out var mindId, out _))
return;
if (!TryComp<ActionsContainerComponent>(mindId, out var mindActionContainerComp))
return;
if (!HasComp<GhostComponent>(uid) && mindActionContainerComp.Container.ContainedEntities.Count > 0 )
_actions.GrantContainedActions(uid, mindId);
}
private void OnMindRemoved(EntityUid uid, ActionsContainerComponent component, MindRemovedMessage args)
{
_actions.RemoveProvidedActions(uid, args.Mind);
}
/// <summary>
/// Spawns a new action entity and adds it to the given container.
/// </summary>
public EntityUid? AddAction(EntityUid uid, string actionPrototypeId, ActionsContainerComponent? comp = null)
{
EntityUid? result = default;
EnsureAction(uid, ref result, actionPrototypeId, comp);
return result;
}
/// <summary>
/// Ensures that a given entityUid refers to a valid entity action contained by the given container.
/// If the entity does not exist, it will attempt to spawn a new action.
/// Returns false if the given entity exists, but is not in a valid state.
/// </summary>
public bool EnsureAction(EntityUid uid,
[NotNullWhen(true)] ref EntityUid? actionId,
string actionPrototypeId,
ActionsContainerComponent? comp = null)
{
return EnsureAction(uid, ref actionId, out _, actionPrototypeId, comp);
}
/// <inheritdoc cref="EnsureAction(Robust.Shared.GameObjects.EntityUid,ref System.Nullable{Robust.Shared.GameObjects.EntityUid},string?,Content.Shared.Actions.ActionsContainerComponent?)"/>
public bool EnsureAction(EntityUid uid,
[NotNullWhen(true)] ref EntityUid? actionId,
[NotNullWhen(true)] out BaseActionComponent? action,
string? actionPrototypeId,
ActionsContainerComponent? comp = null)
{
action = null;
DebugTools.AssertOwner(uid, comp);
comp ??= EnsureComp<ActionsContainerComponent>(uid);
if (Exists(actionId))
{
if (!comp.Container.Contains(actionId.Value))
{
Log.Error($"Action {ToPrettyString(actionId.Value)} is not contained in the expected container {ToPrettyString(uid)}");
return false;
}
if (!_actions.TryGetActionData(actionId, out action))
return false;
DebugTools.Assert(Transform(actionId.Value).ParentUid == uid);
DebugTools.Assert(_container.IsEntityInContainer(actionId.Value));
DebugTools.Assert(action.Container == uid);
return true;
}
// Null prototypes are never valid entities, they mean that someone didn't provide a proper prototype.
if (actionPrototypeId == null)
return false;
// Client cannot predict entity spawning.
if (_netMan.IsClient && !IsClientSide(uid))
return false;
actionId = Spawn(actionPrototypeId);
if (AddAction(uid, actionId.Value, action, comp) && _actions.TryGetActionData(actionId, out action))
return true;
Del(actionId.Value);
actionId = null;
return false;
}
/// <summary>
/// Transfers an action from one container to another, while keeping the attached entity the same.
/// </summary>
/// <remarks>
/// While the attached entity should be the same at the end, this will actually remove and then re-grant the action.
/// </remarks>
public void TransferAction(
EntityUid actionId,
EntityUid newContainer,
BaseActionComponent? action = null,
ActionsContainerComponent? container = null)
{
if (!_actions.ResolveActionData(actionId, ref action))
return;
if (action.Container == newContainer)
return;
var attached = action.AttachedEntity;
if (!AddAction(newContainer, actionId, action, container))
return;
DebugTools.AssertEqual(action.Container, newContainer);
DebugTools.AssertEqual(action.AttachedEntity, attached);
}
/// <summary>
/// Transfers all actions from one container to another, while keeping the attached entity the same.
/// </summary>
/// <remarks>
/// While the attached entity should be the same at the end, this will actually remove and then re-grant the action.
/// </remarks>
public void TransferAllActions(
EntityUid from,
EntityUid to,
ActionsContainerComponent? oldContainer = null,
ActionsContainerComponent? newContainer = null)
{
if (!Resolve(from, ref oldContainer) || !Resolve(to, ref newContainer))
return;
foreach (var action in oldContainer.Container.ContainedEntities.ToArray())
{
TransferAction(action, to, container: newContainer);
}
DebugTools.AssertEqual(oldContainer.Container.Count, 0);
}
/// <summary>
/// Transfers an actions from one container to another, while changing the attached entity.
/// </summary>
/// <remarks>
/// This will actually remove and then re-grant the action.
/// Useful where you need to transfer from one container to another but also change the attached entity (ie spellbook > mind > user)
/// </remarks>
public void TransferActionWithNewAttached(
EntityUid actionId,
EntityUid newContainer,
EntityUid newAttached,
BaseActionComponent? action = null,
ActionsContainerComponent? container = null)
{
if (!_actions.ResolveActionData(actionId, ref action))
return;
if (action.Container == newContainer)
return;
var attached = newAttached;
if (!AddAction(newContainer, actionId, action, container))
return;
DebugTools.AssertEqual(action.Container, newContainer);
_actions.AddActionDirect(newAttached, actionId, action: action);
DebugTools.AssertEqual(action.AttachedEntity, attached);
}
/// <summary>
/// Transfers all actions from one container to another, while changing the attached entity.
/// </summary>
/// <remarks>
/// This will actually remove and then re-grant the action.
/// Useful where you need to transfer from one container to another but also change the attached entity (ie spellbook > mind > user)
/// </remarks>
public void TransferAllActionsWithNewAttached(
EntityUid from,
EntityUid to,
EntityUid newAttached,
ActionsContainerComponent? oldContainer = null,
ActionsContainerComponent? newContainer = null)
{
if (!Resolve(from, ref oldContainer) || !Resolve(to, ref newContainer))
return;
foreach (var action in oldContainer.Container.ContainedEntities.ToArray())
{
TransferActionWithNewAttached(action, to, newAttached, container: newContainer);
}
DebugTools.AssertEqual(oldContainer.Container.Count, 0);
}
/// <summary>
/// Adds a pre-existing action to an action container. If the action is already in some container it will first remove it.
/// </summary>
public bool AddAction(EntityUid uid, EntityUid actionId, BaseActionComponent? action = null, ActionsContainerComponent? comp = null)
{
if (!_actions.ResolveActionData(actionId, ref action))
return false;
if (action.Container != null)
RemoveAction(actionId, action);
DebugTools.AssertOwner(uid, comp);
comp ??= EnsureComp<ActionsContainerComponent>(uid);
if (!_container.Insert(actionId, comp.Container))
{
Log.Error($"Failed to insert action {ToPrettyString(actionId)} into {ToPrettyString(uid)}");
return false;
}
// Container insert events should have updated the component's fields:
DebugTools.Assert(comp.Container.Contains(actionId));
DebugTools.Assert(action.Container == uid);
return true;
}
/// <summary>
/// Removes an action from its container and any action-performer and moves the action to null-space
/// </summary>
public void RemoveAction(EntityUid actionId, BaseActionComponent? action = null)
{
if (!_actions.ResolveActionData(actionId, ref action))
return;
if (action.Container == null)
return;
_transform.DetachEntity(actionId, Transform(actionId));
// Container removal events should have removed the action from the action container.
// However, just in case the container was already deleted we will still manually clear the container field
if (action.Container != null)
{
if (Exists(action.Container))
Log.Error($"Failed to remove action {ToPrettyString(actionId)} from its container {ToPrettyString(action.Container)}?");
action.Container = null;
}
// If the action was granted to some entity, then the removal from the container should have automatically removed it.
// However, if the action was granted without ever being placed in an action container, it will not have been removed.
// Therefore, to ensure that the behaviour of the method is consistent we will also explicitly remove the action.
if (action.AttachedEntity != null)
_actions.RemoveAction(action.AttachedEntity.Value, actionId, action: action);
}
private void OnInit(EntityUid uid, ActionsContainerComponent component, ComponentInit args)
{
component.Container = _container.EnsureContainer<Container>(uid, ActionsContainerComponent.ContainerId);
}
private void OnShutdown(EntityUid uid, ActionsContainerComponent component, ComponentShutdown args)
{
if (_timing.ApplyingState && component.NetSyncEnabled)
return; // The game state should handle the container removal & action deletion.
_container.ShutdownContainer(component.Container);
}
private void OnEntityInserted(EntityUid uid, ActionsContainerComponent component, EntInsertedIntoContainerMessage args)
{
if (args.Container.ID != ActionsContainerComponent.ContainerId)
return;
if (!_actions.TryGetActionData(args.Entity, out var data))
return;
if (data.Container != uid)
{
data.Container = uid;
Dirty(args.Entity, data);
}
var ev = new ActionAddedEvent(args.Entity, data);
RaiseLocalEvent(uid, ref ev);
}
private void OnEntityRemoved(EntityUid uid, ActionsContainerComponent component, EntRemovedFromContainerMessage args)
{
if (args.Container.ID != ActionsContainerComponent.ContainerId)
return;
if (!_actions.TryGetActionData(args.Entity, out var data, false))
return;
var ev = new ActionRemovedEvent(args.Entity, data);
RaiseLocalEvent(uid, ref ev);
if (data.Container == null)
return;
data.Container = null;
Dirty(args.Entity, data);
}
private void OnActionAdded(EntityUid uid, ActionsContainerComponent component, ActionAddedEvent args)
{
if (TryComp<MindComponent>(uid, out var mindComp) && mindComp.OwnedEntity != null && HasComp<ActionsContainerComponent>(mindComp.OwnedEntity.Value))
_actions.GrantContainedAction(mindComp.OwnedEntity.Value, uid, args.Action);
}
}
/// <summary>
/// Raised directed at an action container when a new action entity gets inserted.
/// </summary>
[ByRefEvent]
public readonly struct ActionAddedEvent
{
public readonly EntityUid Action;
public readonly BaseActionComponent Component;
public ActionAddedEvent(EntityUid action, BaseActionComponent component)
{
Action = action;
Component = component;
}
}
/// <summary>
/// Raised directed at an action container when an action entity gets removed.
/// </summary>
[ByRefEvent]
public readonly struct ActionRemovedEvent
{
public readonly EntityUid Action;
public readonly BaseActionComponent Component;
public ActionRemovedEvent(EntityUid action, BaseActionComponent component)
{
Action = action;
Component = component;
}
}