1 using Microsoft.Xna.Framework;
3 using System.Collections.Generic;
4 using System.Diagnostics;
17 public const ushort
DummyID = ushort.MaxValue - 2;
23 private static readonly Dictionary<ushort, Entity> dictionary =
new Dictionary<ushort, Entity>();
26 return dictionary.Values;
43 public readonly ushort
ID;
47 public virtual Vector2
Position => Vector2.Zero;
80 private readonly
double spawnTime;
82 private static UInt64 creationCounter = 0;
83 private readonly
static object creationCounterMutex =
new object();
88 => $
"- {ID}: {this} ({Submarine?.Info?.Name ?? "[
null]
"} {Submarine?.ID ?? 0}) {CreationStackTrace}";
93 spawnTime = Timing.TotalTime;
97 Dictionary<Identifier, int> entityCounts =
new Dictionary<Identifier, int>();
98 foreach (var entity
in dictionary)
102 if (entityCounts.ContainsKey(me.Prefab.Identifier))
104 entityCounts[me.Prefab.Identifier]++;
108 entityCounts[me.Prefab.Identifier] = 1;
112 string errorMsg = $
"Maximum amount of entities ({MaxEntityCount}) exceeded! Largest numbers of entities: " +
113 string.Join(
", ", entityCounts.OrderByDescending(kvp => kvp.Value).Take(10).Select(kvp => $
"{kvp.Key}: {kvp.Value}"));
114 throw new Exception(errorMsg);
120 if (dictionary.ContainsKey(
ID))
122 throw new Exception($
"ID {ID} is taken by {dictionary[ID]}");
125 dictionary.Add(
ID,
this);
129 var st =
new StackTrace(skipFrames: 2, fNeedFileInfo:
true);
130 var frames = st.GetFrames();
132 foreach (var frame
in frames)
134 string fileName = frame.GetFileName();
135 if ((fileName?.Contains(
"BarotraumaClient") ??
false) || (fileName?.Contains(
"BarotraumaServer") ??
false)) {
break; }
137 fileName = Path.GetFileNameWithoutExtension(fileName);
138 int fileLineNumber = frame.GetFileLineNumber();
140 if (fileName.IsNullOrEmpty() || fileLineNumber <= 0) {
continue; }
145 #warning TODO: consider removing this mutex, entity creation probably shouldn't be multithreaded
146 lock (creationCounterMutex)
157 : FindFreeId(submarine ==
null ? (ushort)1 : submarine.
IdOffset);
160 private static ushort FindFreeId(ushort idOffset)
164 throw new Exception($
"Maximum amount of entities ({MaxEntityCount}) reached!");
167 ushort
id = idOffset;
170 if (!dictionary.ContainsKey(
id)) {
break; }
182 int currentBlockSize = 0;
185 if (dictionary.ContainsKey((ushort)i))
187 currentBlockSize = 0;
192 if (currentBlockSize >= minBlockSize)
194 return i - (currentBlockSize-1);
207 dictionary.TryGetValue(
ID, out matchingEntity);
209 return matchingEntity;
214 List<Entity> list =
new List<Entity>(dictionary.Values);
215 foreach (
Entity e
in list)
221 catch (Exception exception)
223 DebugConsole.ThrowError($
"Error while removing entity \"{e}\"", exception);
224 GameAnalyticsManager.AddErrorEventOnce(
225 $
"Entity.RemoveAll:Exception{e}",
226 GameAnalyticsManager.ErrorSeverity.Error,
227 $
"Error while removing entity \"{e} ({exception.Message})\n{exception.StackTrace.CleanupStackTrace()}");
230 StringBuilder errorMsg =
new StringBuilder();
231 if (dictionary.Count > 0)
233 errorMsg.AppendLine(
"Some entities were not removed in Entity.RemoveAll:");
234 foreach (
Entity e
in dictionary.Values)
236 errorMsg.AppendLine(
" - " + e.ToString() +
"(ID " + e.
ID +
")");
241 errorMsg.AppendLine(
"Some items were not removed in Entity.RemoveAll:");
244 errorMsg.AppendLine(
" - " + item.
Name +
"(ID " + item.
ID +
")");
248 foreach (
Item item
in items)
254 catch (Exception exception)
256 DebugConsole.ThrowError($
"Error while removing item \"{item}\"", exception);
263 errorMsg.AppendLine(
"Some characters were not removed in Entity.RemoveAll:");
266 errorMsg.AppendLine(
" - " + character.
Name +
"(ID " + character.
ID +
")");
270 foreach (
Character character
in characters)
276 catch (Exception exception)
278 DebugConsole.ThrowError($
"Error while removing character \"{character}\"", exception);
284 if (!
string.IsNullOrEmpty(errorMsg.ToString()))
286 foreach (
string errorLine
in errorMsg.ToString().Split(
'\n'))
288 DebugConsole.ThrowError(errorLine);
290 GameAnalyticsManager.AddErrorEventOnce(
"Entity.RemoveAll", GameAnalyticsManager.ErrorSeverity.Error, errorMsg.ToString());
296 Items.Components.Projectile.ResetSpreadCounter();
305 DebugConsole.Log($
"Removing entity {ToString()} ({ID}) from entity dictionary.");
306 if (!dictionary.TryGetValue(
ID, out
Entity existingEntity))
308 DebugConsole.ThrowError($
"Entity {ToString()} ({ID}) not present in entity dictionary.");
309 GameAnalyticsManager.AddErrorEventOnce(
310 $
"Entity.FreeID:EntityNotFound{ID}",
311 GameAnalyticsManager.ErrorSeverity.Error,
312 $
"Entity {ToString()} ({ID}) not present in entity dictionary.\n{Environment.StackTrace.CleanupStackTrace()}");
314 else if (existingEntity !=
this)
316 DebugConsole.ThrowError($
"Entity ID mismatch in entity dictionary. Entity {existingEntity} had the ID {ID} (expecting {ToString()})");
317 GameAnalyticsManager.AddErrorEventOnce(
"Entity.FreeID:EntityMismatch" +
ID,
318 GameAnalyticsManager.ErrorSeverity.Error,
319 $
"Entity ID mismatch in entity dictionary. Entity {existingEntity} had the ID {ID} (expecting {ToString()})");
323 dictionary.Remove(
ID);
334 public static void DumpIds(
int count,
string filename)
336 List<Entity> entities = dictionary.Values.OrderByDescending(e => e.
ID).ToList();
338 count = Math.Min(entities.Count, count);
340 List<string> lines =
new List<string>();
341 for (
int i = 0; i < count; i++)
343 lines.Add($
"{entities[i].ID}: {entities[i]}");
344 DebugConsole.ThrowError($
"{entities[i].ID}: {entities[i]}");
347 if (!
string.IsNullOrWhiteSpace(filename))
349 File.WriteAllLines(filename, lines);
bool InDetectable
Should be reset to false each frame and kept indetectable by e.g. a status effect.
static readonly List< Character > CharacterList
const ushort ReservedIDStart
static EntitySpawner Spawner
virtual Vector2 WorldPosition
virtual Vector2 DrawPosition
Entity(Submarine submarine, ushort id)
static int FindFreeIdBlock(int minBlockSize)
Finds a contiguous block of free IDs of at least the given size
static void DumpIds(int count, string filename)
const ushort RespawnManagerID
const ushort EntitySpawnerID
static IReadOnlyCollection< Entity > GetEntities()
virtual Vector2 SimPosition
const ushort NullEntityID
readonly ushort ID
Unique, but non-persistent identifier. Stays the same if the entities are created in the exactly same...
void FreeID()
Removes the entity from the entity dictionary and frees up the ID it was using.
const ushort MaxEntityCount
readonly string CreationStackTrace
static Entity FindEntityByID(ushort ID)
Find an entity based on the ID
bool InDetectable
Indetectable characters can't be spotted by AIs and aren't visible on the sonar or health scanner.
readonly UInt64 CreationIndex
virtual ushort DetermineID(ushort id, Submarine submarine)
static readonly List< EntityGrid > EntityGrids
override string Name
Note that this is not a LocalizedString instance, just the current name of the item as a string....
static readonly List< Item > ItemList
override Vector2? Position