Client LuaCsForBarotrauma
BarotraumaShared/SharedSource/Networking/EntitySpawner.cs
4 using Microsoft.Xna.Framework;
5 using System;
6 using System.Collections.Generic;
7 using System.Linq;
8 
9 namespace Barotrauma
10 {
12  {
13  private enum SpawnableType { Item, Character };
14 
15  public interface IEntitySpawnInfo
16  {
18  void OnSpawned(Entity entity);
19  }
20 
22  {
23  public readonly ItemPrefab Prefab;
24 
25  public readonly Vector2 Position;
26  public readonly Inventory Inventory;
27  public readonly Submarine Submarine;
28  public readonly float Condition;
29  public readonly int Quality;
30 
31  public bool SpawnIfInventoryFull = true;
32  public bool IgnoreLimbSlots = false;
33  public InvSlotType Slot = InvSlotType.None;
34 
35  private readonly Action<Item> onSpawned;
36 
37  public ItemSpawnInfo(ItemPrefab prefab, Vector2 worldPosition, Action<Item> onSpawned, float? condition = null, int? quality = null)
38  {
39  Prefab = prefab ?? throw new ArgumentException("ItemSpawnInfo prefab cannot be null.");
40  Position = worldPosition;
41  Condition = condition ?? prefab.Health;
42  Quality = quality ?? 0;
43  this.onSpawned = onSpawned;
44  }
45 
46  public ItemSpawnInfo(ItemPrefab prefab, Vector2 position, Submarine sub, Action<Item> onSpawned, float? condition = null, int? quality = null)
47  {
48  Prefab = prefab ?? throw new ArgumentException("ItemSpawnInfo prefab cannot be null.");
49  Position = position;
50  Submarine = sub;
51  Condition = condition ?? prefab.Health;
52  Quality = quality ?? 0;
53  this.onSpawned = onSpawned;
54  }
55 
56  public ItemSpawnInfo(ItemPrefab prefab, Inventory inventory, Action<Item> onSpawned, float? condition = null, int? quality = null)
57  {
58  Prefab = prefab ?? throw new ArgumentException("ItemSpawnInfo prefab cannot be null.");
59  Inventory = inventory;
60  Condition = condition ?? prefab.Health;
61  Quality = quality ?? 0;
62  this.onSpawned = onSpawned;
63  }
64 
65  public Entity Spawn()
66  {
67  if (Prefab == null)
68  {
69  return null;
70  }
71  Item spawnedItem;
72  if (Inventory?.Owner != null)
73  {
75  {
76  return null;
77  }
78  spawnedItem = new Item(Prefab, Vector2.Zero, null)
79  {
82  };
83  var slot = Slot != InvSlotType.None ? Slot.ToEnumerable() : spawnedItem.AllowedSlots;
84  if (!Inventory.Owner.Removed && !Inventory.TryPutItem(spawnedItem, null, slot))
85  {
86  if (IgnoreLimbSlots)
87  {
88  for (int i = 0; i < Inventory.Capacity; i++)
89  {
90  if (Inventory.GetItemAt(i) == null)
91  {
92  Inventory.ForceToSlot(spawnedItem, i);
93  break;
94  }
95  }
96  }
97  spawnedItem.SetTransform(FarseerPhysics.ConvertUnits.ToSimUnits(Inventory.Owner?.WorldPosition ?? Vector2.Zero), spawnedItem.body?.Rotation ?? 0.0f, findNewHull: false);
98  }
99  }
100  else
101  {
102  spawnedItem = new Item(Prefab, Position, Submarine)
103  {
105  Quality = Quality
106  };
107  }
108  return spawnedItem;
109  }
110 
111  public void OnSpawned(Entity spawnedItem)
112  {
113  if (spawnedItem is not Item item) { throw new ArgumentException($"The entity passed to ItemSpawnInfo.OnSpawned must be an Item (value was {spawnedItem?.ToString() ?? "null"})."); }
114  onSpawned?.Invoke(item);
115  }
116  }
117 
118  class CharacterSpawnInfo : IEntitySpawnInfo
119  {
120  public readonly Identifier Identifier;
121  public readonly CharacterInfo CharacterInfo;
122 
123  public readonly Vector2 Position;
124  public readonly Submarine Submarine;
125 
126  private readonly Action<Character> onSpawned;
127 
128  public CharacterSpawnInfo(Identifier identifier, Vector2 worldPosition, Action<Character> onSpawn = null)
129  {
130  this.Identifier = identifier;
131  if (identifier.IsEmpty) { throw new ArgumentException($"{nameof(CharacterSpawnInfo)} identifier cannot be null."); }
132  Position = worldPosition;
133  this.onSpawned = onSpawn;
134  }
135 
136  public CharacterSpawnInfo(Identifier identifier, Vector2 position, Submarine sub, Action<Character> onSpawn = null)
137  {
138  this.Identifier = identifier;
139  if (identifier.IsEmpty) { throw new ArgumentException($"{nameof(CharacterSpawnInfo)} identifier cannot be null."); }
140  Position = position;
141  Submarine = sub;
142  this.onSpawned = onSpawn;
143  }
144 
145  public CharacterSpawnInfo(Identifier identifier, Vector2 position, CharacterInfo characterInfo, Action<Character> onSpawn = null) : this (identifier, position, onSpawn)
146  {
147  CharacterInfo = characterInfo;
148  }
149 
150  public Entity Spawn()
151  {
152  var character = Identifier.IsEmpty ? null :
153  Character.Create(Identifier,
155  ToolBox.RandomSeed(8), CharacterInfo, createNetworkEvent: false);
156  return character;
157  }
158 
159  public void OnSpawned(Entity spawnedCharacter)
160  {
161  if (!(spawnedCharacter is Character character)) { throw new ArgumentException($"The entity passed to CharacterSpawnInfo.OnSpawned must be a Character (value was {spawnedCharacter?.ToString() ?? "null"})."); }
162  onSpawned?.Invoke(character);
163  }
164  }
165 
166  class SubmarineSpawnInfo : IEntitySpawnInfo
167  {
168  public readonly string Name;
169 
170  public readonly Vector2 Position;
171 
172  private readonly Action<Character> onSpawned;
173 
174  public SubmarineSpawnInfo(string name, Vector2 worldPosition, Action<Character> onSpawn = null)
175  {
176  this.Name = name ?? throw new ArgumentException("ItemSpawnInfo prefab cannot be null.");
177  Position = worldPosition;
178  this.onSpawned = onSpawn;
179  }
180 
181 
182  public Entity Spawn()
183  {
184  var submarine = string.IsNullOrEmpty(Name) ? null :
185  new Submarine(SubmarineInfo.SavedSubmarines.First(s => s.Name.Equals(Name, StringComparison.OrdinalIgnoreCase)));
186  return submarine;
187  }
188 
189  public void OnSpawned(Entity spawnedCharacter)
190  {
191  if (!(spawnedCharacter is Character character)) { throw new ArgumentException($"The entity passed to CharacterSpawnInfo.OnSpawned must be a Character (value was {spawnedCharacter?.ToString() ?? "null"})."); }
192  onSpawned?.Invoke(character);
193  }
194  }
195 
196  private readonly Queue<Either<IEntitySpawnInfo, Entity>> spawnOrRemoveQueue;
197 
198  public abstract class SpawnOrRemove : NetEntityEvent.IData
199  {
200  public readonly Entity Entity;
201  public UInt16 ID => Entity.ID;
202 
203  public readonly UInt16 InventoryID;
204 
205  public readonly byte ItemContainerIndex;
206  public readonly int SlotIndex;
207 
208  public override string ToString()
209  {
210  return
211  "(" +
212  ((Entity as MapEntity)?.Name ?? "[NULL]") +
213  $", {ID}, {InventoryID}, {SlotIndex})";
214  }
215 
216  protected SpawnOrRemove(Entity entity)
217  {
218  Entity = entity;
219  if (!(entity is Item { ParentInventory: { Owner: { } } } item)) { return; }
220 
221  InventoryID = item.ParentInventory.Owner.ID;
222  SlotIndex = item.ParentInventory.FindIndex(item);
223  //find the index of the ItemContainer this item is inside to get the item to
224  //spawn in the correct inventory in multi-inventory items like fabricators
225  if (item.Container == null) { return; }
226 
227  foreach (ItemComponent component in item.Container.Components)
228  {
229  if (component is ItemContainer container &&
230  container.Inventory == item.ParentInventory)
231  {
232  ItemContainerIndex = (byte)item.Container.GetComponentIndex(component);
233  break;
234  }
235  }
236  }
237  }
238 
239  public sealed class SpawnEntity : SpawnOrRemove
240  {
241  public SpawnEntity(Entity entity) : base(entity) { }
242  public override string ToString()
243  => $"Spawn {base.ToString()}";
244  }
245 
246  public sealed class RemoveEntity : SpawnOrRemove
247  {
248  public RemoveEntity(Entity entity) : base(entity) { }
249  public override string ToString()
250  => $"Remove {base.ToString()}";
251  }
252 
253  public EntitySpawner()
254  : base(null, Entity.EntitySpawnerID)
255  {
256  spawnOrRemoveQueue = new Queue<Either<IEntitySpawnInfo, Entity>>();
257  }
258 
259  public override string ToString()
260  {
261  return "EntitySpawner";
262  }
263 
264  public void AddItemToSpawnQueue(ItemPrefab itemPrefab, Vector2 worldPosition, float? condition = null, int? quality = null, Action<Item> onSpawned = null)
265  {
266  if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
267  if (itemPrefab == null)
268  {
269  string errorMsg = "Attempted to add a null item to entity spawn queue.\n" + Environment.StackTrace.CleanupStackTrace();
270  DebugConsole.ThrowError(errorMsg);
271  GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue1:ItemPrefabNull", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
272  return;
273  }
274  spawnOrRemoveQueue.Enqueue(new ItemSpawnInfo(itemPrefab, worldPosition, onSpawned, condition, quality));
275  }
276 
277  public void AddItemToSpawnQueue(ItemPrefab itemPrefab, Vector2 position, Submarine sub, float? condition = null, int? quality = null, Action<Item> onSpawned = null)
278  {
279  if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
280  if (itemPrefab == null)
281  {
282  string errorMsg = "Attempted to add a null item to entity spawn queue.\n" + Environment.StackTrace.CleanupStackTrace();
283  DebugConsole.ThrowError(errorMsg);
284  GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue2:ItemPrefabNull", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
285  return;
286  }
287  spawnOrRemoveQueue.Enqueue(new ItemSpawnInfo(itemPrefab, position, sub, onSpawned, condition, quality));
288  }
289 
290  public void AddItemToSpawnQueue(ItemPrefab itemPrefab, Inventory inventory, float? condition = null, int? quality = null, Action<Item> onSpawned = null, bool spawnIfInventoryFull = true, bool ignoreLimbSlots = false, InvSlotType slot = InvSlotType.None)
291  {
292  if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
293  if (itemPrefab == null)
294  {
295  string errorMsg = "Attempted to add a null item to entity spawn queue.\n" + Environment.StackTrace.CleanupStackTrace();
296  DebugConsole.ThrowError(errorMsg);
297  GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue3:ItemPrefabNull", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
298  return;
299  }
300  spawnOrRemoveQueue.Enqueue(new ItemSpawnInfo(itemPrefab, inventory, onSpawned, condition, quality)
301  {
302  SpawnIfInventoryFull = spawnIfInventoryFull,
303  IgnoreLimbSlots = ignoreLimbSlots,
304  Slot = slot
305  });
306  }
307 
308  public void AddCharacterToSpawnQueue(Identifier speciesName, Vector2 worldPosition, Action<Character> onSpawn = null)
309  {
310  if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
311  if (speciesName.IsEmpty)
312  {
313  string errorMsg = "Attempted to add an empty/null species name to entity spawn queue.\n" + Environment.StackTrace.CleanupStackTrace();
314  DebugConsole.ThrowError(errorMsg);
315  GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue4:SpeciesNameNullOrEmpty", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
316  return;
317  }
318  spawnOrRemoveQueue.Enqueue(new CharacterSpawnInfo(speciesName, worldPosition, onSpawn));
319  }
320 
321  public void AddCharacterToSpawnQueue(Identifier speciesName, Vector2 position, Submarine sub, Action<Character> onSpawn = null)
322  {
323  if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
324  if (speciesName.IsEmpty)
325  {
326  string errorMsg = "Attempted to add an empty/null species name to entity spawn queue.\n" + Environment.StackTrace.CleanupStackTrace();
327  DebugConsole.ThrowError(errorMsg);
328  GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue5:SpeciesNameNullOrEmpty", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
329  return;
330  }
331  spawnOrRemoveQueue.Enqueue(new CharacterSpawnInfo(speciesName, position, sub, onSpawn));
332  }
333 
334  public void AddCharacterToSpawnQueue(Identifier speciesName, Vector2 worldPosition, CharacterInfo characterInfo, Action<Character> onSpawn = null)
335  {
336  if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
337  if (speciesName.IsEmpty)
338  {
339  string errorMsg = "Attempted to add an empty/null species name to entity spawn queue.\n" + Environment.StackTrace.CleanupStackTrace();
340  DebugConsole.ThrowError(errorMsg);
341  GameAnalyticsManager.AddErrorEventOnce("EntitySpawner.AddToSpawnQueue4:SpeciesNameNullOrEmpty", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
342  return;
343  }
344  spawnOrRemoveQueue.Enqueue(new CharacterSpawnInfo(speciesName, worldPosition, characterInfo, onSpawn));
345  }
346 
347  public void AddEntityToRemoveQueue(Entity entity)
348  {
349  if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
350  if (entity == null || IsInRemoveQueue(entity) || entity.Removed || entity.IdFreed) { return; }
351  if (entity is Item item) { AddItemToRemoveQueue(item); return; }
352  if (entity is Character)
353  {
354  Character character = entity as Character;
355 #if SERVER
356  if (GameMain.Server != null)
357  {
358  Client client = GameMain.Server.ConnectedClients.Find(c => c.Character == character);
359  if (client != null) GameMain.Server.SetClientCharacter(client, null);
360  }
361 #endif
362  }
363 
364  spawnOrRemoveQueue.Enqueue(entity);
365  }
366 
367  public void AddItemToRemoveQueue(Item item)
368  {
369  if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
370  if (IsInRemoveQueue(item) || item.Removed) { return; }
371 
372  spawnOrRemoveQueue.Enqueue(item);
373  var containedItems = item.OwnInventory?.AllItems;
374  if (containedItems == null) { return; }
375  foreach (Item containedItem in containedItems)
376  {
377  if (containedItem != null)
378  {
379  AddItemToRemoveQueue(containedItem);
380  }
381  }
382  }
383 
387  public bool IsInSpawnQueue(Predicate<IEntitySpawnInfo> predicate)
388  {
389  foreach (var spawnOrRemove in spawnOrRemoveQueue)
390  {
391  if (spawnOrRemove.TryGet(out IEntitySpawnInfo spawnInfo) && predicate(spawnInfo)) { return true; }
392  }
393  return false;
394  }
395 
399  public int CountSpawnQueue(Predicate<IEntitySpawnInfo> predicate)
400  {
401  int count = 0;
402  foreach (var spawnOrRemove in spawnOrRemoveQueue)
403  {
404  if (spawnOrRemove.TryGet(out IEntitySpawnInfo spawnInfo) && predicate(spawnInfo)) { count++; }
405  }
406  return count;
407  }
408 
409  public bool IsInRemoveQueue(Entity entity)
410  {
411  foreach (var spawnOrRemove in spawnOrRemoveQueue)
412  {
413  if (spawnOrRemove.TryGet(out Entity entityToRemove) && entityToRemove == entity) { return true; }
414  }
415  return false;
416  }
417 
418  public void Update(bool createNetworkEvents = true)
419  {
420  if (GameMain.NetworkMember is { IsClient: true }) { return; }
421  while (spawnOrRemoveQueue.Count > 0)
422  {
423  var spawnOrRemove = spawnOrRemoveQueue.Dequeue();
424  if (spawnOrRemove.TryGet(out Entity entityToRemove))
425  {
426  if (entityToRemove is Item item)
427  {
428  item.SendPendingNetworkUpdates();
429  }
430  if (createNetworkEvents)
431  {
432  CreateNetworkEventProjSpecific(new RemoveEntity(entityToRemove));
433  }
434  entityToRemove.Remove();
435  }
436  else if (spawnOrRemove.TryGet(out IEntitySpawnInfo spawnInfo))
437  {
438  var spawnedEntity = spawnInfo.Spawn();
439  if (spawnedEntity == null) { continue; }
440  if (createNetworkEvents)
441  {
442  CreateNetworkEventProjSpecific(new SpawnEntity(spawnedEntity));
443  }
444  spawnInfo.OnSpawned(spawnedEntity);
446  }
447  }
448  }
449 
450  partial void CreateNetworkEventProjSpecific(SpawnOrRemove spawnOrRemove);
451 
452  public void Reset()
453  {
454  spawnOrRemoveQueue.Clear();
455 #if CLIENT
456  receivedEvents.Clear();
457 #endif
458  }
459  }
460 }
Stores information about the Character that is needed between rounds in the menu etc....
virtual Vector2 WorldPosition
Definition: Entity.cs:49
Entity(Submarine submarine, ushort id)
Definition: Entity.cs:90
const ushort EntitySpawnerID
Definition: Entity.cs:15
Submarine Submarine
Definition: Entity.cs:53
virtual Vector2 Position
Definition: Entity.cs:47
readonly ushort ID
Unique, but non-persistent identifier. Stays the same if the entities are created in the exactly same...
Definition: Entity.cs:43
ItemSpawnInfo(ItemPrefab prefab, Vector2 position, Submarine sub, Action< Item > onSpawned, float? condition=null, int? quality=null)
ItemSpawnInfo(ItemPrefab prefab, Inventory inventory, Action< Item > onSpawned, float? condition=null, int? quality=null)
ItemSpawnInfo(ItemPrefab prefab, Vector2 worldPosition, Action< Item > onSpawned, float? condition=null, int? quality=null)
void AddCharacterToSpawnQueue(Identifier speciesName, Vector2 worldPosition, Action< Character > onSpawn=null)
int CountSpawnQueue(Predicate< IEntitySpawnInfo > predicate)
How many entities in the spawn queue match the given predicate
void AddCharacterToSpawnQueue(Identifier speciesName, Vector2 worldPosition, CharacterInfo characterInfo, Action< Character > onSpawn=null)
void AddItemToSpawnQueue(ItemPrefab itemPrefab, Vector2 position, Submarine sub, float? condition=null, int? quality=null, Action< Item > onSpawned=null)
void AddItemToSpawnQueue(ItemPrefab itemPrefab, Vector2 worldPosition, float? condition=null, int? quality=null, Action< Item > onSpawned=null)
void AddCharacterToSpawnQueue(Identifier speciesName, Vector2 position, Submarine sub, Action< Character > onSpawn=null)
readonly List<(Entity entity, bool isRemoval)> receivedEvents
void AddItemToSpawnQueue(ItemPrefab itemPrefab, Inventory inventory, float? condition=null, int? quality=null, Action< Item > onSpawned=null, bool spawnIfInventoryFull=true, bool ignoreLimbSlots=false, InvSlotType slot=InvSlotType.None)
bool IsInSpawnQueue(Predicate< IEntitySpawnInfo > predicate)
Are there any entities in the spawn queue that match the given predicate
static GameSession?? GameSession
Definition: GameMain.cs:88
static NetworkMember NetworkMember
Definition: GameMain.cs:190
virtual bool TryPutItem(Item item, Character user, IEnumerable< InvSlotType > allowedSlots=null, bool createNetworkEvent=true, bool ignoreCondition=false)
If there is room, puts the item in the inventory and returns true, otherwise returns false
virtual IEnumerable< Item > AllItems
All items contained in the inventory. Stacked items are returned as individual instances....
void ForceToSlot(Item item, int index)
Forces an item to a specific slot. Doesn't remove the item from existing slots/inventories or do any ...
Item GetItemAt(int index)
Get the item stored in the specified inventory slot. If the slot contains a stack of items,...
bool CanBePut(Item item)
Can the item be put in the inventory (i.e. is there a suitable free slot or a stack the item can be p...
IEnumerable< InvSlotType > AllowedSlots
void SetTransform(Vector2 simPosition, float rotation, bool findNewHull=true, bool setPrevTransform=true)
The base class for components holding the different functionalities of the item
Interface for entities that the server can send events to the clients