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;
187 allowAtEnd = allowAtStart = allowAtEnd && allowAtStart;
190 suitableSpawnPositions.Add(prefab,
191 availableSpawnPositions.Where(sp =>
192 sp.SpawnPosTypes.Any(type => prefab.
SpawnPos.HasFlag(type)) &&
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());
198 spawnPositionWeights.Add(prefab,
199 suitableSpawnPositions[prefab].
Select(sp => sp.GetSpawnProbability(prefab)).ToList());
202 SpawnPosition spawnPosition = ToolBox.SelectWeightedRandom(suitableSpawnPositions[prefab], spawnPositionWeights[prefab], Rand.RandSync.ServerAndClient);
204 PlaceObject(prefab, spawnPosition, level);
207 if (objects.Count(o => o.Prefab == prefab) >= prefab.
MaxCount)
209 availablePrefabs.Remove(prefab);
217 .OrderBy(p => p.UintIdentifier).ToList();
218 availableSpawnPositions.Clear();
219 suitableSpawnPositions.Clear();
220 spawnPositionWeights.Clear();
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))
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); }
232 for (
int i = 0; i < cave.CaveGenerationParams.LevelObjectAmount; i++)
235 LevelObjectPrefab prefab = GetRandomPrefab(cave.CaveGenerationParams, availablePrefabs, requireCaveSpecificOverride:
true);
236 if (prefab ==
null) {
continue; }
237 if (!suitableSpawnPositions.ContainsKey(prefab))
239 suitableSpawnPositions.Add(prefab,
240 availableSpawnPositions.Where(sp =>
242 (sp.Alignment == Alignment.Any || prefab.
Alignment.HasFlag(sp.Alignment))).ToList());
243 spawnPositionWeights.Add(prefab,
244 suitableSpawnPositions[prefab].
Select(sp => sp.GetSpawnProbability(prefab)).ToList());
246 SpawnPosition spawnPosition = ToolBox.SelectWeightedRandom(suitableSpawnPositions[prefab], spawnPositionWeights[prefab], Rand.RandSync.ServerAndClient);
248 PlaceObject(prefab, spawnPosition, level, cave);
252 for (
int j = 0; j < objects.Count; j++)
254 if (objects[j].
Prefab == prefab && objects[j].ParentCave == cave)
265 availablePrefabs.Remove(prefab);
274 Rand.SetSyncedSeed(ToolBox.StringToInt(level.
Seed));
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>>();
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))
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); }
293 for (
int i = 0; i < objectAmount; i++)
296 LevelObjectPrefab prefab = GetRandomPrefab(cave.CaveGenerationParams, availablePrefabs, requireCaveSpecificOverride:
false);
297 if (prefab ==
null) {
continue; }
298 if (!suitableSpawnPositions.ContainsKey(prefab))
300 suitableSpawnPositions.Add(prefab,
301 availableSpawnPositions.Where(sp =>
303 (sp.Alignment == Alignment.Any || prefab.
Alignment.HasFlag(sp.Alignment))).ToList());
304 spawnPositionWeights.Add(prefab,
305 suitableSpawnPositions[prefab].
Select(sp => sp.GetSpawnProbability(prefab)).ToList());
307 SpawnPosition spawnPosition = ToolBox.SelectWeightedRandom(suitableSpawnPositions[prefab], spawnPositionWeights[prefab], Rand.RandSync.ServerAndClient);
309 PlaceObject(prefab, spawnPosition, level);
310 if (objects.Count(o => o.Prefab == prefab) >= prefab.
MaxCount)
312 availablePrefabs.Remove(prefab);
319 float rotation = 0.0f;
320 if (prefab.
AlignWithSurface && spawnPosition !=
null && spawnPosition.Normal.LengthSquared() > 0.001f)
322 rotation = MathUtils.VectorToAngle(
new Vector2(spawnPosition.Normal.Y, spawnPosition.Normal.X));
326 Vector2 position = Vector2.Zero;
327 Vector2 edgeDir = Vector2.UnitX;
328 if (spawnPosition ==
null)
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));
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);
342 Vector2 offsetDir = spawnPosition.Normal.LengthSquared() > 0.001f ? spawnPosition.Normal : Rand.Vector(1.0f, Rand.RandSync.ServerAndClient);
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;
351 foreach (LevelObjectPrefab.ChildObject child in prefab.
ChildObjects)
353 int childCount = Rand.Range(child.MinCount, child.MaxCount + 1, Rand.RandSync.ServerAndClient);
354 for (
int j = 0; j < childCount; j++)
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; }
361 Vector2 childPos = position + edgeDir * Rand.Range(-0.5f, 0.5f, Rand.RandSync.ServerAndClient) * prefab.
MinSurfaceWidth;
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));
368 AddObject(childObject, level);
369 childObject.ParentCave = parentCave;
374 private void AddObject(LevelObject newObject, Level level)
376 if (newObject.Triggers !=
null)
378 foreach (LevelTrigger trigger
in newObject.Triggers)
380 trigger.OnTriggered += (levelTrigger, obj) =>
382 OnObjectTriggered(newObject, levelTrigger, obj);
387 var spriteCorners =
new List<Vector2>
389 Vector2.Zero, Vector2.Zero, Vector2.Zero, Vector2.Zero
392 Sprite sprite = newObject.Sprite ?? newObject.Prefab.DeformableSprite?.Sprite;
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);
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)));
409 for (
int j = 0; j < 4; j++)
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)));
415 spriteCorners[j] +=
new Vector2(newObject.Position.X, newObject.Position.Y) + pivotOffset;
419 float minX = spriteCorners.Min(c => c.X) - newObject.Position.Z * ParallaxStrength;
420 float maxX = spriteCorners.Max(c => c.X) + newObject.Position.Z * ParallaxStrength;
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;
425 if (newObject.Triggers !=
null)
427 foreach (LevelTrigger trigger
in newObject.Triggers)
429 if (trigger.PhysicsBody ==
null) {
continue; }
430 for (
int i = 0; i < trigger.PhysicsBody.FarseerBody.FixtureList.Count; i++)
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);
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);
444 if (newObject.ParticleEmitters !=
null)
446 foreach (ParticleEmitter emitter
in newObject.ParticleEmitters)
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);
456 objects.Add(newObject);
457 if (newObject.NeedsUpdate) { updateableObjects.Add(newObject); }
458 newObject.Position.Z += (minX + minY) % 100.0f * 0.00001f;
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; }
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; }
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);
473 for (
int x = xStart; x <= xEnd; x++)
475 for (
int y = yStart; y <= yEnd; y++)
477 var list = objectGrid[x, y];
478 if (list ==
null) { objectGrid[x, y] = list =
new List<LevelObject>(); }
481 int drawOrderIndex = 0;
482 while (drawOrderIndex < list.Count && list[drawOrderIndex].Position.Z < newObject.Position.Z)
486 list.Insert(drawOrderIndex, newObject);
494 (
int)Math.Floor(worldPosition.X / GridSize),
503 private readonly
static HashSet<LevelObject> objectsInRange =
new HashSet<LevelObject>();
504 public IEnumerable<LevelObject>
GetAllObjects(Vector2 worldPosition,
float radius)
506 var minIndices =
GetGridIndices(worldPosition - Vector2.One * radius);
507 if (minIndices.X >= objectGrid.GetLength(0) || minIndices.Y >= objectGrid.GetLength(1)) {
return Enumerable.Empty<
LevelObject>(); }
509 var maxIndices =
GetGridIndices(worldPosition + Vector2.One * radius);
510 if (maxIndices.X < 0 || maxIndices.Y < 0) {
return Enumerable.Empty<
LevelObject>(); }
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);
517 objectsInRange.Clear();
518 for (
int x = minIndices.X; x <= maxIndices.X; x++)
520 for (
int y = minIndices.Y; y <= maxIndices.Y; y++)
522 if (objectGrid[x, y] ==
null) {
continue; }
526 objectsInRange.Add(obj);
531 return objectsInRange;
537 List<SpawnPosition> availableSpawnPositions =
new List<SpawnPosition>();
539 foreach (var cell
in cells)
541 foreach (var edge
in cell.Edges)
543 if (!edge.IsSolid || edge.OutsideLevel) {
continue; }
544 if (requireCaveSpawnPos != edge.NextToCave) {
continue; }
545 Vector2 normal = edge.GetNormal(cell);
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;
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); }
563 availableSpawnPositions.Add(
new SpawnPosition(edge, normal, spawnPosTypes, edgeAlignment));
566 return availableSpawnPositions;
577 if (updateableObjects is not
null)
596 for (
int i = 0; i < obj.
Triggers.Count; i++)
615 UpdateProjSpecific(deltaTime);
618 partial
void UpdateProjSpecific(
float deltaTime);
623 foreach (LevelObject obj
in objects)
625 if (obj == triggeredObject || obj.Triggers ==
null) {
continue; }
626 foreach (LevelTrigger otherTrigger
in obj.Triggers)
628 otherTrigger.OtherTriggered(trigger, triggerer);
633 private static LevelObjectPrefab GetRandomPrefab(Level level, IList<LevelObjectPrefab> availablePrefabs)
635 if (availablePrefabs.Sum(p => p.GetCommonness(level.LevelData)) <= 0.0f) {
return null; }
636 return ToolBox.SelectWeightedRandom(
638 availablePrefabs.Select(p => p.GetCommonness(level.LevelData)).ToList(), Rand.RandSync.ServerAndClient);
641 private static LevelObjectPrefab GetRandomPrefab(CaveGenerationParams caveParams, IList<LevelObjectPrefab> availablePrefabs,
bool requireCaveSpecificOverride)
643 if (availablePrefabs.Sum(p => p.GetCommonness(caveParams, requireCaveSpecificOverride)) <= 0.0f) {
return null; }
644 return ToolBox.SelectWeightedRandom(
646 availablePrefabs.Select(p => p.GetCommonness(caveParams, requireCaveSpecificOverride)).ToList(), Rand.RandSync.ServerAndClient);
651 objectsInRange.Clear();
659 updateableObjects.Clear();
661 RemoveProjSpecific();
666 partial
void RemoveProjSpecific();
670 if (extraData is not EventData eventData) {
throw new Exception($
"Malformed LevelObjectManager event: expected {nameof(LevelObjectManager)}.{nameof(EventData)}"); }
const ushort NullEntityID
static NetworkMember NetworkMember
static GameSession GameSession
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?
static List< Structure > WallList
Interface for entities that the server can send events to the clients
void WriteRangedInteger(int val, int min, int max)