Client LuaCsForBarotrauma
BarotraumaShared/SharedSource/Map/Levels/LevelObjects/LevelObjectManager.cs
1 #if CLIENT
3 #endif
5 using FarseerPhysics;
6 using Microsoft.Xna.Framework;
7 using System;
8 using System.Collections.Generic;
9 using System.Linq;
10 using Voronoi2;
12 
13 namespace Barotrauma
14 {
16  {
17  const int GridSize = 2000;
18 
19  private List<LevelObject> objects;
20  private List<LevelObject> updateableObjects;
21  private List<LevelObject>[,] objectGrid;
22 
23  const float ParallaxStrength = 0.0001f;
24 
26  {
27  get;
28  private set;
29  }
30 
31  public LevelObjectManager() : base(null, Entity.NullEntityID)
32  {
33  }
34 
35  private readonly struct EventData : NetEntityEvent.IData
36  {
37  public readonly LevelObject LevelObject;
38 
39  public EventData(LevelObject levelObject)
40  {
41  LevelObject = levelObject;
42  }
43  }
44 
45  class SpawnPosition
46  {
47  public readonly GraphEdge GraphEdge;
48  public readonly Vector2 Normal;
49  public readonly List<LevelObjectPrefab.SpawnPosType> SpawnPosTypes = new List<LevelObjectPrefab.SpawnPosType>();
50  public readonly Alignment Alignment;
51  public readonly float Length;
52 
53  private readonly float noiseVal;
54 
55 
56  public SpawnPosition(GraphEdge graphEdge, Vector2 normal, LevelObjectPrefab.SpawnPosType spawnPosType, Alignment alignment)
57  : this(graphEdge, normal, spawnPosType.ToEnumerable(), alignment)
58  { }
59 
60  public SpawnPosition(GraphEdge graphEdge, Vector2 normal, IEnumerable<LevelObjectPrefab.SpawnPosType> spawnPosTypes, Alignment alignment)
61  {
62  GraphEdge = graphEdge;
63  Normal = normal.NearlyEquals(Vector2.Zero) ? Vector2.UnitY : Vector2.Normalize(normal);
64  SpawnPosTypes.AddRange(spawnPosTypes);
65 
66  if (spawnPosTypes.Contains(LevelObjectPrefab.SpawnPosType.MainPath) ||
67  spawnPosTypes.Contains(LevelObjectPrefab.SpawnPosType.LevelStart) ||
68  spawnPosTypes.Contains(LevelObjectPrefab.SpawnPosType.LevelEnd))
69  {
70  Length = 1000.0f;
71  Normal = Vector2.Zero;
72  Alignment = Alignment.Any;
73  }
74  else
75  {
76  Alignment = alignment;
77  Length = Vector2.Distance(graphEdge.Point1, graphEdge.Point2);
78  }
79 
80  noiseVal =
81  (float)(PerlinNoise.CalculatePerlin(GraphEdge.Point1.X / 10000.0f, GraphEdge.Point1.Y / 10000.0f, 0.5f) +
82  PerlinNoise.CalculatePerlin(GraphEdge.Point1.X / 20000.0f, GraphEdge.Point1.Y / 20000.0f, 0.5f));
83  }
84 
85  public float GetSpawnProbability(LevelObjectPrefab prefab)
86  {
87  if (prefab.ClusteringAmount <= 0.0f) { return Length; }
88  float noise = (noiseVal + PerlinNoise.GetPerlin(prefab.ClusteringGroup, prefab.ClusteringGroup * 0.3f)) % 1.0f;
89  return Length * (float)Math.Pow(noise, prefab.ClusteringAmount);
90  }
91  }
92 
93  public void PlaceObjects(Level level, int amount)
94  {
95  objectGrid = new List<LevelObject>[
96  level.Size.X / GridSize,
97  (level.Size.Y - level.BottomPos) / GridSize];
98 
99  List<SpawnPosition> availableSpawnPositions = new List<SpawnPosition>();
100  var levelCells = level.GetAllCells();
101  availableSpawnPositions.AddRange(GetAvailableSpawnPositions(levelCells, LevelObjectPrefab.SpawnPosType.Wall));
102  availableSpawnPositions.AddRange(GetAvailableSpawnPositions(level.SeaFloor.Cells, LevelObjectPrefab.SpawnPosType.SeaFloor));
103 
104  foreach (Structure structure in Structure.WallList)
105  {
106  if (!structure.HasBody || structure.IsHidden) { continue; }
107 
109  if (level.Ruins.Any(r => r.Submarine == structure.Submarine))
110  {
111  spawnPosType = LevelObjectPrefab.SpawnPosType.RuinWall;
112  }
113  else if (structure.Submarine?.Info?.Type == SubmarineType.Outpost)
114  {
115  spawnPosType = LevelObjectPrefab.SpawnPosType.OutpostWall;
116  }
117  else
118  {
119  continue;
120  }
121 
122  if (structure.IsHorizontal)
123  {
124  bool topHull = Hull.FindHull(structure.WorldPosition + Vector2.UnitY * 64) != null;
125  bool bottomHull = Hull.FindHull(structure.WorldPosition - Vector2.UnitY * 64) != null;
126  if (topHull && bottomHull) { continue; }
127 
128  availableSpawnPositions.Add(new SpawnPosition(
129  new GraphEdge(new Vector2(structure.WorldRect.X, structure.WorldPosition.Y), new Vector2(structure.WorldRect.Right, structure.WorldPosition.Y)),
130  bottomHull ? Vector2.UnitY : -Vector2.UnitY,
131  spawnPosType,
132  bottomHull ? Alignment.Bottom : Alignment.Top));
133  }
134  else
135  {
136  bool rightHull = Hull.FindHull(structure.WorldPosition + Vector2.UnitX * 64) != null;
137  bool leftHull = Hull.FindHull(structure.WorldPosition - Vector2.UnitX * 64) != null;
138  if (rightHull && leftHull) { continue; }
139 
140  availableSpawnPositions.Add(new SpawnPosition(
141  new GraphEdge(new Vector2(structure.WorldPosition.X, structure.WorldRect.Y), new Vector2(structure.WorldPosition.X, structure.WorldRect.Y - structure.WorldRect.Height)),
142  leftHull ? Vector2.UnitX : -Vector2.UnitX,
143  spawnPosType,
144  leftHull ? Alignment.Left : Alignment.Right));
145  }
146  }
147 
148  foreach (var posOfInterest in level.PositionsOfInterest)
149  {
150  if (posOfInterest.PositionType != Level.PositionType.MainPath && posOfInterest.PositionType != Level.PositionType.SidePath) { continue; }
151 
152  availableSpawnPositions.Add(new SpawnPosition(
153  new GraphEdge(posOfInterest.Position.ToVector2(), posOfInterest.Position.ToVector2() + Vector2.UnitX),
154  Vector2.UnitY,
156  Alignment.Top));
157  }
158 
159  availableSpawnPositions.Add(new SpawnPosition(
160  new GraphEdge(level.StartPosition - Vector2.UnitX, level.StartPosition + Vector2.UnitX),
161  -Vector2.UnitY, LevelObjectPrefab.SpawnPosType.LevelStart, Alignment.Top));
162  availableSpawnPositions.Add(new SpawnPosition(
163  new GraphEdge(level.EndPosition - Vector2.UnitX, level.EndPosition + Vector2.UnitX),
164  -Vector2.UnitY, LevelObjectPrefab.SpawnPosType.LevelEnd, Alignment.Top));
165 
166  var availablePrefabs =LevelObjectPrefab.Prefabs.OrderBy(p => p.UintIdentifier).ToList();
167  objects = new List<LevelObject>();
168  updateableObjects = new List<LevelObject>();
169 
170  Dictionary<LevelObjectPrefab, List<SpawnPosition>> suitableSpawnPositions = new Dictionary<LevelObjectPrefab, List<SpawnPosition>>();
171  Dictionary<LevelObjectPrefab, List<float>> spawnPositionWeights = new Dictionary<LevelObjectPrefab, List<float>>();
172  for (int i = 0; i < amount; i++)
173  {
174  //get a random prefab and find a place to spawn it
175  LevelObjectPrefab prefab = GetRandomPrefab(level, availablePrefabs);
176  if (prefab == null) { continue; }
177  if (!suitableSpawnPositions.ContainsKey(prefab))
178  {
179  float minDistance = level.Size.X * 0.2f;
180 
181  suitableSpawnPositions.Add(prefab,
182  availableSpawnPositions.Where(sp =>
183  sp.SpawnPosTypes.Any(type => prefab.SpawnPos.HasFlag(type)) &&
184  sp.Length >= prefab.MinSurfaceWidth &&
185  (prefab.AllowAtStart || !level.IsCloseToStart(sp.GraphEdge.Center, minDistance)) &&
186  (prefab.AllowAtEnd || !level.IsCloseToEnd(sp.GraphEdge.Center, minDistance)) &&
187  (sp.Alignment == Alignment.Any || prefab.Alignment.HasFlag(sp.Alignment))).ToList());
188 
189  spawnPositionWeights.Add(prefab,
190  suitableSpawnPositions[prefab].Select(sp => sp.GetSpawnProbability(prefab)).ToList());
191  }
192 
193  SpawnPosition spawnPosition = ToolBox.SelectWeightedRandom(suitableSpawnPositions[prefab], spawnPositionWeights[prefab], Rand.RandSync.ServerAndClient);
194  if (spawnPosition == null && prefab.SpawnPos != LevelObjectPrefab.SpawnPosType.None) { continue; }
195  PlaceObject(prefab, spawnPosition, level);
196  if (prefab.MaxCount < amount)
197  {
198  if (objects.Count(o => o.Prefab == prefab) >= prefab.MaxCount)
199  {
200  availablePrefabs.Remove(prefab);
201  }
202  }
203  }
204 
205  foreach (Level.Cave cave in level.Caves)
206  {
207  availablePrefabs = LevelObjectPrefab.Prefabs.Where(p => p.SpawnPos.HasFlag(LevelObjectPrefab.SpawnPosType.CaveWall))
208  .OrderBy(p => p.UintIdentifier).ToList();
209  availableSpawnPositions.Clear();
210  suitableSpawnPositions.Clear();
211  spawnPositionWeights.Clear();
212 
213  var caveCells = cave.Tunnels.SelectMany(t => t.Cells);
214  List<VoronoiCell> caveWallCells = new List<VoronoiCell>();
215  foreach (var edge in caveCells.SelectMany(c => c.Edges))
216  {
217  if (!edge.NextToCave) { continue; }
218  if (edge.Cell1?.CellType == CellType.Solid) { caveWallCells.Add(edge.Cell1); }
219  if (edge.Cell2?.CellType == CellType.Solid) { caveWallCells.Add(edge.Cell2); }
220  }
221  availableSpawnPositions.AddRange(GetAvailableSpawnPositions(caveWallCells.Distinct(), LevelObjectPrefab.SpawnPosType.CaveWall));
222 
223  for (int i = 0; i < cave.CaveGenerationParams.LevelObjectAmount; i++)
224  {
225  //get a random prefab and find a place to spawn it
226  LevelObjectPrefab prefab = GetRandomPrefab(cave.CaveGenerationParams, availablePrefabs, requireCaveSpecificOverride: true);
227  if (prefab == null) { continue; }
228  if (!suitableSpawnPositions.ContainsKey(prefab))
229  {
230  suitableSpawnPositions.Add(prefab,
231  availableSpawnPositions.Where(sp =>
232  sp.Length >= prefab.MinSurfaceWidth &&
233  (sp.Alignment == Alignment.Any || prefab.Alignment.HasFlag(sp.Alignment))).ToList());
234  spawnPositionWeights.Add(prefab,
235  suitableSpawnPositions[prefab].Select(sp => sp.GetSpawnProbability(prefab)).ToList());
236  }
237  SpawnPosition spawnPosition = ToolBox.SelectWeightedRandom(suitableSpawnPositions[prefab], spawnPositionWeights[prefab], Rand.RandSync.ServerAndClient);
238  if (spawnPosition == null && prefab.SpawnPos != LevelObjectPrefab.SpawnPosType.None) { continue; }
239  PlaceObject(prefab, spawnPosition, level, cave);
240  if (amount > prefab.MaxCount && objects.Count > prefab.MaxCount)
241  {
242  int objectCount = 0;
243  for (int j = 0; j < objects.Count; j++)
244  {
245  if (objects[j].Prefab == prefab && objects[j].ParentCave == cave)
246  {
247  objectCount++;
248  if (objectCount >= prefab.MaxCount)
249  {
250  break;
251  }
252  }
253  }
254  if (objectCount >= prefab.MaxCount)
255  {
256  availablePrefabs.Remove(prefab);
257  }
258  }
259  }
260  }
261  }
262 
263  public void PlaceNestObjects(Level level, Level.Cave cave, Vector2 nestPosition, float nestRadius, int objectAmount)
264  {
265  Rand.SetSyncedSeed(ToolBox.StringToInt(level.Seed));
266 
267  var availablePrefabs = LevelObjectPrefab.Prefabs.Where(p => p.SpawnPos.HasFlag(LevelObjectPrefab.SpawnPosType.NestWall))
268  .OrderBy(p => p.UintIdentifier).ToList();
269  Dictionary<LevelObjectPrefab, List<SpawnPosition>> suitableSpawnPositions = new Dictionary<LevelObjectPrefab, List<SpawnPosition>>();
270  Dictionary<LevelObjectPrefab, List<float>> spawnPositionWeights = new Dictionary<LevelObjectPrefab, List<float>>();
271 
272  List<SpawnPosition> availableSpawnPositions = new List<SpawnPosition>();
273  var caveCells = cave.Tunnels.SelectMany(t => t.Cells);
274  List<VoronoiCell> caveWallCells = new List<VoronoiCell>();
275  foreach (var edge in caveCells.SelectMany(c => c.Edges))
276  {
277  if (!edge.NextToCave) { continue; }
278  if (MathUtils.LineSegmentToPointDistanceSquared(edge.Point1.ToPoint(), edge.Point2.ToPoint(), nestPosition.ToPoint()) > nestRadius * nestRadius) { continue; }
279  if (edge.Cell1?.CellType == CellType.Solid) { caveWallCells.Add(edge.Cell1); }
280  if (edge.Cell2?.CellType == CellType.Solid) { caveWallCells.Add(edge.Cell2); }
281  }
282  availableSpawnPositions.AddRange(GetAvailableSpawnPositions(caveWallCells.Distinct(), LevelObjectPrefab.SpawnPosType.CaveWall));
283 
284  for (int i = 0; i < objectAmount; i++)
285  {
286  //get a random prefab and find a place to spawn it
287  LevelObjectPrefab prefab = GetRandomPrefab(cave.CaveGenerationParams, availablePrefabs, requireCaveSpecificOverride: false);
288  if (prefab == null) { continue; }
289  if (!suitableSpawnPositions.ContainsKey(prefab))
290  {
291  suitableSpawnPositions.Add(prefab,
292  availableSpawnPositions.Where(sp =>
293  sp.Length >= prefab.MinSurfaceWidth &&
294  (sp.Alignment == Alignment.Any || prefab.Alignment.HasFlag(sp.Alignment))).ToList());
295  spawnPositionWeights.Add(prefab,
296  suitableSpawnPositions[prefab].Select(sp => sp.GetSpawnProbability(prefab)).ToList());
297  }
298  SpawnPosition spawnPosition = ToolBox.SelectWeightedRandom(suitableSpawnPositions[prefab], spawnPositionWeights[prefab], Rand.RandSync.ServerAndClient);
299  if (spawnPosition == null && prefab.SpawnPos != LevelObjectPrefab.SpawnPosType.None) { continue; }
300  PlaceObject(prefab, spawnPosition, level);
301  if (objects.Count(o => o.Prefab == prefab) >= prefab.MaxCount)
302  {
303  availablePrefabs.Remove(prefab);
304  }
305  }
306  }
307 
308  private void PlaceObject(LevelObjectPrefab prefab, SpawnPosition spawnPosition, Level level, Level.Cave parentCave = null)
309  {
310  float rotation = 0.0f;
311  if (prefab.AlignWithSurface && spawnPosition != null && spawnPosition.Normal.LengthSquared() > 0.001f)
312  {
313  rotation = MathUtils.VectorToAngle(new Vector2(spawnPosition.Normal.Y, spawnPosition.Normal.X));
314  }
315  rotation += Rand.Range(prefab.RandomRotationRad.X, prefab.RandomRotationRad.Y, Rand.RandSync.ServerAndClient);
316 
317  Vector2 position = Vector2.Zero;
318  Vector2 edgeDir = Vector2.UnitX;
319  if (spawnPosition == null)
320  {
321  position = new Vector2(
322  Rand.Range(0.0f, level.Size.X, Rand.RandSync.ServerAndClient),
323  Rand.Range(0.0f, level.Size.Y, Rand.RandSync.ServerAndClient));
324  }
325  else
326  {
327  edgeDir = (spawnPosition.GraphEdge.Point1 - spawnPosition.GraphEdge.Point2) / spawnPosition.Length;
328  position = spawnPosition.GraphEdge.Point2 + edgeDir * Rand.Range(prefab.MinSurfaceWidth / 2.0f, spawnPosition.Length - prefab.MinSurfaceWidth / 2.0f, Rand.RandSync.ServerAndClient);
329  }
330 
331  if (!MathUtils.NearlyEqual(prefab.RandomOffset.X, 0.0f) || !MathUtils.NearlyEqual(prefab.RandomOffset.Y, 0.0f))
332  {
333  Vector2 offsetDir = spawnPosition.Normal.LengthSquared() > 0.001f ? spawnPosition.Normal : Rand.Vector(1.0f, Rand.RandSync.ServerAndClient);
334  position += offsetDir * Rand.Range(prefab.RandomOffset.X, prefab.RandomOffset.Y, Rand.RandSync.ServerAndClient);
335  }
336 
337  var newObject = new LevelObject(prefab,
338  new Vector3(position, Rand.Range(prefab.DepthRange.X, prefab.DepthRange.Y, Rand.RandSync.ServerAndClient)), Rand.Range(prefab.MinSize, prefab.MaxSize, Rand.RandSync.ServerAndClient), rotation);
339  AddObject(newObject, level);
340  newObject.ParentCave = parentCave;
341 
342  foreach (LevelObjectPrefab.ChildObject child in prefab.ChildObjects)
343  {
344  int childCount = Rand.Range(child.MinCount, child.MaxCount + 1, Rand.RandSync.ServerAndClient);
345  for (int j = 0; j < childCount; j++)
346  {
347  var matchingPrefabs = LevelObjectPrefab.Prefabs.Where(p => child.AllowedNames.Contains(p.Name));
348  int prefabCount = matchingPrefabs.Count();
349  var childPrefab = prefabCount == 0 ? null : matchingPrefabs.ElementAt(Rand.Range(0, prefabCount, Rand.RandSync.ServerAndClient));
350  if (childPrefab == null) { continue; }
351 
352  Vector2 childPos = position + edgeDir * Rand.Range(-0.5f, 0.5f, Rand.RandSync.ServerAndClient) * prefab.MinSurfaceWidth;
353 
354  var childObject = new LevelObject(childPrefab,
355  new Vector3(childPos, Rand.Range(childPrefab.DepthRange.X, childPrefab.DepthRange.Y, Rand.RandSync.ServerAndClient)),
356  Rand.Range(childPrefab.MinSize, childPrefab.MaxSize, Rand.RandSync.ServerAndClient),
357  rotation + Rand.Range(childPrefab.RandomRotationRad.X, childPrefab.RandomRotationRad.Y, Rand.RandSync.ServerAndClient));
358 
359  AddObject(childObject, level);
360  childObject.ParentCave = parentCave;
361  }
362  }
363  }
364 
365  private void AddObject(LevelObject newObject, Level level)
366  {
367  if (newObject.Triggers != null)
368  {
369  foreach (LevelTrigger trigger in newObject.Triggers)
370  {
371  trigger.OnTriggered += (levelTrigger, obj) =>
372  {
373  OnObjectTriggered(newObject, levelTrigger, obj);
374  };
375  }
376  }
377 
378  var spriteCorners = new List<Vector2>
379  {
380  Vector2.Zero, Vector2.Zero, Vector2.Zero, Vector2.Zero
381  };
382 
383  Sprite sprite = newObject.Sprite ?? newObject.Prefab.DeformableSprite?.Sprite;
384 
385  //calculate the positions of the corners of the rotated sprite
386  if (sprite != null)
387  {
388  Vector2 halfSize = sprite.size * newObject.Scale / 2;
389  spriteCorners[0] = -halfSize;
390  spriteCorners[1] = new Vector2(-halfSize.X, halfSize.Y);
391  spriteCorners[2] = halfSize;
392  spriteCorners[3] = new Vector2(halfSize.X, -halfSize.Y);
393 
394  Vector2 pivotOffset = sprite.Origin * newObject.Scale - halfSize;
395  pivotOffset.X = -pivotOffset.X;
396  pivotOffset = new Vector2(
397  (float)(pivotOffset.X * Math.Cos(-newObject.Rotation) - pivotOffset.Y * Math.Sin(-newObject.Rotation)),
398  (float)(pivotOffset.X * Math.Sin(-newObject.Rotation) + pivotOffset.Y * Math.Cos(-newObject.Rotation)));
399 
400  for (int j = 0; j < 4; j++)
401  {
402  spriteCorners[j] = new Vector2(
403  (float)(spriteCorners[j].X * Math.Cos(-newObject.Rotation) - spriteCorners[j].Y * Math.Sin(-newObject.Rotation)),
404  (float)(spriteCorners[j].X * Math.Sin(-newObject.Rotation) + spriteCorners[j].Y * Math.Cos(-newObject.Rotation)));
405 
406  spriteCorners[j] += new Vector2(newObject.Position.X, newObject.Position.Y) + pivotOffset;
407  }
408  }
409 
410  float minX = spriteCorners.Min(c => c.X) - newObject.Position.Z * ParallaxStrength;
411  float maxX = spriteCorners.Max(c => c.X) + newObject.Position.Z * ParallaxStrength;
412 
413  float minY = spriteCorners.Min(c => c.Y) - newObject.Position.Z * ParallaxStrength - level.BottomPos;
414  float maxY = spriteCorners.Max(c => c.Y) + newObject.Position.Z * ParallaxStrength - level.BottomPos;
415 
416  if (newObject.Triggers != null)
417  {
418  foreach (LevelTrigger trigger in newObject.Triggers)
419  {
420  if (trigger.PhysicsBody == null) { continue; }
421  for (int i = 0; i < trigger.PhysicsBody.FarseerBody.FixtureList.Count; i++)
422  {
423  trigger.PhysicsBody.FarseerBody.GetTransform(out FarseerPhysics.Common.Transform transform);
424  trigger.PhysicsBody.FarseerBody.FixtureList[i].Shape.ComputeAABB(out FarseerPhysics.Collision.AABB aabb, ref transform, i);
425 
426  minX = Math.Min(minX, ConvertUnits.ToDisplayUnits(aabb.LowerBound.X));
427  maxX = Math.Max(maxX, ConvertUnits.ToDisplayUnits(aabb.UpperBound.X));
428  minY = Math.Min(minY, ConvertUnits.ToDisplayUnits(aabb.LowerBound.Y) - level.BottomPos);
429  maxY = Math.Max(maxY, ConvertUnits.ToDisplayUnits(aabb.UpperBound.Y) - level.BottomPos);
430  }
431  }
432  }
433 
434 #if CLIENT
435  if (newObject.ParticleEmitters != null)
436  {
437  foreach (ParticleEmitter emitter in newObject.ParticleEmitters)
438  {
439  Rectangle particleBounds = emitter.CalculateParticleBounds(new Vector2(newObject.Position.X, newObject.Position.Y));
440  minX = Math.Min(minX, particleBounds.X);
441  maxX = Math.Max(maxX, particleBounds.Right);
442  minY = Math.Min(minY, particleBounds.Y - level.BottomPos);
443  maxY = Math.Max(maxY, particleBounds.Bottom - level.BottomPos);
444  }
445  }
446 #endif
447  objects.Add(newObject);
448  if (newObject.NeedsUpdate) { updateableObjects.Add(newObject); }
449  newObject.Position.Z += (minX + minY) % 100.0f * 0.00001f;
450 
451  int xStart = (int)Math.Floor(minX / GridSize);
452  int xEnd = (int)Math.Floor(maxX / GridSize);
453  if (xEnd < 0 || xStart >= objectGrid.GetLength(0)) { return; }
454 
455  int yStart = (int)Math.Floor(minY / GridSize);
456  int yEnd = (int)Math.Floor(maxY / GridSize);
457  if (yEnd < 0 || yStart >= objectGrid.GetLength(1)) { return; }
458 
459  xStart = Math.Max(xStart, 0);
460  xEnd = Math.Min(xEnd, objectGrid.GetLength(0) - 1);
461  yStart = Math.Max(yStart, 0);
462  yEnd = Math.Min(yEnd, objectGrid.GetLength(1) - 1);
463 
464  for (int x = xStart; x <= xEnd; x++)
465  {
466  for (int y = yStart; y <= yEnd; y++)
467  {
468  var list = objectGrid[x, y];
469  if (list == null) { objectGrid[x, y] = list = new List<LevelObject>(); }
470 
471  //insertion sort in ascending order (= prefer rendering objects in front)
472  int drawOrderIndex = 0;
473  while (drawOrderIndex < list.Count && list[drawOrderIndex].Position.Z < newObject.Position.Z)
474  {
475  drawOrderIndex++;
476  }
477  list.Insert(drawOrderIndex, newObject);
478  }
479  }
480  }
481 
482  public static Point GetGridIndices(Vector2 worldPosition)
483  {
484  return new Point(
485  (int)Math.Floor(worldPosition.X / GridSize),
486  (int)Math.Floor((worldPosition.Y - Level.Loaded.BottomPos) / GridSize));
487  }
488 
489  public IEnumerable<LevelObject> GetAllObjects()
490  {
491  return objects;
492  }
493 
494  private readonly static HashSet<LevelObject> objectsInRange = new HashSet<LevelObject>();
495  public IEnumerable<LevelObject> GetAllObjects(Vector2 worldPosition, float radius)
496  {
497  var minIndices = GetGridIndices(worldPosition - Vector2.One * radius);
498  if (minIndices.X >= objectGrid.GetLength(0) || minIndices.Y >= objectGrid.GetLength(1)) { return Enumerable.Empty<LevelObject>(); }
499 
500  var maxIndices = GetGridIndices(worldPosition + Vector2.One * radius);
501  if (maxIndices.X < 0 || maxIndices.Y < 0) { return Enumerable.Empty<LevelObject>(); }
502 
503  minIndices.X = Math.Max(0, minIndices.X);
504  minIndices.Y = Math.Max(0, minIndices.Y);
505  maxIndices.X = Math.Min(objectGrid.GetLength(0) - 1, maxIndices.X);
506  maxIndices.Y = Math.Min(objectGrid.GetLength(1) - 1, maxIndices.Y);
507 
508  objectsInRange.Clear();
509  for (int x = minIndices.X; x <= maxIndices.X; x++)
510  {
511  for (int y = minIndices.Y; y <= maxIndices.Y; y++)
512  {
513  if (objectGrid[x, y] == null) { continue; }
514  foreach (LevelObject obj in objectGrid[x, y])
515  {
516  if (obj.Prefab.HideWhenBroken && obj.Health <= 0.0f) { continue; }
517  objectsInRange.Add(obj);
518  }
519  }
520  }
521 
522  return objectsInRange;
523  }
524 
525  private static List<SpawnPosition> GetAvailableSpawnPositions(IEnumerable<VoronoiCell> cells, LevelObjectPrefab.SpawnPosType spawnPosType)
526  {
527  List<LevelObjectPrefab.SpawnPosType> spawnPosTypes = new List<LevelObjectPrefab.SpawnPosType>(4);
528  List<SpawnPosition> availableSpawnPositions = new List<SpawnPosition>();
529  bool requireCaveSpawnPos = spawnPosType == LevelObjectPrefab.SpawnPosType.CaveWall;
530  foreach (var cell in cells)
531  {
532  foreach (var edge in cell.Edges)
533  {
534  if (!edge.IsSolid || edge.OutsideLevel) { continue; }
535  if (requireCaveSpawnPos != edge.NextToCave) { continue; }
536  Vector2 normal = edge.GetNormal(cell);
537 
538  Alignment edgeAlignment = 0;
539  if (normal.Y < -0.5f)
540  edgeAlignment |= Alignment.Bottom;
541  else if (normal.Y > 0.5f)
542  edgeAlignment |= Alignment.Top;
543  else if (normal.X < -0.5f)
544  edgeAlignment |= Alignment.Left;
545  else if(normal.X > 0.5f)
546  edgeAlignment |= Alignment.Right;
547 
548  spawnPosTypes.Clear();
549  spawnPosTypes.Add(spawnPosType);
550  if (spawnPosType.HasFlag(LevelObjectPrefab.SpawnPosType.MainPathWall) && edge.NextToMainPath) { spawnPosTypes.Add(LevelObjectPrefab.SpawnPosType.MainPathWall); }
551  if (spawnPosType.HasFlag(LevelObjectPrefab.SpawnPosType.SidePathWall) && edge.NextToSidePath) { spawnPosTypes.Add(LevelObjectPrefab.SpawnPosType.SidePathWall); }
552  if (spawnPosType.HasFlag(LevelObjectPrefab.SpawnPosType.CaveWall) && edge.NextToCave) { spawnPosTypes.Add(LevelObjectPrefab.SpawnPosType.CaveWall); }
553 
554  availableSpawnPositions.Add(new SpawnPosition(edge, normal, spawnPosTypes, edgeAlignment));
555  }
556  }
557  return availableSpawnPositions;
558  }
559 
560  public void Update(float deltaTime)
561  {
562  GlobalForceDecreaseTimer += deltaTime;
563  if (GlobalForceDecreaseTimer > 1000000.0f)
564  {
566  }
567 
568  if (updateableObjects is not null)
569  {
570  foreach (LevelObject obj in updateableObjects)
571  {
572  if (GameMain.NetworkMember is { IsServer: true })
573  {
574  obj.NetworkUpdateTimer -= deltaTime;
575  if (obj.NeedsNetworkSyncing && obj.NetworkUpdateTimer <= 0.0f)
576  {
577  GameMain.NetworkMember.CreateEntityEvent(this, new EventData(obj));
578  obj.NeedsNetworkSyncing = false;
579  obj.NetworkUpdateTimer = NetConfig.LevelObjectUpdateInterval;
580  }
581  }
582  if (obj.Prefab.HideWhenBroken && obj.Health <= 0.0f) { continue; }
583 
584  if (obj.Triggers != null)
585  {
586  obj.ActivePrefab = obj.Prefab;
587  for (int i = 0; i < obj.Triggers.Count; i++)
588  {
589  obj.Triggers[i].Update(deltaTime);
590  if (obj.Triggers[i].IsTriggered && obj.Prefab.OverrideProperties[i] != null)
591  {
593  }
594  }
595  }
596 
597  if (obj.PhysicsBody != null)
598  {
599  if (obj.Prefab.PhysicsBodyTriggerIndex > -1) { obj.PhysicsBody.Enabled = obj.Triggers[obj.Prefab.PhysicsBodyTriggerIndex].IsTriggered; }
600  /*obj.Position = new Vector3(obj.PhysicsBody.Position, obj.Position.Z);
601  obj.Rotation = -obj.PhysicsBody.Rotation;*/
602  }
603  }
604  }
605 
606  UpdateProjSpecific(deltaTime);
607  }
608 
609  partial void UpdateProjSpecific(float deltaTime);
610 
611  private void OnObjectTriggered(LevelObject triggeredObject, LevelTrigger trigger, Entity triggerer)
612  {
613  if (trigger.TriggerOthersDistance <= 0.0f) { return; }
614  foreach (LevelObject obj in objects)
615  {
616  if (obj == triggeredObject || obj.Triggers == null) { continue; }
617  foreach (LevelTrigger otherTrigger in obj.Triggers)
618  {
619  otherTrigger.OtherTriggered(trigger, triggerer);
620  }
621  }
622  }
623 
624  private static LevelObjectPrefab GetRandomPrefab(Level level, IList<LevelObjectPrefab> availablePrefabs)
625  {
626  if (availablePrefabs.Sum(p => p.GetCommonness(level.LevelData)) <= 0.0f) { return null; }
627  return ToolBox.SelectWeightedRandom(
628  availablePrefabs,
629  availablePrefabs.Select(p => p.GetCommonness(level.LevelData)).ToList(), Rand.RandSync.ServerAndClient);
630  }
631 
632  private static LevelObjectPrefab GetRandomPrefab(CaveGenerationParams caveParams, IList<LevelObjectPrefab> availablePrefabs, bool requireCaveSpecificOverride)
633  {
634  if (availablePrefabs.Sum(p => p.GetCommonness(caveParams, requireCaveSpecificOverride)) <= 0.0f) { return null; }
635  return ToolBox.SelectWeightedRandom(
636  availablePrefabs,
637  availablePrefabs.Select(p => p.GetCommonness(caveParams, requireCaveSpecificOverride)).ToList(), Rand.RandSync.ServerAndClient);
638  }
639 
640  public override void Remove()
641  {
642  objectsInRange.Clear();
643  if (objects != null)
644  {
645  foreach (LevelObject obj in objects)
646  {
647  obj.Remove();
648  }
649  objects.Clear();
650  updateableObjects.Clear();
651  }
652  RemoveProjSpecific();
653 
654  base.Remove();
655  }
656 
657  partial void RemoveProjSpecific();
658 
659  public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
660  {
661  if (extraData is not EventData eventData) { throw new Exception($"Malformed LevelObjectManager event: expected {nameof(LevelObjectManager)}.{nameof(EventData)}"); }
662  LevelObject obj = eventData.LevelObject;
663  msg.WriteRangedInteger(objects.IndexOf(obj), 0, objects.Count);
664  obj.ServerWrite(msg, c);
665  }
666  }
667 }
Submarine Submarine
Definition: Entity.cs:53
const ushort NullEntityID
Definition: Entity.cs:14
static NetworkMember NetworkMember
Definition: GameMain.cs:190
static Hull FindHull(Vector2 position, Hull guess=null, bool useWorldCoordinates=true, bool inclusive=true)
Returns the hull which contains the point (or null if it isn't inside any)
LevelObject(LevelObjectPrefab prefab, Vector3 position, float scale, float rotation=0.0f)
void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData=null)
void PlaceNestObjects(Level level, Level.Cave cave, Vector2 nestPosition, float nestRadius, int objectAmount)
IEnumerable< LevelObject > GetAllObjects(Vector2 worldPosition, float radius)
List< LevelObjectPrefab > OverrideProperties
A list of prefabs whose properties override this one's properties when a trigger is active....
float MinSurfaceWidth
Minimum length of a graph edge the object can spawn on.
Alignment Alignment
Which sides of a wall the object can appear on.
bool IsHidden
Is the entity hidden due to HiddenInGame being enabled or the layer the entity is in being hidden?
Rectangle CalculateParticleBounds(Vector2 startPosition)
Interface for entities that the server can send events to the clients
void WriteRangedInteger(int val, int min, int max)