6 using Microsoft.Xna.Framework;
8 using System.Collections.Generic;
17 const int GridSize = 2000;
19 private List<LevelObject> objects;
20 private List<LevelObject> updateableObjects;
21 private List<LevelObject>[,] objectGrid;
23 const float ParallaxStrength = 0.0001f;
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;
53 private readonly
float noiseVal;
56 public SpawnPosition(
GraphEdge graphEdge, Vector2 normal, LevelObjectPrefab.SpawnPosType spawnPosType, Alignment alignment)
57 : this(graphEdge, normal, spawnPosType.ToEnumerable(), alignment)
60 public SpawnPosition(
GraphEdge graphEdge, Vector2 normal, IEnumerable<LevelObjectPrefab.SpawnPosType> spawnPosTypes, Alignment alignment)
63 Normal = normal.NearlyEquals(Vector2.Zero) ? Vector2.UnitY : Vector2.Normalize(normal);
64 SpawnPosTypes.AddRange(spawnPosTypes);
66 if (spawnPosTypes.Contains(LevelObjectPrefab.SpawnPosType.MainPath) ||
67 spawnPosTypes.Contains(LevelObjectPrefab.SpawnPosType.LevelStart) ||
68 spawnPosTypes.Contains(LevelObjectPrefab.SpawnPosType.LevelEnd))
72 Alignment = Alignment.Any;
76 Alignment = alignment;
77 Length = Vector2.Distance(graphEdge.
Point1, graphEdge.Point2);
85 public float GetSpawnProbability(LevelObjectPrefab prefab)
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);
95 objectGrid =
new List<LevelObject>[
96 level.
Size.X / GridSize,
99 List<SpawnPosition> availableSpawnPositions =
new List<SpawnPosition>();
109 if (level.Ruins.Any(r => r.Submarine == structure.
Submarine))
126 if (topHull && bottomHull) {
continue; }
128 availableSpawnPositions.Add(
new SpawnPosition(
130 bottomHull ? Vector2.UnitY : -Vector2.UnitY,
132 bottomHull ? Alignment.Bottom : Alignment.Top));
138 if (rightHull && leftHull) {
continue; }
140 availableSpawnPositions.Add(
new SpawnPosition(
142 leftHull ? Vector2.UnitX : -Vector2.UnitX,
144 leftHull ? Alignment.Left : Alignment.Right));
148 foreach (var posOfInterest
in level.PositionsOfInterest)
152 availableSpawnPositions.Add(
new SpawnPosition(
153 new GraphEdge(posOfInterest.Position.ToVector2(), posOfInterest.Position.ToVector2() + Vector2.UnitX),
159 availableSpawnPositions.Add(
new SpawnPosition(
160 new GraphEdge(level.StartPosition - Vector2.UnitX, level.StartPosition + Vector2.UnitX),
162 availableSpawnPositions.Add(
new SpawnPosition(
163 new GraphEdge(level.EndPosition - Vector2.UnitX, level.EndPosition + Vector2.UnitX),
167 objects =
new List<LevelObject>();
168 updateableObjects =
new List<LevelObject>();
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++)
176 if (prefab ==
null) {
continue; }
177 if (!suitableSpawnPositions.ContainsKey(prefab))
179 float minDistance = level.Size.X * 0.2f;
181 suitableSpawnPositions.Add(prefab,
182 availableSpawnPositions.Where(sp =>
183 sp.SpawnPosTypes.Any(type => prefab.
SpawnPos.HasFlag(type)) &&
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());
189 spawnPositionWeights.Add(prefab,
190 suitableSpawnPositions[prefab].
Select(sp => sp.GetSpawnProbability(prefab)).ToList());
193 SpawnPosition spawnPosition = ToolBox.SelectWeightedRandom(suitableSpawnPositions[prefab], spawnPositionWeights[prefab], Rand.RandSync.ServerAndClient);
195 PlaceObject(prefab, spawnPosition, level);
198 if (objects.Count(o => o.Prefab == prefab) >= prefab.
MaxCount)
200 availablePrefabs.Remove(prefab);
208 .OrderBy(p => p.UintIdentifier).ToList();
209 availableSpawnPositions.Clear();
210 suitableSpawnPositions.Clear();
211 spawnPositionWeights.Clear();
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))
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); }
223 for (
int i = 0; i < cave.CaveGenerationParams.LevelObjectAmount; i++)
226 LevelObjectPrefab prefab = GetRandomPrefab(cave.CaveGenerationParams, availablePrefabs, requireCaveSpecificOverride:
true);
227 if (prefab ==
null) {
continue; }
228 if (!suitableSpawnPositions.ContainsKey(prefab))
230 suitableSpawnPositions.Add(prefab,
231 availableSpawnPositions.Where(sp =>
233 (sp.Alignment == Alignment.Any || prefab.
Alignment.HasFlag(sp.Alignment))).ToList());
234 spawnPositionWeights.Add(prefab,
235 suitableSpawnPositions[prefab].
Select(sp => sp.GetSpawnProbability(prefab)).ToList());
237 SpawnPosition spawnPosition = ToolBox.SelectWeightedRandom(suitableSpawnPositions[prefab], spawnPositionWeights[prefab], Rand.RandSync.ServerAndClient);
239 PlaceObject(prefab, spawnPosition, level, cave);
243 for (
int j = 0; j < objects.Count; j++)
245 if (objects[j].
Prefab == prefab && objects[j].ParentCave == cave)
256 availablePrefabs.Remove(prefab);
265 Rand.SetSyncedSeed(ToolBox.StringToInt(level.
Seed));
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>>();
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))
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); }
284 for (
int i = 0; i < objectAmount; i++)
287 LevelObjectPrefab prefab = GetRandomPrefab(cave.CaveGenerationParams, availablePrefabs, requireCaveSpecificOverride:
false);
288 if (prefab ==
null) {
continue; }
289 if (!suitableSpawnPositions.ContainsKey(prefab))
291 suitableSpawnPositions.Add(prefab,
292 availableSpawnPositions.Where(sp =>
294 (sp.Alignment == Alignment.Any || prefab.
Alignment.HasFlag(sp.Alignment))).ToList());
295 spawnPositionWeights.Add(prefab,
296 suitableSpawnPositions[prefab].
Select(sp => sp.GetSpawnProbability(prefab)).ToList());
298 SpawnPosition spawnPosition = ToolBox.SelectWeightedRandom(suitableSpawnPositions[prefab], spawnPositionWeights[prefab], Rand.RandSync.ServerAndClient);
300 PlaceObject(prefab, spawnPosition, level);
301 if (objects.Count(o => o.Prefab == prefab) >= prefab.
MaxCount)
303 availablePrefabs.Remove(prefab);
310 float rotation = 0.0f;
311 if (prefab.
AlignWithSurface && spawnPosition !=
null && spawnPosition.Normal.LengthSquared() > 0.001f)
313 rotation = MathUtils.VectorToAngle(
new Vector2(spawnPosition.Normal.Y, spawnPosition.Normal.X));
317 Vector2 position = Vector2.Zero;
318 Vector2 edgeDir = Vector2.UnitX;
319 if (spawnPosition ==
null)
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));
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);
333 Vector2 offsetDir = spawnPosition.Normal.LengthSquared() > 0.001f ? spawnPosition.Normal : Rand.Vector(1.0f, Rand.RandSync.ServerAndClient);
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;
342 foreach (LevelObjectPrefab.ChildObject child in prefab.
ChildObjects)
344 int childCount = Rand.Range(child.MinCount, child.MaxCount + 1, Rand.RandSync.ServerAndClient);
345 for (
int j = 0; j < childCount; j++)
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; }
352 Vector2 childPos = position + edgeDir * Rand.Range(-0.5f, 0.5f, Rand.RandSync.ServerAndClient) * prefab.
MinSurfaceWidth;
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));
359 AddObject(childObject, level);
360 childObject.ParentCave = parentCave;
365 private void AddObject(LevelObject newObject, Level level)
367 if (newObject.Triggers !=
null)
369 foreach (LevelTrigger trigger
in newObject.Triggers)
371 trigger.OnTriggered += (levelTrigger, obj) =>
373 OnObjectTriggered(newObject, levelTrigger, obj);
378 var spriteCorners =
new List<Vector2>
380 Vector2.Zero, Vector2.Zero, Vector2.Zero, Vector2.Zero
383 Sprite sprite = newObject.Sprite ?? newObject.Prefab.DeformableSprite?.Sprite;
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);
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)));
400 for (
int j = 0; j < 4; j++)
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)));
406 spriteCorners[j] +=
new Vector2(newObject.Position.X, newObject.Position.Y) + pivotOffset;
410 float minX = spriteCorners.Min(c => c.X) - newObject.Position.Z * ParallaxStrength;
411 float maxX = spriteCorners.Max(c => c.X) + newObject.Position.Z * ParallaxStrength;
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;
416 if (newObject.Triggers !=
null)
418 foreach (LevelTrigger trigger
in newObject.Triggers)
420 if (trigger.PhysicsBody ==
null) {
continue; }
421 for (
int i = 0; i < trigger.PhysicsBody.FarseerBody.FixtureList.Count; i++)
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);
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);
435 if (newObject.ParticleEmitters !=
null)
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);
447 objects.Add(newObject);
448 if (newObject.NeedsUpdate) { updateableObjects.Add(newObject); }
449 newObject.Position.Z += (minX + minY) % 100.0f * 0.00001f;
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; }
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; }
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);
464 for (
int x = xStart; x <= xEnd; x++)
466 for (
int y = yStart; y <= yEnd; y++)
468 var list = objectGrid[x, y];
469 if (list ==
null) { objectGrid[x, y] = list =
new List<LevelObject>(); }
472 int drawOrderIndex = 0;
473 while (drawOrderIndex < list.Count && list[drawOrderIndex].Position.Z < newObject.Position.Z)
477 list.Insert(drawOrderIndex, newObject);
485 (
int)Math.Floor(worldPosition.X / GridSize),
494 private readonly
static HashSet<LevelObject> objectsInRange =
new HashSet<LevelObject>();
495 public IEnumerable<LevelObject>
GetAllObjects(Vector2 worldPosition,
float radius)
497 var minIndices =
GetGridIndices(worldPosition - Vector2.One * radius);
498 if (minIndices.X >= objectGrid.GetLength(0) || minIndices.Y >= objectGrid.GetLength(1)) {
return Enumerable.Empty<
LevelObject>(); }
500 var maxIndices =
GetGridIndices(worldPosition + Vector2.One * radius);
501 if (maxIndices.X < 0 || maxIndices.Y < 0) {
return Enumerable.Empty<
LevelObject>(); }
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);
508 objectsInRange.Clear();
509 for (
int x = minIndices.X; x <= maxIndices.X; x++)
511 for (
int y = minIndices.Y; y <= maxIndices.Y; y++)
513 if (objectGrid[x, y] ==
null) {
continue; }
517 objectsInRange.Add(obj);
522 return objectsInRange;
528 List<SpawnPosition> availableSpawnPositions =
new List<SpawnPosition>();
530 foreach (var cell
in cells)
532 foreach (var edge
in cell.Edges)
534 if (!edge.IsSolid || edge.OutsideLevel) {
continue; }
535 if (requireCaveSpawnPos != edge.NextToCave) {
continue; }
536 Vector2 normal = edge.GetNormal(cell);
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;
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); }
554 availableSpawnPositions.Add(
new SpawnPosition(edge, normal, spawnPosTypes, edgeAlignment));
557 return availableSpawnPositions;
568 if (updateableObjects is not
null)
587 for (
int i = 0; i < obj.
Triggers.Count; i++)
606 UpdateProjSpecific(deltaTime);
609 partial
void UpdateProjSpecific(
float deltaTime);
614 foreach (LevelObject obj
in objects)
616 if (obj == triggeredObject || obj.Triggers ==
null) {
continue; }
617 foreach (LevelTrigger otherTrigger
in obj.Triggers)
619 otherTrigger.OtherTriggered(trigger, triggerer);
624 private static LevelObjectPrefab GetRandomPrefab(Level level, IList<LevelObjectPrefab> availablePrefabs)
626 if (availablePrefabs.Sum(p => p.GetCommonness(level.LevelData)) <= 0.0f) {
return null; }
627 return ToolBox.SelectWeightedRandom(
629 availablePrefabs.Select(p => p.GetCommonness(level.LevelData)).ToList(), Rand.RandSync.ServerAndClient);
632 private static LevelObjectPrefab GetRandomPrefab(CaveGenerationParams caveParams, IList<LevelObjectPrefab> availablePrefabs,
bool requireCaveSpecificOverride)
634 if (availablePrefabs.Sum(p => p.GetCommonness(caveParams, requireCaveSpecificOverride)) <= 0.0f) {
return null; }
635 return ToolBox.SelectWeightedRandom(
637 availablePrefabs.Select(p => p.GetCommonness(caveParams, requireCaveSpecificOverride)).ToList(), Rand.RandSync.ServerAndClient);
642 objectsInRange.Clear();
650 updateableObjects.Clear();
652 RemoveProjSpecific();
657 partial
void RemoveProjSpecific();
661 if (extraData is not EventData eventData) {
throw new Exception($
"Malformed LevelObjectManager event: expected {nameof(LevelObjectManager)}.{nameof(EventData)}"); }
const ushort NullEntityID
static NetworkMember NetworkMember
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)
List< VoronoiCell > GetAllCells()
readonly LevelObjectPrefab Prefab
LevelObject(LevelObjectPrefab prefab, Vector3 position, float scale, float rotation=0.0f)
void ServerWrite(IWriteMessage msg, Client c)
LevelObjectPrefab ActivePrefab
List< LevelTrigger > Triggers
void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData=null)
float GlobalForceDecreaseTimer
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()
void Update(float deltaTime)
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
int PhysicsBodyTriggerIndex
Vector2 RandomRotationRad
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.
float TriggerOthersDistance
List< VoronoiCell > Cells
bool IsHidden
Is the entity hidden due to HiddenInGame being enabled or the layer the entity is in being hidden?
Rectangle CalculateParticleBounds(Vector2 startPosition)
static List< Structure > WallList
Interface for entities that the server can send events to the clients
void WriteRangedInteger(int val, int min, int max)