Client LuaCsForBarotrauma
BarotraumaShared/SharedSource/Map/MapEntity.cs
2 using FarseerPhysics;
3 using Microsoft.Xna.Framework;
4 using System;
5 using System.Collections.Generic;
6 using System.Diagnostics;
7 using System.Linq;
8 using System.Reflection;
9 using System.Xml.Linq;
10 
11 namespace Barotrauma
12 {
13  abstract partial class MapEntity : Entity, ISpatialEntity
14  {
15  public readonly static List<MapEntity> MapEntityList = new List<MapEntity>();
16 
17  public readonly MapEntityPrefab Prefab;
18 
19  protected List<ushort> linkedToID;
20  public List<ushort> unresolvedLinkedToID;
21 
22  public static int MapEntityUpdateInterval = 1;
23  public static int PoweredUpdateInterval = 1;
24  private static int mapEntityUpdateTick;
25 
29  protected readonly List<Upgrade> Upgrades = new List<Upgrade>();
30 
31  public readonly HashSet<Identifier> DisallowedUpgradeSet = new HashSet<Identifier>();
32 
34  public string DisallowedUpgrades
35  {
36  get { return string.Join(",", DisallowedUpgradeSet); }
37  set
38  {
39  DisallowedUpgradeSet.Clear();
40  if (!string.IsNullOrWhiteSpace(value))
41  {
42  string[] splitTags = value.Split(',');
43  foreach (string tag in splitTags)
44  {
45  string[] splitTag = tag.Trim().Split(':');
46  DisallowedUpgradeSet.Add(string.Join(":", splitTag).ToIdentifier());
47  }
48  }
49  }
50  }
51 
52  public readonly List<MapEntity> linkedTo = new List<MapEntity>();
53 
54  protected bool flippedX, flippedY;
55  public bool FlippedX { get { return flippedX; } }
56  public bool FlippedY { get { return flippedY; } }
57 
58  public bool ShouldBeSaved = true;
59 
60  //the position and dimensions of the entity
61  protected Rectangle rect;
62 
63  protected static readonly HashSet<MapEntity> highlightedEntities = new HashSet<MapEntity>();
64 
65  public static IEnumerable<MapEntity> HighlightedEntities => highlightedEntities;
66 
67 
68  private bool externalHighlight = false;
69  public bool ExternalHighlight
70  {
71  get { return externalHighlight; }
72  set
73  {
74  if (value != externalHighlight)
75  {
76  externalHighlight = value;
78  }
79  }
80  }
81 
82  //is the mouse inside the rect
83  private bool isHighlighted;
84 
85  public bool IsHighlighted
86  {
87  get { return isHighlighted || ExternalHighlight; }
88  set
89  {
90  if (value != isHighlighted)
91  {
92  isHighlighted = value;
94  }
95  }
96  }
97 
98  public virtual Rectangle Rect
99  {
100  get { return rect; }
101  set { rect = value; }
102  }
103 
105  {
106  get { return Submarine == null ? rect : new Rectangle((int)(Submarine.Position.X + rect.X), (int)(Submarine.Position.Y + rect.Y), rect.Width, rect.Height); }
107  }
108 
109  public virtual Sprite Sprite
110  {
111  get { return null; }
112  }
113 
114  public virtual bool DrawBelowWater
115  {
116  get
117  {
118  return Sprite != null && SpriteDepth > 0.5f;
119  }
120  }
121 
122  public virtual bool DrawOverWater
123  {
124  get
125  {
126  return !DrawBelowWater;
127  }
128  }
129 
130  public virtual bool Linkable
131  {
132  get { return false; }
133  }
134 
135  public IEnumerable<Identifier> AllowedLinks => Prefab == null ? Enumerable.Empty<Identifier>() : Prefab.AllowedLinks;
136 
137  public bool ResizeHorizontal
138  {
139  get { return Prefab != null && Prefab.ResizeHorizontal; }
140  }
141  public bool ResizeVertical
142  {
143  get { return Prefab != null && Prefab.ResizeVertical; }
144  }
145 
146  //for upgrading the dimensions of the entity from xml
148  public int RectWidth
149  {
150  get { return rect.Width; }
151  set
152  {
153  if (value <= 0) { return; }
154  Rect = new Rectangle(rect.X, rect.Y, value, rect.Height);
155  }
156  }
157  //for upgrading the dimensions of the entity from xml
159  public int RectHeight
160  {
161  get { return rect.Height; }
162  set
163  {
164  if (value <= 0) { return; }
165  Rect = new Rectangle(rect.X, rect.Y, rect.Width, value);
166  }
167  }
168 
169  // We could use NaN or nullables, but in this case the first is not preferable, because it needs to be checked every time the value is used.
170  // Nullable on the other requires boxing that we don't want to do too often, since it generates garbage.
171  public bool SpriteDepthOverrideIsSet { get; private set; }
172  public float SpriteOverrideDepth => SpriteDepth;
173  private float _spriteOverrideDepth = float.NaN;
174  [Editable(0.001f, 0.999f, decimals: 3), Serialize(float.NaN, IsPropertySaveable.Yes)]
175  public float SpriteDepth
176  {
177  get
178  {
179  if (SpriteDepthOverrideIsSet) { return _spriteOverrideDepth; }
180  return Sprite != null ? Sprite.Depth : 0;
181  }
182  set
183  {
184  if (!float.IsNaN(value))
185  {
186  _spriteOverrideDepth = MathHelper.Clamp(value, 0.001f, 0.999999f);
187  if (this is Item) { _spriteOverrideDepth = Math.Min(_spriteOverrideDepth, 0.9f); }
188  SpriteDepthOverrideIsSet = true;
189  }
190  }
191  }
192 
193  [Serialize(1f, IsPropertySaveable.Yes), Editable(0.01f, 10f, DecimalCount = 3, ValueStep = 0.1f)]
194  public virtual float Scale { get; set; } = 1;
195 
196  [Editable, Serialize(false, IsPropertySaveable.Yes)]
197  public bool HiddenInGame
198  {
199  get;
200  set;
201  }
202 
206  public bool IsLayerHidden { get; set; }
207 
211  public bool IsHidden => HiddenInGame || IsLayerHidden;
212 
213  public override Vector2 Position
214  {
215  get
216  {
217  Vector2 rectPos = new Vector2(
218  rect.X + rect.Width / 2.0f,
219  rect.Y - rect.Height / 2.0f);
220 
221  //if (MoveWithLevel) rectPos += Level.Loaded.Position;
222  return rectPos;
223  }
224  }
225 
226  public override Vector2 SimPosition
227  {
228  get
229  {
230  return ConvertUnits.ToSimUnits(Position);
231  }
232  }
233 
234  public float SoundRange
235  {
236  get
237  {
238  if (aiTarget == null) return 0.0f;
239  return aiTarget.SoundRange;
240  }
241  set
242  {
243  if (aiTarget == null) return;
244  aiTarget.SoundRange = value;
245  }
246  }
247 
248  public float SightRange
249  {
250  get
251  {
252  if (aiTarget == null) return 0.0f;
253  return aiTarget.SightRange;
254  }
255  set
256  {
257  if (aiTarget == null) return;
258  aiTarget.SightRange = value;
259  }
260  }
261 
262  [Serialize(true, IsPropertySaveable.Yes)]
263  public bool RemoveIfLinkedOutpostDoorInUse
264  {
265  get;
266  protected set;
267  } = true;
268 
269  [Serialize("", IsPropertySaveable.Yes, "Submarine editor layer")]
270  public string Layer { get; set; }
271 
275  public int OriginalModuleIndex = -1;
276 
277  public int OriginalContainerIndex = -1;
278 
279  public virtual string Name
280  {
281  get { return ""; }
282  }
283 
284  public MapEntity(MapEntityPrefab prefab, Submarine submarine, ushort id) : base(submarine, id)
285  {
286  this.Prefab = prefab;
287  Scale = prefab != null ? prefab.Scale : 1;
288  }
289 
290  protected void ParseLinks(XElement element, IdRemap idRemap)
291  {
292  string linkedToString = element.GetAttributeString("linked", "");
293  if (!string.IsNullOrEmpty(linkedToString))
294  {
295  string[] linkedToIds = linkedToString.Split(',');
296  for (int i = 0; i < linkedToIds.Length; i++)
297  {
298  int srcId = int.Parse(linkedToIds[i]);
299  int targetId = idRemap.GetOffsetId(srcId);
300  if (targetId <= 0)
301  {
302  unresolvedLinkedToID ??= new List<ushort>();
303  unresolvedLinkedToID.Add((ushort)srcId);
304  continue;
305  }
306  linkedToID.Add((ushort)targetId);
307  }
308  }
309  }
310 
311  public void ResolveLinks(IdRemap childRemap)
312  {
313  if (unresolvedLinkedToID == null) { return; }
314  for (int i = 0; i < unresolvedLinkedToID.Count; i++)
315  {
316  int srcId = unresolvedLinkedToID[i];
317  int targetId = childRemap.GetOffsetId(srcId);
318  if (targetId > 0)
319  {
320  var otherEntity = FindEntityByID((ushort)targetId) as MapEntity;
321  linkedTo.Add(otherEntity);
322  if (otherEntity.Linkable && otherEntity.linkedTo != null) otherEntity.linkedTo.Add(this);
323  unresolvedLinkedToID.RemoveAt(i);
324  i--;
325  }
326  }
327  }
328 
329  public virtual void Move(Vector2 amount, bool ignoreContacts = true)
330  {
331  rect.X += (int)amount.X;
332  rect.Y += (int)amount.Y;
333  }
334 
335  public virtual bool IsMouseOn(Vector2 position)
336  {
337  return (Submarine.RectContains(WorldRect, position));
338  }
339 
340  public bool HasUpgrade(Identifier identifier)
341  {
342  return GetUpgrade(identifier) != null;
343  }
344 
345  public Upgrade GetUpgrade(Identifier identifier)
346  {
347  return Upgrades.Find(upgrade => upgrade.Identifier == identifier);
348  }
349 
350  public List<Upgrade> GetUpgrades()
351  {
352  return Upgrades;
353  }
354 
355  public void SetUpgrade(Upgrade upgrade, bool createNetworkEvent = false)
356  {
357  Upgrade existingUpgrade = GetUpgrade(upgrade.Identifier);
358  if (existingUpgrade != null)
359  {
360  existingUpgrade.Level = upgrade.Level;
361  existingUpgrade.ApplyUpgrade();
362  upgrade.Dispose();
363  }
364  else
365  {
366  AddUpgrade(upgrade, createNetworkEvent);
367  }
368  DebugConsole.Log($"Set (ID: {ID} {Prefab.Name})'s \"{upgrade.Prefab.Name}\" upgrade to level {upgrade.Level}");
369  }
370 
374  public virtual bool AddUpgrade(Upgrade upgrade, bool createNetworkEvent = false)
375  {
376  if (!upgrade.Prefab.UpgradeCategories.Any(category => category.CanBeApplied(this, upgrade.Prefab)))
377  {
378  return false;
379  }
380 
381  if (DisallowedUpgradeSet.Contains(upgrade.Identifier)) { return false; }
382 
383  Upgrade existingUpgrade = GetUpgrade(upgrade.Identifier);
384 
385  if (existingUpgrade != null)
386  {
387  existingUpgrade.Level += upgrade.Level;
388  existingUpgrade.ApplyUpgrade();
389  upgrade.Dispose();
390  }
391  else
392  {
393  upgrade.ApplyUpgrade();
394  Upgrades.Add(upgrade);
395  }
396 
397  return true;
398  }
399 
400  protected virtual void CheckIsHighlighted()
401  {
402  if (IsHighlighted || ExternalHighlight)
403  {
404  highlightedEntities.Add(this);
405  }
406  else
407  {
408  highlightedEntities.Remove(this);
409  }
410  }
411 
412  private static readonly List<MapEntity> tempHighlightedEntities = new List<MapEntity>();
413  public static void ClearHighlightedEntities()
414  {
415  highlightedEntities.RemoveWhere(e => e.Removed);
416  tempHighlightedEntities.Clear();
417  tempHighlightedEntities.AddRange(highlightedEntities);
418  foreach (var entity in tempHighlightedEntities)
419  {
420  entity.IsHighlighted = false;
421  }
422  }
423 
424 
425  public abstract MapEntity Clone();
426 
427  public static List<MapEntity> Clone(List<MapEntity> entitiesToClone)
428  {
429  List<MapEntity> clones = new List<MapEntity>();
430  foreach (MapEntity e in entitiesToClone)
431  {
432  Debug.Assert(e != null);
433  try
434  {
435  clones.Add(e.Clone());
436  }
437  catch (Exception ex)
438  {
439  DebugConsole.ThrowError("Cloning entity \"" + e.Name + "\" failed.", ex);
440  GameAnalyticsManager.AddErrorEventOnce(
441  "MapEntity.Clone:" + e.Name,
442  GameAnalyticsManager.ErrorSeverity.Error,
443  "Cloning entity \"" + e.Name + "\" failed (" + ex.Message + ").\n" + ex.StackTrace.CleanupStackTrace());
444  return clones;
445  }
446  Debug.Assert(clones.Last() != null);
447  }
448 
449  Debug.Assert(clones.Count == entitiesToClone.Count);
450 
451  //clone links between the entities
452  for (int i = 0; i < clones.Count; i++)
453  {
454  if (entitiesToClone[i].linkedTo == null) { continue; }
455  foreach (MapEntity linked in entitiesToClone[i].linkedTo)
456  {
457  if (!entitiesToClone.Contains(linked)) { continue; }
458  clones[i].linkedTo.Add(clones[entitiesToClone.IndexOf(linked)]);
459  }
460  }
461 
462  //connect clone wires to the clone items and refresh links between doors and gaps
463  List<Wire> orphanedWires = new List<Wire>();
464  for (int i = 0; i < clones.Count; i++)
465  {
466  if (clones[i] is not Item cloneItem) { continue; }
467 
468  var door = cloneItem.GetComponent<Door>();
469  door?.RefreshLinkedGap();
470 
471  var cloneWire = cloneItem.GetComponent<Wire>();
472  if (cloneWire == null) { continue; }
473 
474  var originalWire = ((Item)entitiesToClone[i]).GetComponent<Wire>();
475 
476  cloneWire.SetNodes(originalWire.GetNodes());
477 
478  for (int n = 0; n < 2; n++)
479  {
480  if (originalWire.Connections[n] == null)
481  {
482  var disconnectedFrom = entitiesToClone.Find(e => e is Item item && (item.GetComponent<ConnectionPanel>()?.DisconnectedWires.Contains(originalWire) ?? false));
483  if (disconnectedFrom == null) { continue; }
484 
485  int disconnectedFromIndex = entitiesToClone.IndexOf(disconnectedFrom);
486  var disconnectedFromClone = (clones[disconnectedFromIndex] as Item)?.GetComponent<ConnectionPanel>();
487  if (disconnectedFromClone == null) { continue; }
488 
489  disconnectedFromClone.DisconnectedWires.Add(cloneWire);
490  if (cloneWire.Item.body != null) { cloneWire.Item.body.Enabled = false; }
491  cloneWire.IsActive = false;
492  continue;
493  }
494 
495  var connectedItem = originalWire.Connections[n].Item;
496  if (connectedItem == null || !entitiesToClone.Contains(connectedItem)) { continue; }
497 
498  //index of the item the wire is connected to
499  int itemIndex = entitiesToClone.IndexOf(connectedItem);
500  if (itemIndex < 0)
501  {
502  DebugConsole.ThrowError("Error while cloning wires - item \"" + connectedItem.Name + "\" was not found in entities to clone.");
503  GameAnalyticsManager.AddErrorEventOnce("MapEntity.Clone:ConnectedNotFound" + connectedItem.ID,
504  GameAnalyticsManager.ErrorSeverity.Error,
505  "Error while cloning wires - item \"" + connectedItem.Name + "\" was not found in entities to clone.");
506  continue;
507  }
508 
509  //index of the connection in the connectionpanel of the target item
510  int connectionIndex = connectedItem.Connections.IndexOf(originalWire.Connections[n]);
511  if (connectionIndex < 0)
512  {
513  DebugConsole.ThrowError("Error while cloning wires - connection \"" + originalWire.Connections[n].Name + "\" was not found in connected item \"" + connectedItem.Name + "\".");
514  GameAnalyticsManager.AddErrorEventOnce("MapEntity.Clone:ConnectionNotFound" + connectedItem.ID,
515  GameAnalyticsManager.ErrorSeverity.Error,
516  "Error while cloning wires - connection \"" + originalWire.Connections[n].Name + "\" was not found in connected item \"" + connectedItem.Name + "\".");
517  continue;
518  }
519 
520  (clones[itemIndex] as Item).Connections[connectionIndex].TryAddLink(cloneWire);
521  cloneWire.Connect((clones[itemIndex] as Item).Connections[connectionIndex], n, addNode: false);
522  }
523 
524  if (originalWire.Connections.Any(c => c != null) &&
525  (cloneWire.Connections[0] == null || cloneWire.Connections[1] == null) &&
526  cloneItem.GetComponent<DockingPort>() == null)
527  {
528  if (!clones.Any(c => (c as Item)?.GetComponent<ConnectionPanel>()?.DisconnectedWires.Contains(cloneWire) ?? false))
529  {
530  orphanedWires.Add(cloneWire);
531  }
532  }
533  }
534 
535  foreach (var orphanedWire in orphanedWires)
536  {
537  orphanedWire.Item.Remove();
538  clones.Remove(orphanedWire.Item);
539  }
540 
541  return clones;
542  }
543 
544  protected void InsertToList()
545  {
546  if (Sprite == null)
547  {
548  MapEntityList.Add(this);
549  return;
550  }
551 
552  int i = 0;
553  while (i < MapEntityList.Count)
554  {
555  i++;
556  if (MapEntityList[i - 1]?.Prefab == Prefab)
557  {
558  MapEntityList.Insert(i, this);
559  return;
560  }
561  }
562 
563 #if CLIENT
564  i = 0;
565  while (i < MapEntityList.Count)
566  {
567  i++;
568  Sprite existingSprite = MapEntityList[i - 1].Sprite;
569  if (existingSprite == null) { continue; }
570  if (existingSprite.Texture == this.Sprite.Texture) { break; }
571  }
572 #endif
573  MapEntityList.Insert(i, this);
574  }
575 
579  public virtual void ShallowRemove()
580  {
581  base.Remove();
582 
583  MapEntityList.Remove(this);
584 
585  if (aiTarget != null) aiTarget.Remove();
586  }
587 
588  public override void Remove()
589  {
590  base.Remove();
591 
592  MapEntityList.Remove(this);
593 #if CLIENT
595  SelectedList.Remove(this);
596 #endif
597  if (aiTarget != null)
598  {
599  aiTarget.Remove();
600  aiTarget = null;
601  }
602 
603  if (linkedTo != null)
604  {
605  for (int i = linkedTo.Count - 1; i >= 0; i--)
606  {
607  linkedTo[i].RemoveLinked(this);
608  }
609  linkedTo.Clear();
610  }
611  }
612 
616  public static void UpdateAll(float deltaTime, Camera cam)
617  {
618  mapEntityUpdateTick++;
619 
620 #if CLIENT
621  var sw = new System.Diagnostics.Stopwatch();
622  sw.Start();
623 #endif
624  if (mapEntityUpdateTick % MapEntityUpdateInterval == 0)
625  {
626 
627  foreach (Hull hull in Hull.HullList)
628  {
629  hull.Update(deltaTime * MapEntityUpdateInterval, cam);
630  }
631 #if CLIENT
632  Hull.UpdateCheats(deltaTime * MapEntityUpdateInterval, cam);
633 #endif
634 
635  foreach (Structure structure in Structure.WallList)
636  {
637  structure.Update(deltaTime * MapEntityUpdateInterval, cam);
638  }
639  }
640 
641  //update gaps in random order, because otherwise in rooms with multiple gaps
642  //the water/air will always tend to flow through the first gap in the list,
643  //which may lead to weird behavior like water draining down only through
644  //one gap in a room even if there are several
645  foreach (Gap gap in Gap.GapList.OrderBy(g => Rand.Int(int.MaxValue)))
646  {
647  gap.Update(deltaTime, cam);
648  }
649 
650  if (mapEntityUpdateTick % PoweredUpdateInterval == 0)
651  {
652  Powered.UpdatePower(deltaTime * PoweredUpdateInterval);
653  }
654 
655 #if CLIENT
656  sw.Stop();
657  GameMain.PerformanceCounter.AddElapsedTicks("Update:MapEntity:Misc", sw.ElapsedTicks);
658  sw.Restart();
659 #endif
660 
662  if (mapEntityUpdateTick % MapEntityUpdateInterval == 0)
663  {
664  Item lastUpdatedItem = null;
665 
666  try
667  {
668  foreach (Item item in Item.ItemList)
669  {
670  if (GameMain.LuaCs.Game.UpdatePriorityItems.Contains(item)) { continue; }
671  lastUpdatedItem = item;
672  item.Update(deltaTime * MapEntityUpdateInterval, cam);
673  }
674  }
675  catch (InvalidOperationException e)
676  {
677  GameAnalyticsManager.AddErrorEventOnce(
678  "MapEntity.UpdateAll:ItemUpdateInvalidOperation",
679  GameAnalyticsManager.ErrorSeverity.Critical,
680  $"Error while updating item {lastUpdatedItem?.Name ?? "null"}: {e.Message}");
681  throw new InvalidOperationException($"Error while updating item {lastUpdatedItem?.Name ?? "null"}", innerException: e);
682  }
683  }
684 
685  foreach (var item in GameMain.LuaCs.Game.UpdatePriorityItems)
686  {
687  if (item.Removed) continue;
688 
689  item.Update(deltaTime, cam);
690  }
691 
692 #if CLIENT
693  sw.Stop();
694  GameMain.PerformanceCounter.AddElapsedTicks("Update:MapEntity:Items", sw.ElapsedTicks);
695  sw.Restart();
696 #endif
697 
698  if (mapEntityUpdateTick % MapEntityUpdateInterval == 0)
699  {
700  UpdateAllProjSpecific(deltaTime * MapEntityUpdateInterval);
701 
702  Spawner?.Update();
703  }
704  }
705 
706  static partial void UpdateAllProjSpecific(float deltaTime);
707 
708  public virtual void Update(float deltaTime, Camera cam) { }
709 
714  public virtual void FlipX(bool relativeToSub)
715  {
716  flippedX = !flippedX;
717  if (!relativeToSub || Submarine == null) { return; }
718 
719  Vector2 relative = WorldPosition - Submarine.WorldPosition;
720  relative.Y = 0.0f;
721  Move(-relative * 2.0f);
722  }
723 
728  public virtual void FlipY(bool relativeToSub)
729  {
730  flippedY = !flippedY;
731  if (!relativeToSub || Submarine == null) { return; }
732 
733  Vector2 relative = WorldPosition - Submarine.WorldPosition;
734  relative.X = 0.0f;
735  Move(-relative * 2.0f);
736  }
737 
738  public virtual Quad2D GetTransformedQuad()
739  => Quad2D.FromSubmarineRectangle(rect);
740 
741  public static List<MapEntity> LoadAll(Submarine submarine, XElement parentElement, string filePath, int idOffset)
742  {
743  IdRemap idRemap = new IdRemap(parentElement, idOffset);
744 
745  bool containsHiddenContainers = false;
746  bool hiddenContainerCreated = false;
747  MTRandom hiddenContainerRNG = new MTRandom(ToolBox.StringToInt(submarine.Info.Name));
748  foreach (var element in parentElement.Elements())
749  {
750  if (element.NameAsIdentifier() != "Item") { continue; }
751  var tags = element.GetAttributeIdentifierArray("tags", Array.Empty<Identifier>());
752  if (tags.Contains(Tags.HiddenItemContainer))
753  {
754  containsHiddenContainers = true;
755  break;
756  }
757  }
758 
759  List<MapEntity> entities = new List<MapEntity>();
760  foreach (var element in parentElement.Elements())
761  {
762 #if CLIENT
764 #endif
765  string typeName = element.Name.ToString();
766 
767  Type t;
768  try
769  {
770  t = Type.GetType("Barotrauma." + typeName, true, true);
771  if (t == null)
772  {
773  DebugConsole.ThrowError("Error in " + filePath + "! Could not find a entity of the type \"" + typeName + "\".");
774  continue;
775  }
776  }
777  catch (Exception e)
778  {
779  DebugConsole.ThrowError("Error in " + filePath + "! Could not find a entity of the type \"" + typeName + "\".", e);
780  continue;
781  }
782 
783  Identifier identifier = element.GetAttributeIdentifier("identifier", "");
784  Identifier replacementIdentifier = Identifier.Empty;
785  if (t == typeof(Structure))
786  {
787  string name = element.Attribute("name").Value;
788  StructurePrefab structurePrefab = Structure.FindPrefab(name, identifier);
789  if (structurePrefab == null)
790  {
791  ItemPrefab itemPrefab = ItemPrefab.Find(name, identifier);
792  if (itemPrefab != null)
793  {
794  t = typeof(Item);
795  }
796  }
797  }
798  else if (t == typeof(Item) && !containsHiddenContainers && identifier == "vent" &&
799  submarine.Info.Type == SubmarineType.Player && !submarine.Info.HasTag(SubmarineTag.Shuttle))
800  {
801  if (!hiddenContainerCreated)
802  {
803  DebugConsole.AddWarning($"There are no hidden containers such as loose vents or loose panels in the submarine \"{submarine.Info.Name}\". Certain traitor events require these to function properly. Converting one of the vents to a loose vent...");
804  }
805  if (!hiddenContainerCreated || hiddenContainerRNG.NextDouble() < 0.2)
806  {
807  replacementIdentifier = "loosevent".ToIdentifier();
808  containsHiddenContainers = true;
809  hiddenContainerCreated = true;
810  }
811  }
812 
813  try
814  {
815  MethodInfo loadMethod = t.GetMethod("Load", new[] { typeof(ContentXElement), typeof(Submarine), typeof(IdRemap) });
816  if (loadMethod == null)
817  {
818  DebugConsole.ThrowError("Could not find the method \"Load\" in " + t + ".");
819  }
820  else if (!loadMethod.ReturnType.IsSubclassOf(typeof(MapEntity)))
821  {
822  DebugConsole.ThrowError("Error loading entity of the type \"" + t.ToString() + "\" - load method does not return a valid map entity.");
823  }
824  else
825  {
826  var newElement = element.FromPackage(null);
827  if (!replacementIdentifier.IsEmpty)
828  {
829  newElement.SetAttributeValue("identifier", replacementIdentifier.ToString());
830  }
831  object newEntity = loadMethod.Invoke(t, new object[] { newElement, submarine, idRemap });
832  if (newEntity != null)
833  {
834  entities.Add((MapEntity)newEntity);
835  }
836  }
837  }
838  catch (TargetInvocationException e)
839  {
840  DebugConsole.ThrowError("Error while loading entity of the type " + t + ".", e.InnerException);
841  }
842  catch (Exception e)
843  {
844  DebugConsole.ThrowError("Error while loading entity of the type " + t + ".", e);
845  }
846  }
847  return entities;
848  }
849 
855  private bool mapLoadedCalled;
856  public static void MapLoaded(List<MapEntity> entities, bool updateHulls)
857  {
858  InitializeLoadedLinks(entities);
859 
860  List<LinkedSubmarine> linkedSubs = new List<LinkedSubmarine>();
861  for (int i = 0; i < entities.Count; i++)
862  {
863  if (entities[i].mapLoadedCalled || entities[i].Removed) { continue; }
864  if (entities[i] is LinkedSubmarine sub)
865  {
866  linkedSubs.Add(sub);
867  continue;
868  }
869 
870  entities[i].OnMapLoaded();
871  }
872 
873  if (updateHulls)
874  {
875  Item.UpdateHulls();
876  Gap.UpdateHulls();
877  }
878 
879  entities.ForEach(e => e.mapLoadedCalled = true);
880 
881  foreach (LinkedSubmarine linkedSub in linkedSubs)
882  {
883  linkedSub.OnMapLoaded();
884  }
885 
886  CreateDroppedStacks(entities);
887  }
888 
889  private static void CreateDroppedStacks(List<MapEntity> entities)
890  {
891  const float MaxDist = 10.0f;
892  List<Item> itemsInStack = new List<Item>();
893  for (int i = 0; i < entities.Count; i++)
894  {
895  if (entities[i] is not Item item1 || item1.Prefab.MaxStackSize <= 1 || item1.body is not { Enabled: true }) { continue; }
896  itemsInStack.Clear();
897  itemsInStack.Add(item1);
898  for (int j = i + 1; j < entities.Count; j++)
899  {
900  if (entities[j] is not Item item2) { continue; }
901  if (item1.Prefab != item2.Prefab) { continue; }
902  if (item2.body is not { Enabled: true }) { continue; }
903  if (item2.DroppedStack.Any()) { continue; }
904  if (Math.Abs(item1.Position.X - item2.Position.X) > MaxDist) { continue; }
905  if (Math.Abs(item1.Position.Y - item2.Position.Y) > MaxDist) { continue; }
906  itemsInStack.Add(item2);
907  }
908  if (itemsInStack.Count > 1)
909  {
910  item1.CreateDroppedStack(itemsInStack, allowClientExecute: true);
911  DebugConsole.Log($"Merged x{itemsInStack.Count} of {item1.Name} into a dropped stack.");
912  }
913  }
914  }
915 
916  public static void InitializeLoadedLinks(IEnumerable<MapEntity> entities)
917  {
918  foreach (MapEntity e in entities)
919  {
920  if (e.mapLoadedCalled) { continue; }
921  if (e.linkedToID == null) { continue; }
922  if (e.linkedToID.Count == 0) { continue; }
923 
924  e.linkedTo.Clear();
925 
926  foreach (ushort i in e.linkedToID)
927  {
928  if (FindEntityByID(i) is MapEntity linked)
929  {
930  e.linkedTo.Add(linked);
931  }
932  else
933  {
934 #if DEBUG
935  DebugConsole.ThrowError($"Linking the entity \"{e.Name}\" to another entity failed. Could not find an entity with the ID \"{i}\".");
936 #endif
937  }
938  }
939  e.linkedToID.Clear();
940 
941  (e as WayPoint)?.InitializeLinks();
942  }
943  }
944 
945  public virtual void OnMapLoaded() { }
946 
947  public virtual XElement Save(XElement parentElement)
948  {
949  DebugConsole.ThrowError("Saving entity " + GetType() + " failed.");
950  return null;
951  }
952 
953  public void RemoveLinked(MapEntity e)
954  {
955  if (linkedTo == null) return;
956  if (linkedTo.Contains(e)) linkedTo.Remove(e);
957  }
958 
962  public HashSet<T> GetLinkedEntities<T>(HashSet<T> list = null, int? maxDepth = null, Func<T, bool> filter = null) where T : MapEntity
963  {
964  list = list ?? new HashSet<T>();
965  int startDepth = 0;
966  GetLinkedEntitiesRecursive<T>(this, list, ref startDepth, maxDepth, filter);
967  return list;
968  }
969 
973  private static void GetLinkedEntitiesRecursive<T>(MapEntity mapEntity, HashSet<T> linkedTargets, ref int depth, int? maxDepth = null, Func<T, bool> filter = null)
974  where T : MapEntity
975  {
976  if (depth > maxDepth) { return; }
977  foreach (var linkedEntity in mapEntity.linkedTo)
978  {
979  if (linkedEntity is T linkedTarget)
980  {
981  if (!linkedTargets.Contains(linkedTarget) && (filter == null || filter(linkedTarget)))
982  {
983  linkedTargets.Add(linkedTarget);
984  depth++;
985  GetLinkedEntitiesRecursive(linkedEntity, linkedTargets, ref depth, maxDepth, filter);
986  }
987  }
988  }
989  }
990  }
991 }
static GameSession?? GameSession
Definition: GameMain.cs:88
static PerformanceCounter PerformanceCounter
Definition: GameMain.cs:37
static LuaCsSetup LuaCs
Definition: GameMain.cs:26
override void Update(float deltaTime, Camera cam)
static void UpdateCheats(float deltaTime, Camera cam)
static readonly List< Hull > HullList
override void Update(float deltaTime, Camera cam)
ushort GetOffsetId(XElement element)
Definition: IdRemap.cs:86
static void UpdateHulls()
goes through every item and re-checks which hull they are in
override void Update(float deltaTime, Camera cam)
static void UpdatePendingConditionUpdates(float deltaTime)
static readonly List< Item > ItemList
static ItemPrefab Find(string name, Identifier identifier)
readonly HashSet< Wire > DisconnectedWires
Wires that have been disconnected from the panel, but not removed completely (visible at the bottom o...
static void UpdatePower(float deltaTime)
Update the power calculations of all devices and grids Updates grids in the order of ConnCurrConsumpt...
HashSet< Item > UpdatePriorityItems
Definition: LuaGame.cs:175
Mersenne Twister based random
Definition: MTRandom.cs:9
virtual bool AddUpgrade(Upgrade upgrade, bool createNetworkEvent=false)
Adds a new upgrade to the item
virtual void FlipX(bool relativeToSub)
Flip the entity horizontally
virtual void ShallowRemove()
Remove the entity from the entity list without removing links to other entities
static void InitializeLoadedLinks(IEnumerable< MapEntity > entities)
static void UpdateAll(float deltaTime, Camera cam)
Call Update() on every object in Entity.list
virtual void Update(float deltaTime, Camera cam)
static void MapLoaded(List< MapEntity > entities, bool updateHulls)
static IEnumerable< MapEntity > HighlightedEntities
void SetUpgrade(Upgrade upgrade, bool createNetworkEvent=false)
virtual void FlipY(bool relativeToSub)
Flip the entity vertically
static readonly List< MapEntity > MapEntityList
void ParseLinks(XElement element, IdRemap idRemap)
readonly HashSet< Identifier > DisallowedUpgradeSet
virtual Quad2D GetTransformedQuad()
MapEntity(MapEntityPrefab prefab, Submarine submarine, ushort id)
virtual void Move(Vector2 amount, bool ignoreContacts=true)
static readonly HashSet< MapEntity > highlightedEntities
abstract MapEntity Clone()
virtual XElement Save(XElement parentElement)
readonly List< Upgrade > Upgrades
List of upgrades this item has
static List< MapEntity > Clone(List< MapEntity > entitiesToClone)
static List< MapEntity > LoadAll(Submarine submarine, XElement parentElement, string filePath, int idOffset)
void AddElapsedTicks(string identifier, long ticks)
Sprite(ContentXElement element, string path="", string file="", bool lazyLoad=false, float sourceRectScale=1)
override void Update(float deltaTime, Camera cam)
static StructurePrefab FindPrefab(string name, Identifier identifier)
static bool RectContains(Rectangle rect, Vector2 pos, bool inclusive=false)
static void ForceRemoveFromVisibleEntities(MapEntity entity)