1313 lines
58 KiB
C#
1313 lines
58 KiB
C#
using FarseerPhysics;
|
|
using FarseerPhysics.Factories;
|
|
using Microsoft.Xna.Framework;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using Voronoi2;
|
|
using Barotrauma.Extensions;
|
|
using Barotrauma.Items.Components;
|
|
|
|
namespace Barotrauma.RuinGeneration
|
|
{
|
|
abstract class RuinShape
|
|
{
|
|
protected Rectangle rect;
|
|
|
|
public Rectangle Rect
|
|
{
|
|
get { return rect; }
|
|
}
|
|
|
|
public int DistanceFromEntrance
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
public Vector2 Center
|
|
{
|
|
get { return rect.Center.ToVector2(); }
|
|
}
|
|
|
|
public RuinRoom RoomType;
|
|
|
|
public List<Line> Walls;
|
|
|
|
public virtual void CreateWalls() { }
|
|
|
|
public Alignment GetLineAlignment(Line line)
|
|
{
|
|
if (line.IsHorizontal)
|
|
{
|
|
if (line.A.Y > rect.Center.Y && line.B.Y > rect.Center.Y)
|
|
{
|
|
return Alignment.Bottom;
|
|
}
|
|
else if (line.A.Y < rect.Center.Y && line.B.Y < rect.Center.Y)
|
|
{
|
|
return Alignment.Top;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (line.A.X < rect.Center.X && line.B.X < rect.Center.X)
|
|
{
|
|
return Alignment.Left;
|
|
}
|
|
else if (line.A.X > rect.Center.X && line.B.X > rect.Center.X)
|
|
{
|
|
return Alignment.Right;
|
|
}
|
|
}
|
|
|
|
return Alignment.Center;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Goes through all the walls of the ruin shape and clips off parts that are inside the rectangle
|
|
/// </summary>
|
|
public void SplitWalls(Rectangle rectangle)
|
|
{
|
|
List<Line> newLines = new List<Line>();
|
|
|
|
foreach (Line line in Walls)
|
|
{
|
|
if (!line.IsHorizontal) //vertical line
|
|
{
|
|
//line doesn't intersect the rectangle
|
|
if (rectangle.X > line.A.X || rectangle.Right < line.A.X ||
|
|
rectangle.Y > line.B.Y || rectangle.Bottom < line.A.Y)
|
|
{
|
|
newLines.Add(line);
|
|
}
|
|
//line completely inside the rectangle, no need to create a wall at all
|
|
else if (line.A.Y >= rectangle.Y && line.B.Y <= rectangle.Bottom)
|
|
{
|
|
continue;
|
|
}
|
|
//point A is within the rectangle -> cut a portion from the top of the line
|
|
else if (line.A.Y >= rectangle.Y && line.A.Y <= rectangle.Bottom)
|
|
{
|
|
newLines.Add(new Line(new Vector2(line.A.X, rectangle.Bottom), line.B));
|
|
}
|
|
//point B is within the rectangle -> cut a portion from the bottom of the line
|
|
else if (line.B.Y >= rectangle.Y && line.B.Y <= rectangle.Bottom)
|
|
{
|
|
newLines.Add(new Line(line.A, new Vector2(line.A.X, rectangle.Y)));
|
|
}
|
|
//rect is in between the lines -> split the line into two
|
|
else
|
|
{
|
|
newLines.Add(new Line(line.A, new Vector2(line.A.X, rectangle.Y)));
|
|
newLines.Add(new Line(new Vector2(line.A.X, rectangle.Bottom), line.B));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//line doesn't intersect the rectangle
|
|
if (rectangle.X > line.B.X || rectangle.Right < line.A.X ||
|
|
rectangle.Y > line.A.Y || rectangle.Bottom < line.A.Y)
|
|
{
|
|
|
|
newLines.Add(line);
|
|
}
|
|
else if (line.A.X >= rectangle.X && line.B.X <= rectangle.Right)
|
|
{
|
|
continue;
|
|
}
|
|
//point A is within the rectangle -> cut a portion from the left side of the line
|
|
else if (line.A.X >= rectangle.X && line.A.X <= rectangle.Right)
|
|
{
|
|
newLines.Add(new Line(new Vector2(rectangle.Right, line.A.Y), line.B));
|
|
}
|
|
//point B is within the rectangle -> cut a portion from the right side of the line
|
|
else if (line.B.X >= rectangle.X && line.B.X <= rectangle.Right)
|
|
{
|
|
newLines.Add(new Line(line.A, new Vector2(rectangle.X, line.A.Y)));
|
|
}
|
|
//rect is in between the lines -> split the line into two
|
|
else
|
|
{
|
|
newLines.Add(new Line(line.A, new Vector2(rectangle.X, line.A.Y)));
|
|
newLines.Add(new Line(new Vector2(rectangle.Right, line.A.Y), line.B));
|
|
}
|
|
}
|
|
}
|
|
|
|
Walls = newLines;
|
|
}
|
|
|
|
public void MirrorX(Vector2 mirrorOrigin)
|
|
{
|
|
rect.X = (int)(mirrorOrigin.X + (mirrorOrigin.X - rect.Right));
|
|
for (int i = 0; i < Walls.Count; i++)
|
|
{
|
|
Walls[i].A = new Vector2(mirrorOrigin.X + (mirrorOrigin.X - Walls[i].A.X), Walls[i].A.Y);
|
|
Walls[i].B = new Vector2(mirrorOrigin.X + (mirrorOrigin.X - Walls[i].B.X), Walls[i].B.Y);
|
|
|
|
if (Walls[i].B.X < Walls[i].A.X)
|
|
{
|
|
var temp = Walls[i].A.X;
|
|
Walls[i].A.X = Walls[i].B.X;
|
|
Walls[i].B.X = temp;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class Line
|
|
{
|
|
public Vector2 A, B;
|
|
|
|
public float Radius;
|
|
|
|
public bool IsHorizontal
|
|
{
|
|
get { return Math.Abs(A.Y - B.Y) < Math.Abs(A.X - B.X); }
|
|
}
|
|
|
|
public Line(Vector2 a, Vector2 b)
|
|
{
|
|
Debug.Assert(a.X <= b.X);
|
|
Debug.Assert(a.Y <= b.Y);
|
|
|
|
A = a;
|
|
B = b;
|
|
}
|
|
}
|
|
|
|
partial class Ruin
|
|
{
|
|
private List<BTRoom> rooms;
|
|
private List<Corridor> corridors;
|
|
|
|
private List<Line> walls;
|
|
|
|
private List<RuinShape> allShapes;
|
|
|
|
private RuinGenerationParams generationParams;
|
|
|
|
private BTRoom entranceRoom;
|
|
|
|
private List<RuinEntity> ruinEntities = new List<RuinEntity>();
|
|
private List<Door> doors = new List<Door>();
|
|
|
|
public IEnumerable<RuinEntity> RuinEntities
|
|
{
|
|
get { return ruinEntities; }
|
|
}
|
|
|
|
public List<RuinShape> RuinShapes
|
|
{
|
|
get { return allShapes; }
|
|
}
|
|
|
|
public List<Line> Walls
|
|
{
|
|
get { return walls; }
|
|
}
|
|
|
|
public Rectangle Area
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
public Ruin(VoronoiCell closestPathCell, List<VoronoiCell> caveCells, RuinGenerationParams generationParams, Rectangle area, bool mirror = false)
|
|
{
|
|
this.generationParams = generationParams;
|
|
Area = area;
|
|
corridors = new List<Corridor>();
|
|
rooms = new List<BTRoom>();
|
|
walls = new List<Line>();
|
|
allShapes = new List<RuinShape>();
|
|
Generate(closestPathCell, caveCells, area, mirror);
|
|
}
|
|
|
|
public void Generate(VoronoiCell closestPathCell, List<VoronoiCell> caveCells, Rectangle area, bool mirror = false)
|
|
{
|
|
corridors.Clear();
|
|
rooms.Clear();
|
|
|
|
int iterations = Rand.Range(generationParams.RoomDivisionIterationsMin, generationParams.RoomDivisionIterationsMax, Rand.RandSync.Server);
|
|
float verticalProbability = generationParams.VerticalSplitProbability;
|
|
|
|
BTRoom baseRoom = new BTRoom(area);
|
|
rooms = new List<BTRoom> { baseRoom };
|
|
|
|
for (int i = 0; i < iterations; i++)
|
|
{
|
|
rooms.ForEach(l => l.Split(0.3f, verticalProbability, generationParams.MinSplitWidth));
|
|
rooms = baseRoom.GetLeaves();
|
|
}
|
|
|
|
foreach (BTRoom leaf in rooms)
|
|
{
|
|
leaf.Scale
|
|
(
|
|
new Vector2(
|
|
Rand.Range(generationParams.RoomWidthRange.X, generationParams.RoomWidthRange.Y, Rand.RandSync.Server),
|
|
Rand.Range(generationParams.RoomHeightRange.X, generationParams.RoomHeightRange.Y, Rand.RandSync.Server))
|
|
);
|
|
}
|
|
|
|
baseRoom.GenerateCorridors(generationParams.CorridorWidthRange.X, generationParams.CorridorWidthRange.Y, corridors);
|
|
|
|
walls = new List<Line>();
|
|
rooms.ForEach(leaf => { leaf.CreateWalls(); });
|
|
|
|
//---------------------------
|
|
|
|
float shortestDistance = 0.0f;
|
|
foreach (BTRoom leaf in rooms)
|
|
{
|
|
Vector2 leafPos = leaf.Rect.Center.ToVector2();
|
|
if (mirror)
|
|
{
|
|
leafPos.X = area.Center.X + (area.Center.X - leafPos.X);
|
|
}
|
|
float distance = Vector2.Distance(leafPos, closestPathCell.Center);
|
|
if (entranceRoom == null || distance < shortestDistance)
|
|
{
|
|
entranceRoom = leaf;
|
|
shortestDistance = distance;
|
|
}
|
|
}
|
|
|
|
rooms.Remove(entranceRoom);
|
|
|
|
//---------------------------
|
|
|
|
foreach (BTRoom leaf in rooms)
|
|
{
|
|
foreach (Corridor corridor in corridors)
|
|
{
|
|
leaf.SplitWalls(corridor.Rect);
|
|
}
|
|
|
|
walls.AddRange(leaf.Walls);
|
|
}
|
|
|
|
foreach (Corridor corridor in corridors)
|
|
{
|
|
corridor.CreateWalls();
|
|
|
|
foreach (BTRoom leaf in rooms)
|
|
{
|
|
corridor.SplitWalls(leaf.Rect);
|
|
}
|
|
|
|
foreach (Corridor corridor2 in corridors)
|
|
{
|
|
if (corridor == corridor2) continue;
|
|
corridor.SplitWalls(corridor2.Rect);
|
|
}
|
|
walls.AddRange(corridor.Walls);
|
|
}
|
|
|
|
BTRoom.CalculateDistancesFromEntrance(entranceRoom, rooms, corridors);
|
|
GenerateRuinEntities(caveCells, area, mirror);
|
|
}
|
|
|
|
public class RuinEntity
|
|
{
|
|
public readonly RuinEntityConfig Config;
|
|
public readonly MapEntity Entity;
|
|
public readonly MapEntity Parent;
|
|
public readonly RuinShape Room;
|
|
|
|
public RuinEntity(RuinEntityConfig config, MapEntity entity, RuinShape room, MapEntity parent = null)
|
|
{
|
|
Config = config;
|
|
Entity = entity;
|
|
Room = room;
|
|
Parent = parent;
|
|
}
|
|
}
|
|
|
|
private void GenerateRuinEntities(List<VoronoiCell> caveCells, Rectangle ruinArea, bool mirror)
|
|
{
|
|
var entityGrid = Hull.GenerateEntityGrid(new Rectangle(ruinArea.X, ruinArea.Y + ruinArea.Height, ruinArea.Width, ruinArea.Height));
|
|
doors.Clear();
|
|
|
|
allShapes = new List<RuinShape>(rooms);
|
|
allShapes.AddRange(corridors);
|
|
|
|
if (mirror)
|
|
{
|
|
foreach (RuinShape shape in allShapes)
|
|
{
|
|
shape.MirrorX(ruinArea.Center.ToVector2());
|
|
}
|
|
}
|
|
|
|
int maxRoomDistanceFromEntrance = rooms.Max(s => s.DistanceFromEntrance);
|
|
int maxCorridorDistanceFromEntrance = corridors.Max(s => s.DistanceFromEntrance);
|
|
|
|
//assign the room types for the first and last rooms
|
|
foreach (RuinRoom roomType in generationParams.RoomTypeList)
|
|
{
|
|
RuinShape selectedRoom = null;
|
|
switch (roomType.Placement)
|
|
{
|
|
case RuinRoom.RoomPlacement.First:
|
|
//find the room nearest to the entrance
|
|
//there may be multiple ones, choose one that hasn't been assigned yet
|
|
selectedRoom = roomType.IsCorridor ? FindFirstRoom(corridors, r => r.RoomType == null) : FindFirstRoom(rooms, r => r.RoomType == null);
|
|
|
|
break;
|
|
case RuinRoom.RoomPlacement.Last:
|
|
//find the room furthest to the entrance
|
|
//there may be multiple ones, choose one that hasn't been assigned yet
|
|
selectedRoom = roomType.IsCorridor ? FindLastRoom(corridors, r => r.RoomType == null) : FindLastRoom(rooms, r => r.RoomType == null);
|
|
break;
|
|
}
|
|
if (selectedRoom == null) continue;
|
|
|
|
//step forwards/backwards from the selected room according to the placement offset
|
|
for (int i = 0; i < Math.Abs(roomType.PlacementOffset); i++)
|
|
{
|
|
selectedRoom = FindNearestRoom(
|
|
selectedRoom,
|
|
roomType.IsCorridor ? corridors : (IEnumerable<RuinShape>)rooms,
|
|
roomType.PlacementOffset,
|
|
r => r.RoomType == null);
|
|
}
|
|
|
|
if (selectedRoom != null) selectedRoom.RoomType = roomType;
|
|
}
|
|
|
|
//go through the unassigned rooms
|
|
foreach (RuinShape room in allShapes)
|
|
{
|
|
if (room.RoomType != null) continue;
|
|
|
|
room.RoomType = generationParams.RoomTypeList.GetRandom(rt =>
|
|
rt.IsCorridor == room is Corridor &&
|
|
rt.Placement == RuinRoom.RoomPlacement.Any,
|
|
Rand.RandSync.Server);
|
|
|
|
if (room.RoomType == null)
|
|
{
|
|
DebugConsole.ThrowError("Could not find a suitable room type for a room (is corridor: " + (room is Corridor) + ")");
|
|
}
|
|
}
|
|
|
|
List<Rectangle> hullRects = new List<Rectangle>(allShapes.Select(s => s.Rect));
|
|
|
|
//split intersecting hulls into multiple parts to prevent overlaps
|
|
for (int i = 0; i < hullRects.Count; i++)
|
|
{
|
|
if (hullRects[i].Width <= 0 || hullRects[i].Height <= 0) continue;
|
|
for (int j = 0; j < hullRects.Count; j++)
|
|
{
|
|
if (i == j) continue;
|
|
if (hullRects[j].Width <= 0 || hullRects[j].Height <= 0) continue;
|
|
if (!hullRects[i].Intersects(hullRects[j])) continue;
|
|
|
|
//hull i goes through hull j vertically
|
|
if (hullRects[i].X >= hullRects[j].X && hullRects[i].Right <= hullRects[j].Right &&
|
|
hullRects[i].Y <= hullRects[j].Y && hullRects[i].Bottom >= hullRects[j].Bottom)
|
|
{
|
|
Rectangle rectLeft = new Rectangle(hullRects[j].X, hullRects[j].Y, hullRects[i].X - hullRects[j].X, hullRects[j].Height);
|
|
Rectangle rectRight = new Rectangle(hullRects[i].Right, hullRects[j].Y, hullRects[j].Right - hullRects[i].Right, hullRects[j].Height);
|
|
hullRects[j] = rectLeft;
|
|
hullRects.Add(rectRight);
|
|
}
|
|
else if //hull i goes through hull j horizontally
|
|
(hullRects[i].Y >= hullRects[j].Y && hullRects[i].Bottom <= hullRects[j].Bottom &&
|
|
hullRects[i].X <= hullRects[j].X && hullRects[i].Right >= hullRects[j].Right)
|
|
{
|
|
Rectangle rectBottom = new Rectangle(hullRects[j].X, hullRects[j].Y, hullRects[j].Width, hullRects[i].Y - hullRects[j].Y);
|
|
Rectangle rectTop = new Rectangle(hullRects[j].X, hullRects[i].Bottom, hullRects[j].Width, hullRects[j].Bottom - hullRects[i].Bottom);
|
|
hullRects[j] = rectBottom;
|
|
hullRects.Add(rectTop);
|
|
}
|
|
//upper side of hull i is inside hull j
|
|
else if (hullRects[j].Contains(hullRects[i].Location) && hullRects[j].Contains(new Vector2(hullRects[i].Right, hullRects[i].Y)))
|
|
{
|
|
hullRects[i] = new Rectangle(hullRects[i].X, hullRects[j].Bottom, hullRects[i].Width, hullRects[i].Bottom - hullRects[j].Bottom);
|
|
}
|
|
//lower side of hull i is inside hull j
|
|
else if (hullRects[j].Contains(new Vector2(hullRects[i].X, hullRects[i].Bottom)) && hullRects[j].Contains(new Vector2(hullRects[i].Right, hullRects[i].Bottom)))
|
|
{
|
|
hullRects[i] = new Rectangle(hullRects[i].X, hullRects[i].Y, hullRects[i].Width, hullRects[j].Y - hullRects[i].Y);
|
|
}
|
|
//left side of hull i is inside hull j
|
|
else if (hullRects[j].Contains(hullRects[i].Location) && hullRects[j].Contains(new Vector2(hullRects[i].X, hullRects[i].Bottom)))
|
|
{
|
|
hullRects[i] = new Rectangle(hullRects[j].X, hullRects[i].Y, hullRects[i].Right - hullRects[j].X, hullRects[i].Height);
|
|
}
|
|
//right side of hull i is inside hull j
|
|
else if (hullRects[j].Contains(new Vector2(hullRects[i].Right, hullRects[i].Y)) && hullRects[j].Contains(new Vector2(hullRects[i].Right, hullRects[i].Bottom)))
|
|
{
|
|
hullRects[i] = new Rectangle(hullRects[i].X, hullRects[i].Y, hullRects[j].X - hullRects[i].X, hullRects[i].Height);
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (RuinShape room in allShapes)
|
|
{
|
|
if (room.RoomType == null) continue;
|
|
//generate walls --------------------------------------------------------------
|
|
foreach (Line wall in room.Walls)
|
|
{
|
|
var ruinEntityConfig = room.RoomType.GetRandomEntity(RuinEntityType.Wall, room.GetLineAlignment(wall));
|
|
if (ruinEntityConfig == null) continue;
|
|
|
|
wall.Radius = (wall.A.X == wall.B.X) ?
|
|
(ruinEntityConfig.Prefab as StructurePrefab).Size.X * 0.5f :
|
|
(ruinEntityConfig.Prefab as StructurePrefab).Size.Y * 0.5f;
|
|
|
|
Rectangle rect = new Rectangle(
|
|
(int)(wall.A.X - wall.Radius),
|
|
(int)(wall.B.Y + wall.Radius),
|
|
(int)((wall.B.X - wall.A.X) + wall.Radius * 2.0f),
|
|
(int)((wall.B.Y - wall.A.Y) + wall.Radius * 2.0f));
|
|
|
|
//cut a section off from both ends of a horizontal wall to get nicer looking corners
|
|
if (wall.A.Y == wall.B.Y)
|
|
{
|
|
rect.Inflate(-32, 0);
|
|
if (rect.Width < Submarine.GridSize.X) continue;
|
|
}
|
|
|
|
var structure = new Structure(rect, ruinEntityConfig.Prefab as StructurePrefab, null)
|
|
{
|
|
ShouldBeSaved = false
|
|
};
|
|
structure.SetCollisionCategory(Physics.CollisionLevel);
|
|
CreateChildEntities(ruinEntityConfig, structure, room);
|
|
ruinEntities.Add(new RuinEntity(ruinEntityConfig, structure, room));
|
|
}
|
|
|
|
//generate backgrounds --------------------------------------------------------------
|
|
var backgroundConfig = room.RoomType.GetRandomEntity(RuinEntityType.Back, Alignment.Center);
|
|
if (backgroundConfig != null)
|
|
{
|
|
Rectangle backgroundRect = new Rectangle(room.Rect.X, room.Rect.Y + room.Rect.Height, room.Rect.Width, room.Rect.Height);
|
|
var backgroundStructure = new Structure(backgroundRect, (backgroundConfig.Prefab as StructurePrefab), null)
|
|
{
|
|
ShouldBeSaved = false
|
|
};
|
|
CreateChildEntities(backgroundConfig, backgroundStructure, room);
|
|
ruinEntities.Add(new RuinEntity(backgroundConfig, backgroundStructure, room));
|
|
}
|
|
|
|
var submarineBlocker = BodyFactory.CreateRectangle(GameMain.World,
|
|
ConvertUnits.ToSimUnits(room.Rect.Width),
|
|
ConvertUnits.ToSimUnits(room.Rect.Height),
|
|
1, ConvertUnits.ToSimUnits(room.Center));
|
|
|
|
submarineBlocker.IsStatic = true;
|
|
submarineBlocker.CollisionCategories = Physics.CollisionWall;
|
|
submarineBlocker.CollidesWith = Physics.CollisionWall;
|
|
|
|
//generate doors --------------------------------------------------------------
|
|
if (room is Corridor corridor)
|
|
{
|
|
var doorConfig = room.RoomType.GetRandomEntity(corridor.IsHorizontal ? RuinEntityType.Door : RuinEntityType.Hatch, Alignment.Center);
|
|
if (corridor != null && doorConfig != null)
|
|
{
|
|
//find all walls that are parallel to the corridor
|
|
var suitableWalls = corridor.IsHorizontal ?
|
|
corridor.Walls.FindAll(c => c.A.Y == c.B.Y) : corridor.Walls.FindAll(c => c.A.X == c.B.X);
|
|
|
|
if (suitableWalls.Any())
|
|
{
|
|
//choose a random wall to place the door next to
|
|
Vector2 doorPos = corridor.Center;
|
|
var wall = suitableWalls[Rand.Int(suitableWalls.Count, Rand.RandSync.Server)];
|
|
if (corridor.IsHorizontal)
|
|
{
|
|
doorPos.X = (wall.A.X + wall.B.X) / 2.0f;
|
|
}
|
|
else
|
|
{
|
|
doorPos.Y = (wall.A.Y + wall.B.Y) / 2.0f;
|
|
}
|
|
Item doorItem = null;
|
|
if (doorConfig.Prefab is ItemPrefab itemPrefab)
|
|
{
|
|
doorItem = new Item(doorConfig.Prefab as ItemPrefab, doorPos, null)
|
|
{
|
|
ShouldBeSaved = false
|
|
};
|
|
}
|
|
else if (doorConfig.Prefab is ItemAssemblyPrefab itemAssemblyPrefab)
|
|
{
|
|
var entities = itemAssemblyPrefab.CreateInstance(doorPos, sub: null);
|
|
foreach (MapEntity e in entities)
|
|
{
|
|
if (e is Structure) e.ShouldBeSaved = false;
|
|
if (doorItem == null && e is Item item && item.GetComponent<Door>() != null)
|
|
{
|
|
doorItem = item;
|
|
}
|
|
else
|
|
{
|
|
ruinEntities.Add(new RuinEntity(doorConfig, e, room));
|
|
}
|
|
}
|
|
if (doorConfig.Expand) { ExpandEntities(entities); }
|
|
//make sure the door gets positioned at the correct place regardless of its position in the item assembly
|
|
if (doorItem != null)
|
|
{
|
|
Vector2 doorOffset = doorPos - doorItem.WorldPosition;
|
|
foreach (MapEntity e in entities)
|
|
{
|
|
e.Move(doorOffset);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DebugConsole.ThrowError("Failed to create a ruin door. Ruin entity \"" + doorConfig.Name + "\" is marked as a door but is neither an item or an item assembly.");
|
|
continue;
|
|
}
|
|
|
|
Door door = doorItem?.GetComponent<Door>();
|
|
if (door == null)
|
|
{
|
|
DebugConsole.ThrowError("Failed to create a ruin door. Door not found in the ruin entity \"" + doorConfig.Name + "\".");
|
|
continue;
|
|
}
|
|
|
|
CreateChildEntities(doorConfig, doorItem, corridor);
|
|
doors.Add(door);
|
|
ruinEntities.Add(new RuinEntity(doorConfig, doorItem, room));
|
|
}
|
|
}
|
|
}
|
|
|
|
//generate props --------------------------------------------------------------
|
|
var props = room.RoomType.GetPropList(room, Rand.RandSync.Server);
|
|
foreach (RuinEntityConfig prop in props)
|
|
{
|
|
int amount = Rand.Range(prop.MinAmount, prop.MaxAmount + 1, Rand.RandSync.Server);
|
|
for (int i = 0; i < amount; i++)
|
|
{
|
|
CreateEntity(prop, room, parent: null);
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (RuinEntity entity in ruinEntities)
|
|
{
|
|
if (!entity.Room.RoomType.IsCorridor) { continue; }
|
|
|
|
Item item = entity.Entity as Item;
|
|
Door door = item?.GetComponent<Door>();
|
|
if (door == null) { continue; }
|
|
|
|
//split the hull the door is inside
|
|
for (int i = 0; i < hullRects.Count; i++)
|
|
{
|
|
Vector2 doorPos = door.Item.WorldPosition;
|
|
if (!hullRects[i].Contains(doorPos)) continue;
|
|
|
|
if (door.IsHorizontal)
|
|
{
|
|
Rectangle rectBottom = new Rectangle(hullRects[i].X, hullRects[i].Y, hullRects[i].Width, (int)doorPos.Y - hullRects[i].Y);
|
|
Rectangle rectTop = new Rectangle(hullRects[i].X, (int)doorPos.Y, hullRects[i].Width, hullRects[i].Bottom - (int)doorPos.Y);
|
|
hullRects[i] = rectBottom;
|
|
hullRects.Add(rectTop);
|
|
}
|
|
else
|
|
{
|
|
Rectangle rectLeft = new Rectangle(hullRects[i].X, hullRects[i].Y, (int)doorPos.X - hullRects[i].X, hullRects[i].Height);
|
|
Rectangle rectRight = new Rectangle((int)doorPos.X, hullRects[i].Y, hullRects[i].Right - (int)doorPos.X, hullRects[i].Height);
|
|
hullRects[i] = rectLeft;
|
|
hullRects.Add(rectRight);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
//randomize door states (20% open on average)
|
|
foreach (Door door in doors)
|
|
{
|
|
door.IsOpen = Rand.Range(0.0f, 1.0f, Rand.RandSync.Server) < 0.2f;
|
|
}
|
|
|
|
//create connections between all generated entities ---------------------------
|
|
foreach (RuinEntity ruinEntity in ruinEntities)
|
|
{
|
|
CreateConnections(ruinEntity);
|
|
}
|
|
|
|
foreach (RuinEntity ruinEntity in ruinEntities)
|
|
{
|
|
if (ruinEntity.Entity is Item item)
|
|
{
|
|
foreach (ItemComponent ic in item.Components)
|
|
{
|
|
// Prevent wiring & interacting
|
|
if (ic is ConnectionPanel connectionPanel)
|
|
{
|
|
connectionPanel.Locked = true;
|
|
connectionPanel.CanBeSelected = false;
|
|
}
|
|
|
|
// Hide wires for now
|
|
if (ic is Wire wire)
|
|
{
|
|
wire.Hidden = true;
|
|
wire.CanBeSelected = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//create hulls ---------------------------
|
|
foreach (Rectangle hullRect in hullRects)
|
|
{
|
|
if (hullRect.Width <= 0 || hullRect.Height <= 0) continue;
|
|
var hull = new Hull(MapEntityPrefab.Find(null, "hull"),
|
|
new Rectangle(hullRect.X, hullRect.Y + hullRect.Height, hullRect.Width, hullRect.Height), submarine: null)
|
|
{
|
|
ParentRuin = this,
|
|
ShouldBeSaved = false
|
|
};
|
|
RuinShape room = allShapes.Find(s => s.Rect.Contains(hullRect.Center));
|
|
if (room?.RoomType != null)
|
|
{
|
|
hull.WaterVolume = hull.Volume * Rand.Range(room.RoomType.MinWaterAmount, room.RoomType.MaxWaterAmount, Rand.RandSync.Server);
|
|
}
|
|
entityGrid.InsertEntity(hull);
|
|
}
|
|
|
|
//create gaps between hulls ---------------------------
|
|
hullRects.Add(entranceRoom.Rect);
|
|
for (int i = 0; i < hullRects.Count; i++)
|
|
{
|
|
if (hullRects[i].Width <= 0 || hullRects[i].Height <= 0) continue;
|
|
for (int j = i + 1; j < hullRects.Count; j++)
|
|
{
|
|
Rectangle? gapRect = null;
|
|
if (Math.Abs(hullRects[i].X - hullRects[j].Right) <= 1 && hullYIntersect(hullRects[i], hullRects[j]))
|
|
{
|
|
gapRect = new Rectangle(
|
|
hullRects[i].X - 3, Math.Max(hullRects[i].Y, hullRects[j].Y),
|
|
6, Math.Min(hullRects[i].Bottom, hullRects[j].Bottom) - Math.Max(hullRects[i].Y, hullRects[j].Y));
|
|
}
|
|
else if (Math.Abs(hullRects[i].Right - hullRects[j].X) <= 1 && hullYIntersect(hullRects[i], hullRects[j]))
|
|
{
|
|
gapRect = new Rectangle(
|
|
hullRects[i].Right - 3, Math.Max(hullRects[i].Y, hullRects[j].Y),
|
|
6, Math.Min(hullRects[i].Bottom, hullRects[j].Bottom) - Math.Max(hullRects[i].Y, hullRects[j].Y));
|
|
}
|
|
else if (Math.Abs(hullRects[i].Y - hullRects[j].Bottom) <= 1 && hullXIntersect(hullRects[i], hullRects[j]))
|
|
{
|
|
gapRect = new Rectangle(
|
|
Math.Max(hullRects[i].X, hullRects[j].X), hullRects[i].Y - 3,
|
|
Math.Min(hullRects[i].Right, hullRects[j].Right) - Math.Max(hullRects[i].X, hullRects[j].X), 6);
|
|
}
|
|
else if (Math.Abs(hullRects[i].Bottom - hullRects[j].Y) <= 1 && hullXIntersect(hullRects[i], hullRects[j]))
|
|
{
|
|
gapRect = new Rectangle(
|
|
Math.Max(hullRects[i].X, hullRects[j].X), hullRects[i].Bottom - 3,
|
|
Math.Min(hullRects[i].Right, hullRects[j].Right) - Math.Max(hullRects[i].X, hullRects[j].X), 6);
|
|
}
|
|
|
|
if (!gapRect.HasValue || gapRect.Value.Width <= 0 || gapRect.Value.Height <= 0) continue;
|
|
|
|
//doors create their own gaps, don't create an additional one if there's a door at this
|
|
bool doorFound = false;
|
|
foreach (Door door in doors)
|
|
{
|
|
if (Math.Abs(door.Item.WorldPosition.X - gapRect.Value.Center.X) < 5 &&
|
|
Math.Abs(door.Item.WorldPosition.Y - gapRect.Value.Center.Y) < 5)
|
|
{
|
|
doorFound = true;
|
|
break;
|
|
}
|
|
}
|
|
if (doorFound) { continue; }
|
|
|
|
new Gap(new Rectangle(gapRect.Value.X, gapRect.Value.Y + gapRect.Value.Height, gapRect.Value.Width, gapRect.Value.Height),
|
|
isHorizontal: gapRect.Value.Height > gapRect.Value.Width, submarine: null)
|
|
{
|
|
ParentRuin = this,
|
|
ShouldBeSaved = false
|
|
};
|
|
}
|
|
}
|
|
|
|
foreach (RuinEntity ruinEntity in ruinEntities)
|
|
{
|
|
ruinEntity.Entity.ParentRuin = this;
|
|
}
|
|
|
|
bool hullXIntersect(Rectangle rect1, Rectangle rect2)
|
|
{
|
|
return
|
|
(rect1.X >= rect2.X && rect1.X <= rect2.Right) ||
|
|
(rect2.X >= rect1.X && rect2.X <= rect1.Right);
|
|
}
|
|
bool hullYIntersect(Rectangle rect1, Rectangle rect2)
|
|
{
|
|
return
|
|
(rect1.Y >= rect2.Y && rect1.Y <= rect2.Bottom) ||
|
|
(rect2.Y >= rect1.Y && rect2.Y <= rect1.Bottom);
|
|
}
|
|
}
|
|
|
|
private void CreateEntity(RuinEntityConfig entityConfig, RuinShape room, MapEntity parent)
|
|
{
|
|
if (room == null) return;
|
|
|
|
int leftWallThickness = 32, rightWallThickness = 32;
|
|
int topWallThickness = 32, bottomWallThickness = 32;
|
|
foreach (Line wall in room.Walls)
|
|
{
|
|
if (wall.IsHorizontal)
|
|
{
|
|
if (wall.A.Y > room.Rect.Center.Y)
|
|
bottomWallThickness = (int)wall.Radius;
|
|
else
|
|
topWallThickness = (int)wall.Radius;
|
|
}
|
|
else
|
|
{
|
|
if (wall.A.X > room.Rect.Center.X)
|
|
rightWallThickness = (int)wall.Radius;
|
|
else
|
|
leftWallThickness = (int)wall.Radius;
|
|
}
|
|
}
|
|
|
|
Rectangle roomBounds = new Rectangle(
|
|
room.Rect.X + leftWallThickness,
|
|
room.Rect.Y + bottomWallThickness,
|
|
room.Rect.Width - leftWallThickness - rightWallThickness,
|
|
room.Rect.Height - topWallThickness - bottomWallThickness);
|
|
|
|
Vector2 size = Vector2.Zero;
|
|
if (entityConfig.Prefab is StructurePrefab structurePrefab)
|
|
{
|
|
size = structurePrefab.Size;
|
|
}
|
|
else if (entityConfig.Prefab is ItemPrefab itemPrefab)
|
|
{
|
|
size = itemPrefab.Size;
|
|
}
|
|
else if (entityConfig.Prefab is ItemAssemblyPrefab assemblyPrefab)
|
|
{
|
|
size = new Vector2(assemblyPrefab.Bounds.Width, assemblyPrefab.Bounds.Height);
|
|
|
|
Vector2 boundsMin = new Vector2(-assemblyPrefab.Bounds.X, -assemblyPrefab.Bounds.Y);
|
|
Vector2 boundsMax = new Vector2(assemblyPrefab.Bounds.Right, assemblyPrefab.Bounds.Bottom);
|
|
|
|
roomBounds = new Rectangle(
|
|
(int)(roomBounds.X + boundsMin.X),
|
|
(int)(roomBounds.Y + boundsMin.Y),
|
|
(int)(roomBounds.Width - boundsMin.X - boundsMax.X),
|
|
(int)(roomBounds.Height - boundsMin.Y - boundsMax.Y));
|
|
}
|
|
|
|
List<Vector2> potentialAnchorPositions = new List<Vector2>();
|
|
if (entityConfig.Alignment.HasFlag(Alignment.Top))
|
|
{
|
|
potentialAnchorPositions.Add(new Vector2(roomBounds.Center.X, roomBounds.Bottom));
|
|
}
|
|
if (entityConfig.Alignment.HasFlag(Alignment.Bottom))
|
|
{
|
|
potentialAnchorPositions.Add(new Vector2(roomBounds.Center.X, roomBounds.Top));
|
|
}
|
|
if (entityConfig.Alignment.HasFlag(Alignment.Right))
|
|
{
|
|
potentialAnchorPositions.Add(new Vector2(roomBounds.Right, roomBounds.Center.Y));
|
|
}
|
|
if (entityConfig.Alignment.HasFlag(Alignment.Left))
|
|
{
|
|
potentialAnchorPositions.Add(new Vector2(roomBounds.X, roomBounds.Center.Y));
|
|
}
|
|
if (entityConfig.Alignment.HasFlag(Alignment.Center) || potentialAnchorPositions.Count == 0)
|
|
{
|
|
potentialAnchorPositions.Add(roomBounds.Center.ToVector2());
|
|
}
|
|
|
|
Vector2 position = potentialAnchorPositions[Rand.Int(potentialAnchorPositions.Count, Rand.RandSync.Server)];
|
|
Vector2 minPosition = new Vector2(
|
|
position.X + entityConfig.MinOffset.X * roomBounds.Width,
|
|
position.Y + entityConfig.MinOffset.Y * roomBounds.Height);
|
|
Vector2 maxPosition = new Vector2(
|
|
position.X + entityConfig.MaxOffset.X * roomBounds.Width,
|
|
position.Y + entityConfig.MaxOffset.Y * roomBounds.Height);
|
|
|
|
position = new Vector2(
|
|
Rand.Range(minPosition.X, maxPosition.X, Rand.RandSync.Server),
|
|
Rand.Range(minPosition.Y, maxPosition.Y, Rand.RandSync.Server));
|
|
position.X = MathHelper.Clamp(position.X, roomBounds.X, roomBounds.Right);
|
|
position.Y = MathHelper.Clamp(position.Y, roomBounds.Y, roomBounds.Bottom);
|
|
|
|
int iterations = 0;
|
|
while (iterations < 100)
|
|
{
|
|
bool overlapFound = false;
|
|
foreach (RuinEntity ruinEntity in ruinEntities)
|
|
{
|
|
if (ruinEntity.Config.Type == RuinEntityType.Back || ruinEntity.Config.Type == RuinEntityType.Wall) continue;
|
|
Vector2 diff = position - ruinEntity.Entity.Position;
|
|
if (Math.Abs(diff.X) < (size.X + ruinEntity.Entity.Rect.Width) / 2 &&
|
|
Math.Abs(diff.Y) < (size.Y + ruinEntity.Entity.Rect.Height) / 2)
|
|
{
|
|
float dist = diff.Length();
|
|
Vector2 moveDir = dist < 0.01f ? Vector2.UnitY : diff / dist;
|
|
|
|
position += moveDir * 100.0f;
|
|
|
|
position.X = MathHelper.Clamp(position.X, roomBounds.X, roomBounds.Right);
|
|
position.Y = MathHelper.Clamp(position.Y, roomBounds.Y, roomBounds.Bottom);
|
|
overlapFound = true;
|
|
}
|
|
}
|
|
iterations++;
|
|
if (!overlapFound) { break; }
|
|
}
|
|
|
|
MapEntity entity = null;
|
|
if (entityConfig.Prefab is ItemPrefab)
|
|
{
|
|
Item container = null;
|
|
if (entityConfig.TargetContainer != "")
|
|
{
|
|
List<RuinEntity> roomContents = ruinEntities.FindAll(re => re.Room == room);
|
|
for (int j = 0; j < roomContents.Count; j++)
|
|
{
|
|
if (roomContents[j].Entity is Item && (roomContents[j].Entity as Item).HasTag(entityConfig.TargetContainer))
|
|
{
|
|
container = roomContents[j].Entity as Item;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (container == null) DebugConsole.ThrowError("No container with tag \"" + entityConfig.TargetContainer + "\" found, placing item in the room");
|
|
}
|
|
|
|
if (container != null)
|
|
{
|
|
entity = new Item((ItemPrefab)entityConfig.Prefab, container.Position, null);
|
|
if (container.OwnInventory.TryPutItem(entity as Item, null, createNetworkEvent: false))
|
|
{
|
|
CreateChildEntities(entityConfig, entity, room);
|
|
ruinEntities.Add(new RuinEntity(entityConfig, entity, room, parent));
|
|
}
|
|
else // Removing items that don't fit in the container
|
|
{
|
|
entity.Remove();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
entity = new Item((ItemPrefab)entityConfig.Prefab, position, null);
|
|
CreateChildEntities(entityConfig, entity, room);
|
|
ruinEntities.Add(new RuinEntity(entityConfig, entity, room, parent));
|
|
}
|
|
}
|
|
else if (entityConfig.Prefab is ItemAssemblyPrefab itemAssemblyPrefab)
|
|
{
|
|
var entities = itemAssemblyPrefab.CreateInstance(position, sub: null);
|
|
foreach (MapEntity e in entities)
|
|
{
|
|
if (e is Structure)
|
|
{
|
|
e.ShouldBeSaved = false;
|
|
}
|
|
else if (e is Item item)
|
|
{
|
|
var door = item.GetComponent<Door>();
|
|
if (door != null) { doors.Add(door); }
|
|
}
|
|
ruinEntities.Add(new RuinEntity(entityConfig, e, room, parent));
|
|
}
|
|
if (entityConfig.Expand)
|
|
{
|
|
ExpandEntities(entities);
|
|
}
|
|
CreateChildEntities(entityConfig, entity, room);
|
|
}
|
|
else
|
|
{
|
|
entity = new Structure(new Rectangle(
|
|
(int)(position.X - size.X / 2.0f), (int)(position.Y + size.Y / 2.0f),
|
|
(int)size.X, (int)size.Y),
|
|
entityConfig.Prefab as StructurePrefab, null)
|
|
{
|
|
ShouldBeSaved = false
|
|
};
|
|
if (entityConfig.Expand)
|
|
{
|
|
ExpandEntities(new List<MapEntity>() { entity });
|
|
}
|
|
CreateChildEntities(entityConfig, entity, room);
|
|
ruinEntities.Add(new RuinEntity(entityConfig, entity, room, parent));
|
|
}
|
|
}
|
|
|
|
private void CreateChildEntities(RuinEntityConfig parentEntityConfig, MapEntity parentEntity, RuinShape room, Rand.RandSync randSync = Rand.RandSync.Server)
|
|
{
|
|
Dictionary<int, List<RuinEntityConfig>> propGroups = new Dictionary<int, List<RuinEntityConfig>>();
|
|
foreach (RuinEntityConfig entityConfig in parentEntityConfig.ChildEntities)
|
|
{
|
|
if (!propGroups.ContainsKey(entityConfig.SingleGroupIndex))
|
|
{
|
|
propGroups[entityConfig.SingleGroupIndex] = new List<RuinEntityConfig>();
|
|
}
|
|
propGroups[entityConfig.SingleGroupIndex].Add(entityConfig);
|
|
}
|
|
|
|
List<RuinEntityConfig> props = new List<RuinEntityConfig>();
|
|
foreach (KeyValuePair<int, List<RuinEntityConfig>> propGroup in propGroups)
|
|
{
|
|
if (propGroup.Key == 0)
|
|
{
|
|
props.AddRange(propGroup.Value);
|
|
}
|
|
else
|
|
{
|
|
props.Add(propGroup.Value[Rand.Int(propGroup.Value.Count, randSync)]);
|
|
}
|
|
}
|
|
|
|
foreach (RuinEntityConfig childEntity in props)
|
|
{
|
|
var childRoom = FindRoom(childEntity.PlacementRelativeToParent, room);
|
|
if (childRoom != null)
|
|
{
|
|
int amount = Rand.Range(childEntity.MinAmount, childEntity.MaxAmount + 1, Rand.RandSync.Server);
|
|
for (int i = 0; i < amount; i++)
|
|
{
|
|
CreateEntity(childEntity, childRoom, parentEntity);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void CreateConnections(RuinEntity entity)
|
|
{
|
|
foreach (RuinEntityConfig.EntityConnection connection in entity.Config.EntityConnections)
|
|
{
|
|
if (!string.IsNullOrEmpty(connection.SourceEntityIdentifier) &&
|
|
connection.SourceEntityIdentifier != entity.Entity?.prefab.Identifier)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
MapEntity targetEntity = null;
|
|
if (connection.TargetEntityIdentifier == "parent")
|
|
{
|
|
targetEntity = entity.Parent;
|
|
}
|
|
else if (!string.IsNullOrEmpty(connection.RoomName))
|
|
{
|
|
RuinShape targetRoom = null;
|
|
if (Enum.TryParse(connection.RoomName, out RuinEntityConfig.RelativePlacement placement))
|
|
{
|
|
targetRoom = FindRoom(placement, entity.Room);
|
|
}
|
|
else
|
|
{
|
|
targetRoom = allShapes.Find(s => s.RoomType?.Name == connection.RoomName);
|
|
}
|
|
|
|
if (targetRoom == null)
|
|
{
|
|
DebugConsole.ThrowError("Error while generating ruins - could not find a room of the type \"" + connection.RoomName + "\".");
|
|
}
|
|
else
|
|
{
|
|
targetEntity = ruinEntities.GetRandom(e =>
|
|
e.Room == targetRoom &&
|
|
e.Entity.prefab?.Identifier == connection.TargetEntityIdentifier)?.Entity;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
targetEntity = ruinEntities.GetRandom(e => e.Entity.prefab?.Identifier == connection.TargetEntityIdentifier)?.Entity;
|
|
}
|
|
|
|
if (targetEntity == null) continue;
|
|
|
|
if (connection.WireConnection != null)
|
|
{
|
|
Item item = entity.Entity as Item;
|
|
if (item == null)
|
|
{
|
|
DebugConsole.ThrowError("Could not connect a wire to the ruin entity \"" + entity.Entity.Name + "\" - the entity is not an item.");
|
|
continue;
|
|
}
|
|
else if (item.Connections == null)
|
|
{
|
|
DebugConsole.ThrowError("Could not connect a wire to the ruin entity \"" + entity.Entity.Name + "\" - the item does not have a connection panel component.");
|
|
continue;
|
|
}
|
|
|
|
Item parentItem = entity.Parent as Item;
|
|
if (parentItem == null)
|
|
{
|
|
DebugConsole.ThrowError("Could not connect a wire to the ruin entity \"" + parentItem.Name + "\" - the entity is not an item.");
|
|
continue;
|
|
}
|
|
else if (parentItem.Connections == null)
|
|
{
|
|
DebugConsole.ThrowError("Could not connect a wire to the ruin entity \"" + parentItem.Name + "\" - the item does not have a connection panel component.");
|
|
continue;
|
|
}
|
|
|
|
//TODO: alien wire prefab w/ custom sprite?
|
|
var wirePrefab = MapEntityPrefab.Find(null, "blackwire") as ItemPrefab;
|
|
|
|
var conn1 = item.Connections.Find(c => c.Name == connection.WireConnection.First);
|
|
if (conn1 == null)
|
|
{
|
|
DebugConsole.ThrowError("Could not connect a wire to the ruin entity \"" + item.Name +
|
|
"\" - the item does not have a connection named \"" + connection.WireConnection.First + "\".");
|
|
continue;
|
|
}
|
|
var conn2 = parentItem.Connections.Find(c => c.Name == connection.WireConnection.Second);
|
|
if (conn2 == null)
|
|
{
|
|
DebugConsole.ThrowError("Could not connect a wire to the ruin entity \"" + parentItem.Name +
|
|
"\" - the item does not have a connection named \"" + connection.WireConnection.Second + "\".");
|
|
continue;
|
|
}
|
|
|
|
var wire = new Item(wirePrefab, parentItem.WorldPosition, null).GetComponent<Items.Components.Wire>();
|
|
wire.Item.ShouldBeSaved = false;
|
|
conn1.TryAddLink(wire);
|
|
wire.Connect(conn1, true);
|
|
conn2.TryAddLink(wire);
|
|
wire.Connect(conn2, true);
|
|
wire.Hidden = true; // Hidden for now
|
|
}
|
|
else
|
|
{
|
|
entity.Entity.linkedTo.Add(targetEntity);
|
|
targetEntity.linkedTo.Add(entity.Entity);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ExpandEntities(IEnumerable<MapEntity> entities)
|
|
{
|
|
Vector2 xBounds = new Vector2(entities.Min(e => e.Rect.X), entities.Max(e => e.Rect.Right));
|
|
Vector2 yBounds = new Vector2(entities.Min(e => e.Rect.Y - e.Rect.Height), entities.Max(e => e.Rect.Y));
|
|
Vector2 center = new Vector2((xBounds.X + xBounds.Y) / 2.0f, (yBounds.X + yBounds.Y) / 2.0f);
|
|
|
|
foreach (MapEntity entity in entities)
|
|
{
|
|
if (entity is Item item)
|
|
{
|
|
Vector2 moveTo = StretchPoint(entity.WorldPosition, center, xBounds, yBounds);
|
|
Vector2 moveAmount = moveTo - entity.WorldPosition;
|
|
var connectionPanel = item.GetComponent<ConnectionPanel>();
|
|
connectionPanel?.MoveConnectedWires(moveAmount);
|
|
entity.Move(moveAmount);
|
|
}
|
|
else if (entity is Structure structure)
|
|
{
|
|
if (!entity.ResizeHorizontal && !entity.ResizeVertical)
|
|
{
|
|
Vector2 moveTo = StretchPoint(entity.WorldPosition, center, xBounds, yBounds);
|
|
entity.Move(moveTo - entity.WorldPosition);
|
|
continue;
|
|
}
|
|
|
|
Vector2 structureBoundsMin = new Vector2(structure.Rect.X, structure.Rect.Y - structure.Rect.Height);
|
|
Vector2 structureBoundsMax = new Vector2(structure.Rect.Right, structure.Rect.Y);
|
|
|
|
if (structure.ResizeHorizontal)
|
|
{
|
|
if (structure.Rect.Right > center.X)
|
|
{
|
|
Vector2 moveTo = StretchPoint(
|
|
new Vector2(structureBoundsMax.X, structure.Rect.Y - structure.Rect.Height / 2),
|
|
new Vector2(center.X, structure.Rect.Y - structure.Rect.Height / 2),
|
|
xBounds, yBounds);
|
|
structureBoundsMax.X = moveTo.X;
|
|
}
|
|
if (structure.Rect.X < center.X)
|
|
{
|
|
Vector2 moveTo = StretchPoint(
|
|
new Vector2(structureBoundsMin.X, structure.Rect.Y - structure.Rect.Height / 2),
|
|
new Vector2(center.X, structure.Rect.Y - structure.Rect.Height / 2),
|
|
xBounds, yBounds);
|
|
structureBoundsMin.X = moveTo.X;
|
|
}
|
|
}
|
|
if (structure.ResizeVertical)
|
|
{
|
|
if (structure.Rect.Y > center.X)
|
|
{
|
|
Vector2 moveTo = StretchPoint(
|
|
new Vector2(structure.Rect.Center.X, structureBoundsMax.Y),
|
|
new Vector2(structure.Rect.Center.X, center.Y),
|
|
xBounds, yBounds);
|
|
structureBoundsMax.Y = moveTo.Y;
|
|
}
|
|
if (structure.Rect.Y - structure.Rect.Height < center.Y)
|
|
{
|
|
Vector2 moveTo = StretchPoint(
|
|
new Vector2(structure.Rect.Center.X, structureBoundsMin.Y),
|
|
new Vector2(structure.Rect.Center.X, center.Y),
|
|
xBounds, yBounds);
|
|
structureBoundsMin.Y = moveTo.Y;
|
|
}
|
|
}
|
|
|
|
structure.Rect = new Rectangle(
|
|
(int)structureBoundsMin.X,
|
|
(int)structureBoundsMax.Y,
|
|
(int)(structureBoundsMax.X - structureBoundsMin.X),
|
|
(int)(structureBoundsMax.Y - structureBoundsMin.Y));
|
|
}
|
|
}
|
|
}
|
|
|
|
private Vector2 StretchPoint(Vector2 point, Vector2 center, Vector2 xBounds, Vector2 yBounds)
|
|
{
|
|
Vector2 diff = point - center;
|
|
if (diff.LengthSquared() < 0.0001f) return point;
|
|
|
|
Vector2? closestIntersection = RayCastWalls(point, Vector2.Normalize(diff));
|
|
|
|
if (!closestIntersection.HasValue) return point;
|
|
|
|
Vector2 moveAmount = closestIntersection.Value - point;
|
|
Vector2 moveRatio = new Vector2(
|
|
Math.Abs(diff.X) / ((xBounds.Y - xBounds.X) * 0.5f),
|
|
Math.Abs(diff.Y) / ((yBounds.Y - yBounds.X) * 0.5f));
|
|
return point + new Vector2(moveAmount.X * moveRatio.X, moveAmount.Y * moveRatio.Y);
|
|
}
|
|
|
|
private Vector2? RayCastWalls(Vector2 worldPosition, Vector2 dir)
|
|
{
|
|
float rayLength = 10000.0f;
|
|
Vector2 rayStart = worldPosition;
|
|
Vector2 rayEnd = worldPosition + dir * rayLength;
|
|
Vector2? closestIntersection = null;
|
|
float closestDist = rayLength * rayLength;
|
|
foreach (Line line in walls)
|
|
{
|
|
if (!MathUtils.GetLineIntersection(line.A, line.B, rayStart, rayEnd, out Vector2 intersection)) { continue; }
|
|
|
|
intersection = line.IsHorizontal ?
|
|
new Vector2(intersection.X, intersection.Y - Math.Sign(dir.Y) * line.Radius) :
|
|
new Vector2(intersection.X - Math.Sign(dir.X) * line.Radius, intersection.Y);
|
|
|
|
float dist = Vector2.DistanceSquared(rayStart, intersection);
|
|
if (dist < closestDist)
|
|
{
|
|
closestIntersection = intersection;
|
|
closestDist = dist;
|
|
}
|
|
}
|
|
return closestIntersection;
|
|
}
|
|
|
|
private RuinShape FindRoom(RuinEntityConfig.RelativePlacement placement, RuinShape relativeTo)
|
|
{
|
|
switch (placement)
|
|
{
|
|
case RuinEntityConfig.RelativePlacement.SameRoom:
|
|
return relativeTo;
|
|
case RuinEntityConfig.RelativePlacement.NextRoom:
|
|
return FindNearestRoom(relativeTo, rooms, 1);
|
|
case RuinEntityConfig.RelativePlacement.NextCorridor:
|
|
return FindNearestRoom(relativeTo, corridors, 1);
|
|
case RuinEntityConfig.RelativePlacement.PreviousRoom:
|
|
return FindNearestRoom(relativeTo, rooms, -1);
|
|
case RuinEntityConfig.RelativePlacement.PreviousCorridor:
|
|
return FindNearestRoom(relativeTo, corridors, -1);
|
|
case RuinEntityConfig.RelativePlacement.FirstRoom:
|
|
return FindFirstRoom(rooms);
|
|
case RuinEntityConfig.RelativePlacement.FirstCorridor:
|
|
return FindFirstRoom(corridors);
|
|
case RuinEntityConfig.RelativePlacement.LastRoom:
|
|
return FindLastRoom(rooms);
|
|
case RuinEntityConfig.RelativePlacement.LastCorridor:
|
|
return FindLastRoom(corridors);
|
|
default:
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Find the nearest room relative to a specific room.
|
|
/// </summary>
|
|
/// <param name="relativeTo">The room to compare the distance with</param>
|
|
/// <param name="roomList">List of rooms to check (use a list that only contains rooms/corridors if you want a specific types of rooms)</param>
|
|
/// <param name="dir">Direction to check: 1 = find the next room, -1 = find the previous room</param>
|
|
private RuinShape FindNearestRoom(RuinShape relativeTo, IEnumerable<RuinShape> roomList, int dir, Func<RuinShape, bool> predicate = null)
|
|
{
|
|
dir = Math.Sign(dir);
|
|
RuinShape selectedRoom = null;
|
|
foreach (RuinShape room in roomList)
|
|
{
|
|
if (room == relativeTo) continue;
|
|
if (predicate != null && !predicate(room)) continue;
|
|
int roomDir = Math.Sign(room.DistanceFromEntrance - relativeTo.DistanceFromEntrance);
|
|
|
|
if (roomDir == 0 || roomDir == dir)
|
|
{
|
|
if (selectedRoom == null)
|
|
{
|
|
selectedRoom = room;
|
|
}
|
|
else //room already selected, check if this one is closer
|
|
{
|
|
//closer than the previously selected room
|
|
if (Math.Abs(room.DistanceFromEntrance - relativeTo.DistanceFromEntrance) <
|
|
Math.Abs(selectedRoom.DistanceFromEntrance - relativeTo.DistanceFromEntrance))
|
|
{
|
|
selectedRoom = room;
|
|
}
|
|
//same distance measured in room indices, select the room if the actual distance is smaller
|
|
else if (room.DistanceFromEntrance == selectedRoom.DistanceFromEntrance &&
|
|
Vector2.DistanceSquared(relativeTo.Center, room.Center) < Vector2.DistanceSquared(relativeTo.Center, selectedRoom.Center))
|
|
{
|
|
selectedRoom = room;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return selectedRoom;
|
|
}
|
|
|
|
private RuinShape FindFirstRoom(IEnumerable<RuinShape> roomList, Func<RuinShape, bool> predicate = null)
|
|
{
|
|
if (!roomList.Any()) { return null; }
|
|
RuinShape firstRoom = null;
|
|
foreach (RuinShape room in roomList)
|
|
{
|
|
if (predicate != null && !predicate(room)) continue;
|
|
if (firstRoom == null || room.DistanceFromEntrance < firstRoom.DistanceFromEntrance)
|
|
{
|
|
firstRoom = room;
|
|
}
|
|
}
|
|
return firstRoom;
|
|
}
|
|
|
|
private RuinShape FindLastRoom(IEnumerable<RuinShape> roomList, Func<RuinShape, bool> predicate = null)
|
|
{
|
|
if (!roomList.Any()) { return null; }
|
|
RuinShape lastRoom = null;
|
|
foreach (RuinShape room in roomList)
|
|
{
|
|
if (predicate != null && !predicate(room)) continue;
|
|
if (lastRoom == null || room.DistanceFromEntrance > lastRoom.DistanceFromEntrance)
|
|
{
|
|
lastRoom = room;
|
|
}
|
|
}
|
|
return lastRoom;
|
|
}
|
|
|
|
}
|
|
}
|