Server LuaCsForBarotrauma
LevelObjectManager.cs
1 #if CLIENT
2 using Barotrauma.Particles;
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  bool allowAtStart = prefab.AllowAtStart;
182  bool allowAtEnd = prefab.AllowAtEnd;
184  {
185  //in PvP mode, the object must be allowed at both the start and end to be placed at either end
186  //since the 2nd team starts at the end of the level, it'd be unfair to allow e.g. ballast flora to spawn at the end of the level but not the start
187  allowAtEnd = allowAtStart = allowAtEnd && allowAtStart;
188  }
189 
190  suitableSpawnPositions.Add(prefab,
191  availableSpawnPositions.Where(sp =>
192  sp.SpawnPosTypes.Any(type => prefab.SpawnPos.HasFlag(type)) &&
193  sp.Length >= prefab.MinSurfaceWidth &&
194  (allowAtStart || !level.IsCloseToStart(sp.GraphEdge.Center, minDistance)) &&
195  (allowAtEnd || !level.IsCloseToEnd(sp.GraphEdge.Center, minDistance)) &&
196  (sp.Alignment == Alignment.Any || prefab.Alignment.HasFlag(sp.Alignment))).ToList());
197 
198  spawnPositionWeights.Add(prefab,
199  suitableSpawnPositions[prefab].Select(sp => sp.GetSpawnProbability(prefab)).ToList());
200  }
201 
202  SpawnPosition spawnPosition = ToolBox.SelectWeightedRandom(suitableSpawnPositions[prefab], spawnPositionWeights[prefab], Rand.RandSync.ServerAndClient);
203  if (spawnPosition == null && prefab.SpawnPos != LevelObjectPrefab.SpawnPosType.None) { continue; }
204  PlaceObject(prefab, spawnPosition, level);
205  if (prefab.MaxCount < amount)
206  {
207  if (objects.Count(o => o.Prefab == prefab) >= prefab.MaxCount)
208  {
209  availablePrefabs.Remove(prefab);
210  }
211  }
212  }
213 
214  foreach (Level.Cave cave in level.Caves)
215  {
216  availablePrefabs = LevelObjectPrefab.Prefabs.Where(p => p.SpawnPos.HasFlag(LevelObjectPrefab.SpawnPosType.CaveWall))
217  .OrderBy(p => p.UintIdentifier).ToList();
218  availableSpawnPositions.Clear();
219  suitableSpawnPositions.Clear();
220  spawnPositionWeights.Clear();
221 
222  var caveCells = cave.Tunnels.SelectMany(t => t.Cells);
223  List<VoronoiCell> caveWallCells = new List<VoronoiCell>();
224  foreach (var edge in caveCells.SelectMany(c => c.Edges))
225  {
226  if (!edge.NextToCave) { continue; }
227  if (edge.Cell1?.CellType == CellType.Solid) { caveWallCells.Add(edge.Cell1); }
228  if (edge.Cell2?.CellType == CellType.Solid) { caveWallCells.Add(edge.Cell2); }
229  }
230  availableSpawnPositions.AddRange(GetAvailableSpawnPositions(caveWallCells.Distinct(), LevelObjectPrefab.SpawnPosType.CaveWall));
231 
232  for (int i = 0; i < cave.CaveGenerationParams.LevelObjectAmount; i++)
233  {
234  //get a random prefab and find a place to spawn it
235  LevelObjectPrefab prefab = GetRandomPrefab(cave.CaveGenerationParams, availablePrefabs, requireCaveSpecificOverride: true);
236  if (prefab == null) { continue; }
237  if (!suitableSpawnPositions.ContainsKey(prefab))
238  {
239  suitableSpawnPositions.Add(prefab,
240  availableSpawnPositions.Where(sp =>
241  sp.Length >= prefab.MinSurfaceWidth &&
242  (sp.Alignment == Alignment.Any || prefab.Alignment.HasFlag(sp.Alignment))).ToList());
243  spawnPositionWeights.Add(prefab,
244  suitableSpawnPositions[prefab].Select(sp => sp.GetSpawnProbability(prefab)).ToList());
245  }
246  SpawnPosition spawnPosition = ToolBox.SelectWeightedRandom(suitableSpawnPositions[prefab], spawnPositionWeights[prefab], Rand.RandSync.ServerAndClient);
247  if (spawnPosition == null && prefab.SpawnPos != LevelObjectPrefab.SpawnPosType.None) { continue; }
248  PlaceObject(prefab, spawnPosition, level, cave);
249  if (amount > prefab.MaxCount && objects.Count > prefab.MaxCount)
250  {
251  int objectCount = 0;
252  for (int j = 0; j < objects.Count; j++)
253  {
254  if (objects[j].Prefab == prefab && objects[j].ParentCave == cave)
255  {
256  objectCount++;
257  if (objectCount >= prefab.MaxCount)
258  {
259  break;
260  }
261  }
262  }
263  if (objectCount >= prefab.MaxCount)
264  {
265  availablePrefabs.Remove(prefab);
266  }
267  }
268  }
269  }
270  }
271 
272  public void PlaceNestObjects(Level level, Level.Cave cave, Vector2 nestPosition, float nestRadius, int objectAmount)
273  {
274  Rand.SetSyncedSeed(ToolBox.StringToInt(level.Seed));
275 
276  var availablePrefabs = LevelObjectPrefab.Prefabs.Where(p => p.SpawnPos.HasFlag(LevelObjectPrefab.SpawnPosType.NestWall))
277  .OrderBy(p => p.UintIdentifier).ToList();
278  Dictionary<LevelObjectPrefab, List<SpawnPosition>> suitableSpawnPositions = new Dictionary<LevelObjectPrefab, List<SpawnPosition>>();
279  Dictionary<LevelObjectPrefab, List<float>> spawnPositionWeights = new Dictionary<LevelObjectPrefab, List<float>>();
280 
281  List<SpawnPosition> availableSpawnPositions = new List<SpawnPosition>();
282  var caveCells = cave.Tunnels.SelectMany(t => t.Cells);
283  List<VoronoiCell> caveWallCells = new List<VoronoiCell>();
284  foreach (var edge in caveCells.SelectMany(c => c.Edges))
285  {
286  if (!edge.NextToCave) { continue; }
287  if (MathUtils.LineSegmentToPointDistanceSquared(edge.Point1.ToPoint(), edge.Point2.ToPoint(), nestPosition.ToPoint()) > nestRadius * nestRadius) { continue; }
288  if (edge.Cell1?.CellType == CellType.Solid) { caveWallCells.Add(edge.Cell1); }
289  if (edge.Cell2?.CellType == CellType.Solid) { caveWallCells.Add(edge.Cell2); }
290  }
291  availableSpawnPositions.AddRange(GetAvailableSpawnPositions(caveWallCells.Distinct(), LevelObjectPrefab.SpawnPosType.CaveWall));
292 
293  for (int i = 0; i < objectAmount; i++)
294  {
295  //get a random prefab and find a place to spawn it
296  LevelObjectPrefab prefab = GetRandomPrefab(cave.CaveGenerationParams, availablePrefabs, requireCaveSpecificOverride: false);
297  if (prefab == null) { continue; }
298  if (!suitableSpawnPositions.ContainsKey(prefab))
299  {
300  suitableSpawnPositions.Add(prefab,
301  availableSpawnPositions.Where(sp =>
302  sp.Length >= prefab.MinSurfaceWidth &&
303  (sp.Alignment == Alignment.Any || prefab.Alignment.HasFlag(sp.Alignment))).ToList());
304  spawnPositionWeights.Add(prefab,
305  suitableSpawnPositions[prefab].Select(sp => sp.GetSpawnProbability(prefab)).ToList());
306  }
307  SpawnPosition spawnPosition = ToolBox.SelectWeightedRandom(suitableSpawnPositions[prefab], spawnPositionWeights[prefab], Rand.RandSync.ServerAndClient);
308  if (spawnPosition == null && prefab.SpawnPos != LevelObjectPrefab.SpawnPosType.None) { continue; }
309  PlaceObject(prefab, spawnPosition, level);
310  if (objects.Count(o => o.Prefab == prefab) >= prefab.MaxCount)
311  {
312  availablePrefabs.Remove(prefab);
313  }
314  }
315  }
316 
317  private void PlaceObject(LevelObjectPrefab prefab, SpawnPosition spawnPosition, Level level, Level.Cave parentCave = null)
318  {
319  float rotation = 0.0f;
320  if (prefab.AlignWithSurface && spawnPosition != null && spawnPosition.Normal.LengthSquared() > 0.001f)
321  {
322  rotation = MathUtils.VectorToAngle(new Vector2(spawnPosition.Normal.Y, spawnPosition.Normal.X));
323  }
324  rotation += Rand.Range(prefab.RandomRotationRad.X, prefab.RandomRotationRad.Y, Rand.RandSync.ServerAndClient);
325 
326  Vector2 position = Vector2.Zero;
327  Vector2 edgeDir = Vector2.UnitX;
328  if (spawnPosition == null)
329  {
330  position = new Vector2(
331  Rand.Range(0.0f, level.Size.X, Rand.RandSync.ServerAndClient),
332  Rand.Range(0.0f, level.Size.Y, Rand.RandSync.ServerAndClient));
333  }
334  else
335  {
336  edgeDir = (spawnPosition.GraphEdge.Point1 - spawnPosition.GraphEdge.Point2) / spawnPosition.Length;
337  position = spawnPosition.GraphEdge.Point2 + edgeDir * Rand.Range(prefab.MinSurfaceWidth / 2.0f, spawnPosition.Length - prefab.MinSurfaceWidth / 2.0f, Rand.RandSync.ServerAndClient);
338  }
339 
340  if (!MathUtils.NearlyEqual(prefab.RandomOffset.X, 0.0f) || !MathUtils.NearlyEqual(prefab.RandomOffset.Y, 0.0f))
341  {
342  Vector2 offsetDir = spawnPosition.Normal.LengthSquared() > 0.001f ? spawnPosition.Normal : Rand.Vector(1.0f, Rand.RandSync.ServerAndClient);
343  position += offsetDir * Rand.Range(prefab.RandomOffset.X, prefab.RandomOffset.Y, Rand.RandSync.ServerAndClient);
344  }
345 
346  var newObject = new LevelObject(prefab,
347  new Vector3(position, Rand.Range(prefab.DepthRange.X, prefab.DepthRange.Y, Rand.RandSync.ServerAndClient)), Rand.Range(prefab.MinSize, prefab.MaxSize, Rand.RandSync.ServerAndClient), rotation);
348  AddObject(newObject, level);
349  newObject.ParentCave = parentCave;
350 
351  foreach (LevelObjectPrefab.ChildObject child in prefab.ChildObjects)
352  {
353  int childCount = Rand.Range(child.MinCount, child.MaxCount + 1, Rand.RandSync.ServerAndClient);
354  for (int j = 0; j < childCount; j++)
355  {
356  var matchingPrefabs = LevelObjectPrefab.Prefabs.Where(p => child.AllowedNames.Contains(p.Name));
357  int prefabCount = matchingPrefabs.Count();
358  var childPrefab = prefabCount == 0 ? null : matchingPrefabs.ElementAt(Rand.Range(0, prefabCount, Rand.RandSync.ServerAndClient));
359  if (childPrefab == null) { continue; }
360 
361  Vector2 childPos = position + edgeDir * Rand.Range(-0.5f, 0.5f, Rand.RandSync.ServerAndClient) * prefab.MinSurfaceWidth;
362 
363  var childObject = new LevelObject(childPrefab,
364  new Vector3(childPos, Rand.Range(childPrefab.DepthRange.X, childPrefab.DepthRange.Y, Rand.RandSync.ServerAndClient)),
365  Rand.Range(childPrefab.MinSize, childPrefab.MaxSize, Rand.RandSync.ServerAndClient),
366  rotation + Rand.Range(childPrefab.RandomRotationRad.X, childPrefab.RandomRotationRad.Y, Rand.RandSync.ServerAndClient));
367 
368  AddObject(childObject, level);
369  childObject.ParentCave = parentCave;
370  }
371  }
372  }
373 
374  private void AddObject(LevelObject newObject, Level level)
375  {
376  if (newObject.Triggers != null)
377  {
378  foreach (LevelTrigger trigger in newObject.Triggers)
379  {
380  trigger.OnTriggered += (levelTrigger, obj) =>
381  {
382  OnObjectTriggered(newObject, levelTrigger, obj);
383  };
384  }
385  }
386 
387  var spriteCorners = new List<Vector2>
388  {
389  Vector2.Zero, Vector2.Zero, Vector2.Zero, Vector2.Zero
390  };
391 
392  Sprite sprite = newObject.Sprite ?? newObject.Prefab.DeformableSprite?.Sprite;
393 
394  //calculate the positions of the corners of the rotated sprite
395  if (sprite != null)
396  {
397  Vector2 halfSize = sprite.size * newObject.Scale / 2;
398  spriteCorners[0] = -halfSize;
399  spriteCorners[1] = new Vector2(-halfSize.X, halfSize.Y);
400  spriteCorners[2] = halfSize;
401  spriteCorners[3] = new Vector2(halfSize.X, -halfSize.Y);
402 
403  Vector2 pivotOffset = sprite.Origin * newObject.Scale - halfSize;
404  pivotOffset.X = -pivotOffset.X;
405  pivotOffset = new Vector2(
406  (float)(pivotOffset.X * Math.Cos(-newObject.Rotation) - pivotOffset.Y * Math.Sin(-newObject.Rotation)),
407  (float)(pivotOffset.X * Math.Sin(-newObject.Rotation) + pivotOffset.Y * Math.Cos(-newObject.Rotation)));
408 
409  for (int j = 0; j < 4; j++)
410  {
411  spriteCorners[j] = new Vector2(
412  (float)(spriteCorners[j].X * Math.Cos(-newObject.Rotation) - spriteCorners[j].Y * Math.Sin(-newObject.Rotation)),
413  (float)(spriteCorners[j].X * Math.Sin(-newObject.Rotation) + spriteCorners[j].Y * Math.Cos(-newObject.Rotation)));
414 
415  spriteCorners[j] += new Vector2(newObject.Position.X, newObject.Position.Y) + pivotOffset;
416  }
417  }
418 
419  float minX = spriteCorners.Min(c => c.X) - newObject.Position.Z * ParallaxStrength;
420  float maxX = spriteCorners.Max(c => c.X) + newObject.Position.Z * ParallaxStrength;
421 
422  float minY = spriteCorners.Min(c => c.Y) - newObject.Position.Z * ParallaxStrength - level.BottomPos;
423  float maxY = spriteCorners.Max(c => c.Y) + newObject.Position.Z * ParallaxStrength - level.BottomPos;
424 
425  if (newObject.Triggers != null)
426  {
427  foreach (LevelTrigger trigger in newObject.Triggers)
428  {
429  if (trigger.PhysicsBody == null) { continue; }
430  for (int i = 0; i < trigger.PhysicsBody.FarseerBody.FixtureList.Count; i++)
431  {
432  trigger.PhysicsBody.FarseerBody.GetTransform(out FarseerPhysics.Common.Transform transform);
433  trigger.PhysicsBody.FarseerBody.FixtureList[i].Shape.ComputeAABB(out FarseerPhysics.Collision.AABB aabb, ref transform, i);
434 
435  minX = Math.Min(minX, ConvertUnits.ToDisplayUnits(aabb.LowerBound.X));
436  maxX = Math.Max(maxX, ConvertUnits.ToDisplayUnits(aabb.UpperBound.X));
437  minY = Math.Min(minY, ConvertUnits.ToDisplayUnits(aabb.LowerBound.Y) - level.BottomPos);
438  maxY = Math.Max(maxY, ConvertUnits.ToDisplayUnits(aabb.UpperBound.Y) - level.BottomPos);
439  }
440  }
441  }
442 
443 #if CLIENT
444  if (newObject.ParticleEmitters != null)
445  {
446  foreach (ParticleEmitter emitter in newObject.ParticleEmitters)
447  {
448  Rectangle particleBounds = emitter.CalculateParticleBounds(new Vector2(newObject.Position.X, newObject.Position.Y));
449  minX = Math.Min(minX, particleBounds.X);
450  maxX = Math.Max(maxX, particleBounds.Right);
451  minY = Math.Min(minY, particleBounds.Y - level.BottomPos);
452  maxY = Math.Max(maxY, particleBounds.Bottom - level.BottomPos);
453  }
454  }
455 #endif
456  objects.Add(newObject);
457  if (newObject.NeedsUpdate) { updateableObjects.Add(newObject); }
458  newObject.Position.Z += (minX + minY) % 100.0f * 0.00001f;
459 
460  int xStart = (int)Math.Floor(minX / GridSize);
461  int xEnd = (int)Math.Floor(maxX / GridSize);
462  if (xEnd < 0 || xStart >= objectGrid.GetLength(0)) { return; }
463 
464  int yStart = (int)Math.Floor(minY / GridSize);
465  int yEnd = (int)Math.Floor(maxY / GridSize);
466  if (yEnd < 0 || yStart >= objectGrid.GetLength(1)) { return; }
467 
468  xStart = Math.Max(xStart, 0);
469  xEnd = Math.Min(xEnd, objectGrid.GetLength(0) - 1);
470  yStart = Math.Max(yStart, 0);
471  yEnd = Math.Min(yEnd, objectGrid.GetLength(1) - 1);
472 
473  for (int x = xStart; x <= xEnd; x++)
474  {
475  for (int y = yStart; y <= yEnd; y++)
476  {
477  var list = objectGrid[x, y];
478  if (list == null) { objectGrid[x, y] = list = new List<LevelObject>(); }
479 
480  //insertion sort in ascending order (= prefer rendering objects in front)
481  int drawOrderIndex = 0;
482  while (drawOrderIndex < list.Count && list[drawOrderIndex].Position.Z < newObject.Position.Z)
483  {
484  drawOrderIndex++;
485  }
486  list.Insert(drawOrderIndex, newObject);
487  }
488  }
489  }
490 
491  public static Point GetGridIndices(Vector2 worldPosition)
492  {
493  return new Point(
494  (int)Math.Floor(worldPosition.X / GridSize),
495  (int)Math.Floor((worldPosition.Y - Level.Loaded.BottomPos) / GridSize));
496  }
497 
498  public IEnumerable<LevelObject> GetAllObjects()
499  {
500  return objects;
501  }
502 
503  private readonly static HashSet<LevelObject> objectsInRange = new HashSet<LevelObject>();
504  public IEnumerable<LevelObject> GetAllObjects(Vector2 worldPosition, float radius)
505  {
506  var minIndices = GetGridIndices(worldPosition - Vector2.One * radius);
507  if (minIndices.X >= objectGrid.GetLength(0) || minIndices.Y >= objectGrid.GetLength(1)) { return Enumerable.Empty<LevelObject>(); }
508 
509  var maxIndices = GetGridIndices(worldPosition + Vector2.One * radius);
510  if (maxIndices.X < 0 || maxIndices.Y < 0) { return Enumerable.Empty<LevelObject>(); }
511 
512  minIndices.X = Math.Max(0, minIndices.X);
513  minIndices.Y = Math.Max(0, minIndices.Y);
514  maxIndices.X = Math.Min(objectGrid.GetLength(0) - 1, maxIndices.X);
515  maxIndices.Y = Math.Min(objectGrid.GetLength(1) - 1, maxIndices.Y);
516 
517  objectsInRange.Clear();
518  for (int x = minIndices.X; x <= maxIndices.X; x++)
519  {
520  for (int y = minIndices.Y; y <= maxIndices.Y; y++)
521  {
522  if (objectGrid[x, y] == null) { continue; }
523  foreach (LevelObject obj in objectGrid[x, y])
524  {
525  if (obj.Prefab.HideWhenBroken && obj.Health <= 0.0f) { continue; }
526  objectsInRange.Add(obj);
527  }
528  }
529  }
530 
531  return objectsInRange;
532  }
533 
534  private static List<SpawnPosition> GetAvailableSpawnPositions(IEnumerable<VoronoiCell> cells, LevelObjectPrefab.SpawnPosType spawnPosType)
535  {
536  List<LevelObjectPrefab.SpawnPosType> spawnPosTypes = new List<LevelObjectPrefab.SpawnPosType>(4);
537  List<SpawnPosition> availableSpawnPositions = new List<SpawnPosition>();
538  bool requireCaveSpawnPos = spawnPosType == LevelObjectPrefab.SpawnPosType.CaveWall;
539  foreach (var cell in cells)
540  {
541  foreach (var edge in cell.Edges)
542  {
543  if (!edge.IsSolid || edge.OutsideLevel) { continue; }
544  if (requireCaveSpawnPos != edge.NextToCave) { continue; }
545  Vector2 normal = edge.GetNormal(cell);
546 
547  Alignment edgeAlignment = 0;
548  if (normal.Y < -0.5f)
549  edgeAlignment |= Alignment.Bottom;
550  else if (normal.Y > 0.5f)
551  edgeAlignment |= Alignment.Top;
552  else if (normal.X < -0.5f)
553  edgeAlignment |= Alignment.Left;
554  else if(normal.X > 0.5f)
555  edgeAlignment |= Alignment.Right;
556 
557  spawnPosTypes.Clear();
558  spawnPosTypes.Add(spawnPosType);
559  if (spawnPosType.HasFlag(LevelObjectPrefab.SpawnPosType.MainPathWall) && edge.NextToMainPath) { spawnPosTypes.Add(LevelObjectPrefab.SpawnPosType.MainPathWall); }
560  if (spawnPosType.HasFlag(LevelObjectPrefab.SpawnPosType.SidePathWall) && edge.NextToSidePath) { spawnPosTypes.Add(LevelObjectPrefab.SpawnPosType.SidePathWall); }
561  if (spawnPosType.HasFlag(LevelObjectPrefab.SpawnPosType.CaveWall) && edge.NextToCave) { spawnPosTypes.Add(LevelObjectPrefab.SpawnPosType.CaveWall); }
562 
563  availableSpawnPositions.Add(new SpawnPosition(edge, normal, spawnPosTypes, edgeAlignment));
564  }
565  }
566  return availableSpawnPositions;
567  }
568 
569  public void Update(float deltaTime)
570  {
571  GlobalForceDecreaseTimer += deltaTime;
572  if (GlobalForceDecreaseTimer > 1000000.0f)
573  {
575  }
576 
577  if (updateableObjects is not null)
578  {
579  foreach (LevelObject obj in updateableObjects)
580  {
581  if (GameMain.NetworkMember is { IsServer: true })
582  {
583  obj.NetworkUpdateTimer -= deltaTime;
584  if (obj.NeedsNetworkSyncing && obj.NetworkUpdateTimer <= 0.0f)
585  {
586  GameMain.NetworkMember.CreateEntityEvent(this, new EventData(obj));
587  obj.NeedsNetworkSyncing = false;
588  obj.NetworkUpdateTimer = NetConfig.LevelObjectUpdateInterval;
589  }
590  }
591  if (obj.Prefab.HideWhenBroken && obj.Health <= 0.0f) { continue; }
592 
593  if (obj.Triggers != null)
594  {
595  obj.ActivePrefab = obj.Prefab;
596  for (int i = 0; i < obj.Triggers.Count; i++)
597  {
598  obj.Triggers[i].Update(deltaTime);
599  if (obj.Triggers[i].IsTriggered && obj.Prefab.OverrideProperties[i] != null)
600  {
602  }
603  }
604  }
605 
606  if (obj.PhysicsBody != null)
607  {
608  if (obj.Prefab.PhysicsBodyTriggerIndex > -1) { obj.PhysicsBody.Enabled = obj.Triggers[obj.Prefab.PhysicsBodyTriggerIndex].IsTriggered; }
609  /*obj.Position = new Vector3(obj.PhysicsBody.Position, obj.Position.Z);
610  obj.Rotation = -obj.PhysicsBody.Rotation;*/
611  }
612  }
613  }
614 
615  UpdateProjSpecific(deltaTime);
616  }
617 
618  partial void UpdateProjSpecific(float deltaTime);
619 
620  private void OnObjectTriggered(LevelObject triggeredObject, LevelTrigger trigger, Entity triggerer)
621  {
622  if (trigger.TriggerOthersDistance <= 0.0f) { return; }
623  foreach (LevelObject obj in objects)
624  {
625  if (obj == triggeredObject || obj.Triggers == null) { continue; }
626  foreach (LevelTrigger otherTrigger in obj.Triggers)
627  {
628  otherTrigger.OtherTriggered(trigger, triggerer);
629  }
630  }
631  }
632 
633  private static LevelObjectPrefab GetRandomPrefab(Level level, IList<LevelObjectPrefab> availablePrefabs)
634  {
635  if (availablePrefabs.Sum(p => p.GetCommonness(level.LevelData)) <= 0.0f) { return null; }
636  return ToolBox.SelectWeightedRandom(
637  availablePrefabs,
638  availablePrefabs.Select(p => p.GetCommonness(level.LevelData)).ToList(), Rand.RandSync.ServerAndClient);
639  }
640 
641  private static LevelObjectPrefab GetRandomPrefab(CaveGenerationParams caveParams, IList<LevelObjectPrefab> availablePrefabs, bool requireCaveSpecificOverride)
642  {
643  if (availablePrefabs.Sum(p => p.GetCommonness(caveParams, requireCaveSpecificOverride)) <= 0.0f) { return null; }
644  return ToolBox.SelectWeightedRandom(
645  availablePrefabs,
646  availablePrefabs.Select(p => p.GetCommonness(caveParams, requireCaveSpecificOverride)).ToList(), Rand.RandSync.ServerAndClient);
647  }
648 
649  public override void Remove()
650  {
651  objectsInRange.Clear();
652  if (objects != null)
653  {
654  foreach (LevelObject obj in objects)
655  {
656  obj.Remove();
657  }
658  objects.Clear();
659  updateableObjects.Clear();
660  }
661  RemoveProjSpecific();
662 
663  base.Remove();
664  }
665 
666  partial void RemoveProjSpecific();
667 
668  public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
669  {
670  if (extraData is not EventData eventData) { throw new Exception($"Malformed LevelObjectManager event: expected {nameof(LevelObjectManager)}.{nameof(EventData)}"); }
671  LevelObject obj = eventData.LevelObject;
672  msg.WriteRangedInteger(objects.IndexOf(obj), 0, objects.Count);
673  obj.ServerWrite(msg, c);
674  }
675  }
676 }
Submarine Submarine
Definition: Entity.cs:53
const ushort NullEntityID
Definition: Entity.cs:14
static NetworkMember NetworkMember
Definition: GameMain.cs:41
static GameSession GameSession
Definition: GameMain.cs:45
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)
readonly LevelObjectPrefab Prefab
Definition: LevelObject.cs:13
LevelObject(LevelObjectPrefab prefab, Vector3 position, float scale, float rotation=0.0f)
Definition: LevelObject.cs:88
void ServerWrite(IWriteMessage msg, Client c)
Definition: LevelObject.cs:240
PhysicsBody PhysicsBody
Definition: LevelObject.cs:29
LevelObjectPrefab ActivePrefab
Definition: LevelObject.cs:26
List< LevelTrigger > Triggers
Definition: LevelObject.cs:35
void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData=null)
static Point GetGridIndices(Vector2 worldPosition)
void PlaceNestObjects(Level level, Level.Cave cave, Vector2 nestPosition, float nestRadius, int objectAmount)
void PlaceObjects(Level level, int amount)
IEnumerable< LevelObject > GetAllObjects()
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....
static readonly PrefabCollection< LevelObjectPrefab > Prefabs
List< ChildObject > ChildObjects
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.
List< VoronoiCell > Cells
Definition: LevelWall.cs:16
Rectangle? WorldRect
Definition: MapEntity.cs:105
bool IsHidden
Is the entity hidden due to HiddenInGame being enabled or the layer the entity is in being hidden?
Definition: MapEntity.cs:211
Interface for entities that the server can send events to the clients
void WriteRangedInteger(int val, int min, int max)