3 using Microsoft.Xna.Framework;
5 using System.Collections.Generic;
6 using System.Diagnostics;
8 using System.Reflection;
13 abstract partial class MapEntity : Entity, ISpatialEntity
15 public readonly
static List<MapEntity>
MapEntityList =
new List<MapEntity>();
24 private static int mapEntityUpdateTick;
29 protected readonly List<Upgrade>
Upgrades =
new List<Upgrade>();
40 if (!
string.IsNullOrWhiteSpace(value))
42 string[] splitTags = value.Split(
',');
43 foreach (
string tag
in splitTags)
45 string[] splitTag = tag.Trim().Split(
':');
52 public readonly List<MapEntity>
linkedTo =
new List<MapEntity>();
56 public bool FlippedY {
get {
return flippedY; } }
68 private bool externalHighlight =
false;
71 get {
return externalHighlight; }
74 if (value != externalHighlight)
76 externalHighlight = value;
83 private bool isHighlighted;
90 if (value != isHighlighted)
92 isHighlighted = value;
98 public virtual Rectangle
Rect
101 set {
rect = value; }
132 get {
return false; }
139 get {
return Prefab !=
null &&
Prefab.ResizeHorizontal; }
141 public bool ResizeVertical
150 get {
return rect.Width; }
153 if (value <= 0) {
return; }
154 Rect =
new Rectangle(rect.X, rect.Y, value, rect.Height);
159 public int RectHeight
161 get {
return rect.Height; }
164 if (value <= 0) {
return; }
165 Rect =
new Rectangle(rect.X, rect.Y, rect.Width, value);
171 public bool SpriteDepthOverrideIsSet {
get;
private set; }
172 public float SpriteOverrideDepth => SpriteDepth;
173 private float _spriteOverrideDepth =
float.NaN;
175 public float SpriteDepth
179 if (SpriteDepthOverrideIsSet) {
return _spriteOverrideDepth; }
184 if (!
float.IsNaN(value))
186 _spriteOverrideDepth = MathHelper.Clamp(value, 0.001f, 0.999999f);
187 if (
this is
Item) { _spriteOverrideDepth = Math.Min(_spriteOverrideDepth, 0.9f); }
188 SpriteDepthOverrideIsSet =
true;
194 public virtual float Scale {
get;
set; } = 1;
197 public bool HiddenInGame
206 public bool IsLayerHidden {
get;
set; }
211 public bool IsHidden => HiddenInGame || IsLayerHidden;
213 public override Vector2 Position
217 Vector2 rectPos =
new Vector2(
218 rect.X + rect.Width / 2.0f,
219 rect.Y - rect.Height / 2.0f);
226 public override Vector2 SimPosition
230 return ConvertUnits.ToSimUnits(Position);
234 public float SoundRange
238 if (aiTarget ==
null)
return 0.0f;
239 return aiTarget.SoundRange;
243 if (aiTarget ==
null)
return;
244 aiTarget.SoundRange = value;
248 public float SightRange
252 if (aiTarget ==
null)
return 0.0f;
253 return aiTarget.SightRange;
257 if (aiTarget ==
null)
return;
258 aiTarget.SightRange = value;
263 public bool RemoveIfLinkedOutpostDoorInUse
270 public string Layer {
get;
set; }
275 public int OriginalModuleIndex = -1;
277 public int OriginalContainerIndex = -1;
279 public virtual string Name
287 Scale = prefab !=
null ? prefab.
Scale : 1;
292 string linkedToString = element.GetAttributeString(
"linked",
"");
293 if (!
string.IsNullOrEmpty(linkedToString))
295 string[] linkedToIds = linkedToString.Split(
',');
296 for (
int i = 0; i < linkedToIds.Length; i++)
298 int srcId =
int.Parse(linkedToIds[i]);
302 unresolvedLinkedToID ??=
new List<ushort>();
303 unresolvedLinkedToID.Add((ushort)srcId);
306 linkedToID.Add((ushort)targetId);
313 if (unresolvedLinkedToID ==
null) {
return; }
314 for (
int i = 0; i < unresolvedLinkedToID.Count; i++)
316 int srcId = unresolvedLinkedToID[i];
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);
329 public virtual void Move(Vector2 amount,
bool ignoreContacts =
true)
331 rect.X += (int)amount.X;
332 rect.Y += (
int)amount.Y;
342 return GetUpgrade(identifier) !=
null;
347 return Upgrades.Find(upgrade => upgrade.Identifier == identifier);
355 public void SetUpgrade(Upgrade upgrade,
bool createNetworkEvent =
false)
357 Upgrade existingUpgrade = GetUpgrade(upgrade.Identifier);
358 if (existingUpgrade !=
null)
360 existingUpgrade.Level = upgrade.Level;
361 existingUpgrade.ApplyUpgrade();
366 AddUpgrade(upgrade, createNetworkEvent);
368 DebugConsole.Log($
"Set (ID: {ID} {Prefab.Name})'s \"{upgrade.Prefab.Name}\" upgrade to level {upgrade.Level}");
374 public virtual bool AddUpgrade(Upgrade upgrade,
bool createNetworkEvent =
false)
376 if (!upgrade.Prefab.UpgradeCategories.Any(category => category.CanBeApplied(
this, upgrade.Prefab)))
381 if (DisallowedUpgradeSet.Contains(upgrade.Identifier)) {
return false; }
383 Upgrade existingUpgrade = GetUpgrade(upgrade.Identifier);
385 if (existingUpgrade !=
null)
387 existingUpgrade.Level += upgrade.Level;
388 existingUpgrade.ApplyUpgrade();
393 upgrade.ApplyUpgrade();
394 Upgrades.Add(upgrade);
402 if (IsHighlighted || ExternalHighlight)
404 highlightedEntities.Add(
this);
408 highlightedEntities.Remove(
this);
412 private static readonly List<MapEntity> tempHighlightedEntities =
new List<MapEntity>();
415 highlightedEntities.RemoveWhere(e => e.Removed);
416 tempHighlightedEntities.Clear();
417 tempHighlightedEntities.AddRange(highlightedEntities);
418 foreach (var entity
in tempHighlightedEntities)
420 entity.IsHighlighted =
false;
427 public static List<MapEntity>
Clone(List<MapEntity> entitiesToClone)
429 List<MapEntity> clones =
new List<MapEntity>();
432 Debug.Assert(e !=
null);
435 clones.Add(e.Clone());
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());
446 Debug.Assert(clones.Last() !=
null);
449 Debug.Assert(clones.Count == entitiesToClone.Count);
452 for (
int i = 0; i < clones.Count; i++)
454 if (entitiesToClone[i].linkedTo ==
null) {
continue; }
455 foreach (
MapEntity linked
in entitiesToClone[i].linkedTo)
457 if (!entitiesToClone.Contains(linked)) {
continue; }
458 clones[i].linkedTo.Add(clones[entitiesToClone.IndexOf(linked)]);
463 List<Wire> orphanedWires =
new List<Wire>();
464 for (
int i = 0; i < clones.Count; i++)
466 if (clones[i] is not
Item cloneItem) {
continue; }
468 var door = cloneItem.GetComponent<
Door>();
471 var cloneWire = cloneItem.GetComponent<
Wire>();
472 if (cloneWire ==
null) {
continue; }
474 var originalWire = ((
Item)entitiesToClone[i]).GetComponent<
Wire>();
476 cloneWire.
SetNodes(originalWire.GetNodes());
478 for (
int n = 0; n < 2; n++)
480 if (originalWire.Connections[n] ==
null)
483 if (disconnectedFrom ==
null) {
continue; }
485 int disconnectedFromIndex = entitiesToClone.IndexOf(disconnectedFrom);
486 var disconnectedFromClone = (clones[disconnectedFromIndex] as
Item)?.GetComponent<ConnectionPanel>();
487 if (disconnectedFromClone ==
null) {
continue; }
489 disconnectedFromClone.DisconnectedWires.Add(cloneWire);
490 if (cloneWire.Item.body !=
null) { cloneWire.Item.body.Enabled =
false; }
491 cloneWire.IsActive =
false;
495 var connectedItem = originalWire.Connections[n].Item;
496 if (connectedItem ==
null || !entitiesToClone.Contains(connectedItem)) {
continue; }
499 int itemIndex = entitiesToClone.IndexOf(connectedItem);
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.");
510 int connectionIndex = connectedItem.Connections.IndexOf(originalWire.Connections[n]);
511 if (connectionIndex < 0)
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 +
"\".");
520 (clones[itemIndex] as
Item).Connections[connectionIndex].TryAddLink(cloneWire);
521 cloneWire.Connect((clones[itemIndex] as
Item).Connections[connectionIndex], n, addNode:
false);
524 if (originalWire.Connections.Any(c => c !=
null) &&
525 (cloneWire.Connections[0] ==
null || cloneWire.Connections[1] ==
null) &&
528 if (!clones.Any(c => (c as
Item)?.GetComponent<
ConnectionPanel>()?.DisconnectedWires.Contains(cloneWire) ??
false))
530 orphanedWires.Add(cloneWire);
535 foreach (var orphanedWire
in orphanedWires)
537 orphanedWire.Item.Remove();
538 clones.Remove(orphanedWire.Item);
548 MapEntityList.Add(
this);
553 while (i < MapEntityList.Count)
558 MapEntityList.Insert(i,
this);
565 while (i < MapEntityList.Count)
569 if (existingSprite ==
null) {
continue; }
570 if (existingSprite.Texture ==
this.
Sprite.Texture) {
break; }
573 MapEntityList.Insert(i,
this);
583 MapEntityList.Remove(
this);
585 if (aiTarget !=
null) aiTarget.Remove();
592 MapEntityList.Remove(
this);
594 Submarine.ForceRemoveFromVisibleEntities(
this);
595 SelectedList.
Remove(
this);
597 if (aiTarget !=
null)
603 if (linkedTo !=
null)
605 for (
int i = linkedTo.Count - 1; i >= 0; i--)
607 linkedTo[i].RemoveLinked(
this);
618 mapEntityUpdateTick++;
621 var sw =
new System.Diagnostics.Stopwatch();
624 if (mapEntityUpdateTick % MapEntityUpdateInterval == 0)
629 hull.
Update(deltaTime * MapEntityUpdateInterval, cam);
632 Hull.UpdateCheats(deltaTime * MapEntityUpdateInterval, cam);
637 structure.
Update(deltaTime * MapEntityUpdateInterval, cam);
645 foreach (
Gap gap
in Gap.
GapList.OrderBy(g => Rand.Int(
int.MaxValue)))
647 gap.
Update(deltaTime, cam);
650 if (mapEntityUpdateTick % PoweredUpdateInterval == 0)
657 GameMain.PerformanceCounter.AddElapsedTicks(
"Update:MapEntity:Misc", sw.ElapsedTicks);
662 if (mapEntityUpdateTick % MapEntityUpdateInterval == 0)
664 Item lastUpdatedItem =
null;
671 lastUpdatedItem = item;
672 item.
Update(deltaTime * MapEntityUpdateInterval, cam);
675 catch (InvalidOperationException e)
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);
687 if (item.Removed)
continue;
689 item.Update(deltaTime, cam);
694 GameMain.PerformanceCounter.AddElapsedTicks(
"Update:MapEntity:Items", sw.ElapsedTicks);
698 if (mapEntityUpdateTick % MapEntityUpdateInterval == 0)
700 UpdateAllProjSpecific(deltaTime * MapEntityUpdateInterval);
706 static partial
void UpdateAllProjSpecific(
float deltaTime);
714 public virtual void FlipX(
bool relativeToSub)
716 flippedX = !flippedX;
717 if (!relativeToSub ||
Submarine ==
null) {
return; }
721 Move(-relative * 2.0f);
728 public virtual void FlipY(
bool relativeToSub)
730 flippedY = !flippedY;
731 if (!relativeToSub ||
Submarine ==
null) {
return; }
735 Move(-relative * 2.0f);
739 => Quad2D.FromSubmarineRectangle(rect);
741 public static List<MapEntity>
LoadAll(
Submarine submarine, XElement parentElement,
string filePath,
int idOffset)
745 bool containsHiddenContainers =
false;
746 bool hiddenContainerCreated =
false;
748 foreach (var element
in parentElement.Elements())
750 if (element.NameAsIdentifier() !=
"Item") {
continue; }
751 var tags = element.GetAttributeIdentifierArray(
"tags", Array.Empty<Identifier>());
752 if (tags.Contains(Tags.HiddenItemContainer))
754 containsHiddenContainers =
true;
759 List<MapEntity> entities =
new List<MapEntity>();
760 foreach (var element
in parentElement.Elements())
770 t = Type.GetType(
"Barotrauma." + typeName,
true,
true);
773 DebugConsole.ThrowError(
"Error in " + filePath +
"! Could not find a entity of the type \"" + typeName +
"\".");
779 DebugConsole.ThrowError(
"Error in " + filePath +
"! Could not find a entity of the type \"" + typeName +
"\".", e);
783 Identifier identifier = element.GetAttributeIdentifier(
"identifier",
"");
784 Identifier replacementIdentifier = Identifier.Empty;
787 string name = element.Attribute(
"name").Value;
789 if (structurePrefab ==
null)
792 if (itemPrefab !=
null)
798 else if (t == typeof(
Item) && !containsHiddenContainers && identifier ==
"vent" &&
801 if (!hiddenContainerCreated)
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...");
805 if (!hiddenContainerCreated || hiddenContainerRNG.NextDouble() < 0.2)
807 replacementIdentifier =
"loosevent".ToIdentifier();
808 containsHiddenContainers =
true;
809 hiddenContainerCreated =
true;
816 if (loadMethod ==
null)
818 DebugConsole.ThrowError(
"Could not find the method \"Load\" in " + t +
".");
820 else if (!loadMethod.ReturnType.IsSubclassOf(typeof(
MapEntity)))
822 DebugConsole.ThrowError(
"Error loading entity of the type \"" + t.ToString() +
"\" - load method does not return a valid map entity.");
826 var newElement = element.FromPackage(
null);
827 if (!replacementIdentifier.IsEmpty)
829 newElement.SetAttributeValue(
"identifier", replacementIdentifier.ToString());
831 object newEntity = loadMethod.Invoke(t,
new object[] { newElement, submarine, idRemap });
832 if (newEntity !=
null)
838 catch (TargetInvocationException e)
840 DebugConsole.ThrowError(
"Error while loading entity of the type " + t +
".", e.InnerException);
844 DebugConsole.ThrowError(
"Error while loading entity of the type " + t +
".", e);
855 private bool mapLoadedCalled;
856 public static void MapLoaded(List<MapEntity> entities,
bool updateHulls)
858 InitializeLoadedLinks(entities);
860 List<LinkedSubmarine> linkedSubs =
new List<LinkedSubmarine>();
861 for (
int i = 0; i < entities.Count; i++)
863 if (entities[i].mapLoadedCalled || entities[i].Removed) {
continue; }
870 entities[i].OnMapLoaded();
879 entities.ForEach(e => e.mapLoadedCalled =
true);
886 CreateDroppedStacks(entities);
889 private static void CreateDroppedStacks(List<MapEntity> entities)
891 const float MaxDist = 10.0f;
892 List<Item> itemsInStack =
new List<Item>();
893 for (
int i = 0; i < entities.Count; i++)
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++)
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);
908 if (itemsInStack.Count > 1)
910 item1.CreateDroppedStack(itemsInStack, allowClientExecute:
true);
911 DebugConsole.Log($
"Merged x{itemsInStack.Count} of {item1.Name} into a dropped stack.");
920 if (e.mapLoadedCalled) {
continue; }
921 if (e.linkedToID ==
null) {
continue; }
922 if (e.linkedToID.Count == 0) {
continue; }
926 foreach (ushort i
in e.linkedToID)
928 if (FindEntityByID(i) is
MapEntity linked)
930 e.linkedTo.Add(linked);
935 DebugConsole.ThrowError($
"Linking the entity \"{e.Name}\" to another entity failed. Could not find an entity with the ID \"{i}\".");
939 e.linkedToID.Clear();
947 public virtual XElement
Save(XElement parentElement)
949 DebugConsole.ThrowError(
"Saving entity " + GetType() +
" failed.");
955 if (linkedTo ==
null)
return;
956 if (linkedTo.Contains(e)) linkedTo.Remove(e);
962 public HashSet<T> GetLinkedEntities<T>(HashSet<T> list =
null,
int? maxDepth =
null, Func<T, bool> filter =
null) where T :
MapEntity
964 list = list ??
new HashSet<T>();
966 GetLinkedEntitiesRecursive<T>(
this, list, ref startDepth, maxDepth, filter);
973 private static void GetLinkedEntitiesRecursive<T>(
MapEntity mapEntity, HashSet<T> linkedTargets, ref
int depth,
int? maxDepth =
null, Func<T, bool> filter =
null)
976 if (depth > maxDepth) {
return; }
977 foreach (var linkedEntity
in mapEntity.linkedTo)
979 if (linkedEntity is T linkedTarget)
981 if (!linkedTargets.Contains(linkedTarget) && (filter ==
null || filter(linkedTarget)))
983 linkedTargets.Add(linkedTarget);
985 GetLinkedEntitiesRecursive(linkedEntity, linkedTargets, ref depth, maxDepth, filter);
static GameSession GameSession
override void Update(float deltaTime, Camera cam)
static void UpdateHulls()
static List< Gap > GapList
static readonly List< Hull > HullList
override void Update(float deltaTime, Camera cam)
ushort GetOffsetId(XElement element)
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...
void SetNodes(List< Vector2 > nodes)
override void OnMapLoaded()
override string ToString()
HashSet< Item > UpdatePriorityItems
Mersenne Twister based random
virtual bool AddUpgrade(Upgrade upgrade, bool createNetworkEvent=false)
Adds a new upgrade to the item
IEnumerable< Identifier > AllowedLinks
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)
virtual void CheckIsHighlighted()
static void UpdateAll(float deltaTime, Camera cam)
Call Update() on every object in Entity.list
bool HasUpgrade(Identifier identifier)
virtual void Update(float deltaTime, Camera cam)
static int MapEntityUpdateInterval
static void MapLoaded(List< MapEntity > entities, bool updateHulls)
static int PoweredUpdateInterval
static IEnumerable< MapEntity > HighlightedEntities
void SetUpgrade(Upgrade upgrade, bool createNetworkEvent=false)
virtual void FlipY(bool relativeToSub)
Flip the entity vertically
readonly MapEntityPrefab Prefab
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 bool DrawBelowWater
List< ushort > linkedToID
virtual void OnMapLoaded()
void RemoveLinked(MapEntity e)
List< Upgrade > GetUpgrades()
void ResolveLinks(IdRemap childRemap)
virtual void Move(Vector2 amount, bool ignoreContacts=true)
static readonly HashSet< MapEntity > highlightedEntities
List< ushort > unresolvedLinkedToID
abstract MapEntity Clone()
virtual XElement Save(XElement parentElement)
readonly List< MapEntity > linkedTo
readonly List< Upgrade > Upgrades
List of upgrades this item has
static List< MapEntity > Clone(List< MapEntity > entitiesToClone)
virtual bool DrawOverWater
virtual bool IsMouseOn(Vector2 position)
static List< MapEntity > LoadAll(Submarine submarine, XElement parentElement, string filePath, int idOffset)
Upgrade GetUpgrade(Identifier identifier)
static void ClearHighlightedEntities()
string DisallowedUpgrades
Sprite(ContentXElement element, string path="", string file="", bool lazyLoad=false, float sourceRectScale=1)
override void Update(float deltaTime, Camera cam)
static List< Structure > WallList
static StructurePrefab FindPrefab(string name, Identifier identifier)
override Vector2? WorldPosition
static bool RectContains(Rectangle rect, Vector2 pos, bool inclusive=false)
override Vector2? Position