450 lines
15 KiB
C#
450 lines
15 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Threading.Tasks;
|
||
using ColdMint.scripts.levelGraphEditor;
|
||
using ColdMint.scripts.map.dateBean;
|
||
using ColdMint.scripts.map.interfaces;
|
||
using ColdMint.scripts.map.room;
|
||
using ColdMint.scripts.utils;
|
||
using Godot;
|
||
|
||
namespace ColdMint.scripts.map.RoomPlacer;
|
||
|
||
/// <summary>
|
||
/// <para>Patchwork room placement strategy</para>
|
||
/// <para>拼接的房间放置策略</para>
|
||
/// </summary>
|
||
/// <remarks>
|
||
///<para>Under this strategy, think of each room template as a puzzle piece, find their "slots", and then connect them together.</para>
|
||
///<para>在此策略下,将每个房间模板看作是一块拼图,找到他们的“槽”,然后将其连接在一起。</para>
|
||
/// </remarks>
|
||
public class PatchworkRoomPlacementStrategy : IRoomPlacementStrategy
|
||
{
|
||
/// <summary>
|
||
/// <para>We use a temporary area to measure whether the rooms overlap</para>
|
||
/// <para>我们使用一个临时区域进行测量房间是否重叠</para>
|
||
/// </summary>
|
||
private Area2D? _measuringArea2D;
|
||
|
||
private CollisionShape2D? _measuringCollisionShape2D;
|
||
|
||
private Area2D? _selfArea2D;
|
||
|
||
/// <summary>
|
||
/// <para>How many rooms overlap with the new rooms that will be placed</para>
|
||
/// <para>有多少个房间与将要放置的新房间重叠</para>
|
||
/// </summary>
|
||
private int _overlapQuantity;
|
||
|
||
public Task<bool> StartGeneration(Node mapRoot)
|
||
{
|
||
if (_measuringArea2D == null)
|
||
{
|
||
_measuringArea2D = new Area2D();
|
||
_measuringArea2D.Monitoring = true;
|
||
_measuringArea2D.AreaEntered += body =>
|
||
{
|
||
if (_selfArea2D != null && body == _selfArea2D)
|
||
{
|
||
return;
|
||
}
|
||
|
||
//Room overlap detected
|
||
//检测到房间重叠
|
||
_overlapQuantity++;
|
||
};
|
||
_measuringArea2D.AreaExited += body =>
|
||
{
|
||
if (_selfArea2D != null && body == _selfArea2D)
|
||
{
|
||
return;
|
||
}
|
||
|
||
//Rooms no longer overlap
|
||
//房间不再重叠
|
||
_overlapQuantity--;
|
||
};
|
||
mapRoot.AddChild(_measuringArea2D);
|
||
}
|
||
|
||
if (_measuringCollisionShape2D == null)
|
||
{
|
||
_measuringCollisionShape2D = new CollisionShape2D();
|
||
_measuringArea2D.AddChild(_measuringCollisionShape2D);
|
||
}
|
||
|
||
return Task.FromResult(true);
|
||
}
|
||
|
||
public Task GeneratedComplete(Node mapRoot)
|
||
{
|
||
if (_measuringCollisionShape2D != null)
|
||
{
|
||
_measuringCollisionShape2D?.QueueFree();
|
||
_measuringArea2D?.RemoveChild(_measuringCollisionShape2D);
|
||
_measuringCollisionShape2D = null;
|
||
}
|
||
|
||
if (_measuringArea2D != null)
|
||
{
|
||
_measuringArea2D?.QueueFree();
|
||
mapRoot.RemoveChild(_measuringArea2D);
|
||
_measuringArea2D = null;
|
||
}
|
||
|
||
return Task.CompletedTask;
|
||
}
|
||
|
||
|
||
public Task<bool> PlaceRoom(Node mapRoot, RoomPlacementData roomPlacementData)
|
||
{
|
||
if (roomPlacementData.Room == null || roomPlacementData.Position == null)
|
||
{
|
||
return Task.FromResult(false);
|
||
}
|
||
|
||
if (roomPlacementData.Room.RootNode == null)
|
||
{
|
||
return Task.FromResult(false);
|
||
}
|
||
|
||
var rootNode = roomPlacementData.Room.RootNode;
|
||
mapRoot.AddChild(rootNode);
|
||
rootNode.Position = roomPlacementData.Position.Value;
|
||
//Place navigation Link
|
||
//放置导航Link
|
||
//计算旧房间插槽的绝对位置。
|
||
if (roomPlacementData.Room != null && roomPlacementData.ParentRoom != null )
|
||
{
|
||
|
||
}
|
||
|
||
|
||
// if (roomPlacementData.NewRoomSlot != null && roomPlacementData.ParentRoomSlot != null &&
|
||
// roomPlacementData.Position != null)
|
||
// {
|
||
// var navigationLink2D = new NavigationLink2D();
|
||
// navigationLink2D.Position = roomPlacementData.Position.Value;
|
||
// var startPosition = roomPlacementData.ParentRoomSlot.StartPosition;
|
||
// var actualStart = startPosition + roomPlacementData.Position.Value;
|
||
// navigationLink2D.StartPosition = actualStart;
|
||
// navigationLink2D.EndPosition = roomPlacementData.NewRoomSlot.StartPosition;
|
||
// mapRoot.AddChild(navigationLink2D);
|
||
// }
|
||
|
||
return Task.FromResult(true);
|
||
}
|
||
|
||
public async Task<RoomPlacementData?> CalculateNewRoomPlacementData(RandomNumberGenerator randomNumberGenerator,
|
||
Room? parentRoomNode,
|
||
RoomNodeData newRoomNodeData)
|
||
{
|
||
if (newRoomNodeData.RoomTemplateSet == null || newRoomNodeData.RoomTemplateSet.Length == 0)
|
||
{
|
||
return null;
|
||
}
|
||
|
||
if (parentRoomNode == null)
|
||
{
|
||
return null;
|
||
}
|
||
|
||
var roomResArray = RoomFactory.RoomTemplateSetToRoomRes(newRoomNodeData.RoomTemplateSet);
|
||
if (roomResArray.Length == 0)
|
||
{
|
||
return null;
|
||
}
|
||
|
||
var roomSlots = parentRoomNode.RoomSlots;
|
||
if (roomSlots == null || roomSlots.Length == 0)
|
||
{
|
||
return null;
|
||
}
|
||
|
||
//Saves all data in the room template that matches the parent room.
|
||
//保存房间模板内所有与父房间匹配的数据。
|
||
var usableRoomPlacementData = new List<RoomPlacementData>();
|
||
foreach (var roomRes in roomResArray)
|
||
{
|
||
var newRoom = RoomFactory.CreateRoom(roomRes, newRoomNodeData.EnterRoomEventHandlerId,
|
||
newRoomNodeData.ExitRoomEventHandlerId);
|
||
if (newRoom == null)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
//Create a room, try to use the room slot to match the existing room slot.
|
||
//创建了一个房间,尝试使用房间的槽与现有的房间槽匹配。
|
||
if (!IsMatch(parentRoomNode, newRoom, out var mainRoomSlot, out var newRoomSlot).Result)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
if (mainRoomSlot == null || newRoomSlot == null)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
var position = await CalculatedPosition(parentRoomNode, newRoom, mainRoomSlot, newRoomSlot, false);
|
||
if (position == null) continue;
|
||
var roomPlacementData = new RoomPlacementData
|
||
{
|
||
Room = newRoom,
|
||
ParentRoom = parentRoomNode,
|
||
Position = position,
|
||
ParentRoomSlot = mainRoomSlot,
|
||
NewRoomSlot = newRoomSlot
|
||
};
|
||
usableRoomPlacementData.Add(roomPlacementData);
|
||
}
|
||
|
||
if (usableRoomPlacementData.Count == 0)
|
||
{
|
||
return null;
|
||
}
|
||
else
|
||
{
|
||
var index = randomNumberGenerator.Randi() % usableRoomPlacementData.Count;
|
||
var roomPlacementData = usableRoomPlacementData[(int)index];
|
||
//Be sure to mark its slot as a match when you use it.
|
||
//一定要在使用时,将其插槽标记为匹配。
|
||
if (roomPlacementData.ParentRoomSlot != null)
|
||
{
|
||
roomPlacementData.ParentRoomSlot.Matched = true;
|
||
}
|
||
|
||
if (roomPlacementData.NewRoomSlot != null)
|
||
{
|
||
roomPlacementData.NewRoomSlot.Matched = true;
|
||
}
|
||
|
||
return roomPlacementData;
|
||
}
|
||
}
|
||
|
||
public Task<RoomPlacementData?> CalculatePlacementDataForStartingRoom(RandomNumberGenerator randomNumberGenerator,
|
||
RoomNodeData startRoomNodeData)
|
||
{
|
||
if (startRoomNodeData.RoomTemplateSet == null || startRoomNodeData.RoomTemplateSet.Length == 0)
|
||
{
|
||
return Task.FromResult<RoomPlacementData?>(null);
|
||
}
|
||
|
||
var roomResArray = RoomFactory.RoomTemplateSetToRoomRes(startRoomNodeData.RoomTemplateSet);
|
||
if (roomResArray.Length == 0)
|
||
{
|
||
return Task.FromResult<RoomPlacementData?>(null);
|
||
}
|
||
|
||
var index = randomNumberGenerator.Randi() % roomResArray.Length;
|
||
var roomPlacementData = new RoomPlacementData
|
||
{
|
||
Room = RoomFactory.CreateRoom(roomResArray[index], startRoomNodeData.EnterRoomEventHandlerId,
|
||
startRoomNodeData.ExitRoomEventHandlerId),
|
||
Position = Vector2.Zero
|
||
};
|
||
return Task.FromResult<RoomPlacementData?>(roomPlacementData);
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// <para>if it matches</para>
|
||
/// <para>是否匹配</para>
|
||
/// </summary>
|
||
/// <param name="mainRoom"></param>
|
||
/// <param name="newRoom"></param>
|
||
/// <param name="outMainRoomSlot"></param>
|
||
/// <param name="outNewRoomSlot"></param>
|
||
/// <returns></returns>
|
||
public Task<bool> IsMatch(Room? mainRoom, Room newRoom, out RoomSlot? outMainRoomSlot, out RoomSlot? outNewRoomSlot)
|
||
{
|
||
if (mainRoom == null)
|
||
{
|
||
outNewRoomSlot = null;
|
||
outMainRoomSlot = null;
|
||
return Task.FromResult(false);
|
||
}
|
||
|
||
var roomSlots = mainRoom.RoomSlots;
|
||
if (roomSlots == null)
|
||
{
|
||
outNewRoomSlot = null;
|
||
outMainRoomSlot = null;
|
||
return Task.FromResult(false);
|
||
}
|
||
|
||
var newRoomSlots = newRoom.RoomSlots;
|
||
if (newRoomSlots == null)
|
||
{
|
||
outNewRoomSlot = null;
|
||
outMainRoomSlot = null;
|
||
return Task.FromResult(false);
|
||
}
|
||
|
||
foreach (var mainRoomSlot in roomSlots)
|
||
{
|
||
if (mainRoomSlot == null || mainRoomSlot.Matched)
|
||
{
|
||
//If it's already a match, it's no longer a match
|
||
//如果已经匹配过了,就不再匹配
|
||
continue;
|
||
}
|
||
|
||
foreach (var newRoomSlot in newRoomSlots)
|
||
{
|
||
if (newRoomSlot == null)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
if (newRoomSlot.Matched)
|
||
{
|
||
//If it's already a match, it's no longer a match
|
||
//如果已经匹配过了,就不再匹配
|
||
continue;
|
||
}
|
||
|
||
if (mainRoomSlot.IsHorizontal != newRoomSlot.IsHorizontal)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
if (mainRoomSlot.Length != newRoomSlot.Length)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
var distanceToMidpointOfRoom = mainRoomSlot.DistanceToMidpointOfRoom;
|
||
var newDistanceToMidpointOfRoom = newRoomSlot.DistanceToMidpointOfRoom;
|
||
if (distanceToMidpointOfRoom == null || newDistanceToMidpointOfRoom == null)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
if (distanceToMidpointOfRoom[0] == newDistanceToMidpointOfRoom[0] &&
|
||
distanceToMidpointOfRoom[1] == newDistanceToMidpointOfRoom[1])
|
||
{
|
||
continue;
|
||
}
|
||
|
||
outMainRoomSlot = mainRoomSlot;
|
||
outNewRoomSlot = newRoomSlot;
|
||
return Task.FromResult(true);
|
||
}
|
||
}
|
||
|
||
outNewRoomSlot = null;
|
||
outMainRoomSlot = null;
|
||
return Task.FromResult(false);
|
||
}
|
||
|
||
/// <summary>
|
||
/// <para>Calculate room position</para>
|
||
/// <para>计算房间位置</para>
|
||
/// </summary>
|
||
/// <param name="mainRoom">
|
||
///<para>Main room</para>
|
||
///<para>主房间</para>
|
||
/// </param>
|
||
/// <param name="newRoom">
|
||
///<para>New room</para>
|
||
///<para>新房间</para>
|
||
/// </param>
|
||
/// <param name="mainRoomSlot">
|
||
///<para>Main room slot</para>
|
||
///<para>主房间插槽</para>
|
||
/// </param>
|
||
/// <param name="newRoomSlot">
|
||
///<para>New room slot</para>
|
||
///<para>新房间插槽</para>
|
||
/// </param>
|
||
/// <param name="roomSlotOverlap">
|
||
///<para>Whether room slots allow overlays</para>
|
||
///<para>房间插槽是否允许覆盖</para>
|
||
/// </param>
|
||
/// <returns></returns>
|
||
private async Task<Vector2?> CalculatedPosition(Room mainRoom, Room newRoom, RoomSlot? mainRoomSlot,
|
||
RoomSlot? newRoomSlot, bool roomSlotOverlap)
|
||
{
|
||
if (mainRoom.RootNode == null || newRoom.RootNode == null || newRoom.TileMap == null ||
|
||
mainRoom.TileMap == null ||
|
||
newRoom.TileMap == null || mainRoomSlot == null ||
|
||
newRoomSlot == null)
|
||
{
|
||
return null;
|
||
}
|
||
|
||
//Main room slot location description
|
||
//主房间槽位置描述
|
||
var mainOrientationDescribe = mainRoomSlot.DistanceToMidpointOfRoom;
|
||
//New room slot location description
|
||
//新房间槽位置描述
|
||
var newOrientationDescribe = newRoomSlot.DistanceToMidpointOfRoom;
|
||
if (mainOrientationDescribe == null || newOrientationDescribe == null)
|
||
{
|
||
//If the room slot is described as null, null is returned
|
||
//若房间槽描述为null,那么返回null
|
||
return null;
|
||
}
|
||
|
||
var mainRoomSlotPosition = mainRoom.TileMap.MapToLocal(mainRoomSlot.StartPosition);
|
||
var newRoomSlotPosition = newRoom.TileMap.MapToLocal(newRoomSlot.StartPosition);
|
||
//Get the vector from the new room slot to the main room slot
|
||
//得到从新房间槽位到主房间槽位的向量
|
||
var newToMain = mainRoomSlotPosition - newRoomSlotPosition;
|
||
var result = newToMain + mainRoom.RootNode.Position;
|
||
if (!roomSlotOverlap)
|
||
{
|
||
//如果不允许房间槽位重叠
|
||
//If room slot overlap is not allowed
|
||
if (mainRoomSlot.IsHorizontal)
|
||
{
|
||
//Horizontal slot, offset in the Y direction.
|
||
//水平方向槽,向Y方向偏移。
|
||
if (newOrientationDescribe[1] == CoordinateUtils.OrientationDescribe.Up)
|
||
{
|
||
result.Y += Config.CellSize;
|
||
}
|
||
else
|
||
{
|
||
result.Y -= Config.CellSize;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
//Vertical slot, offset in the X direction.
|
||
//垂直方向槽向X方向偏移。
|
||
if (newOrientationDescribe[0] == CoordinateUtils.OrientationDescribe.Right)
|
||
{
|
||
result.X -= Config.CellSize;
|
||
}
|
||
else
|
||
{
|
||
result.X += Config.CellSize;
|
||
}
|
||
}
|
||
}
|
||
|
||
//Do calculations overlap with other rooms?
|
||
//计算结果是否与其他房间重叠?
|
||
if (newRoom.RoomCollisionShape2D != null && _measuringArea2D != null && _measuringCollisionShape2D != null)
|
||
{
|
||
//Ignore yourself when detecting room overlap
|
||
//检测房间重叠时应忽略自身
|
||
_selfArea2D = newRoom.Area2D;
|
||
_measuringArea2D.Position = result;
|
||
_measuringCollisionShape2D.Shape = newRoom.RoomCollisionShape2D.Shape;
|
||
//Calculate the offset of the shape.
|
||
//计算形状的偏移量。
|
||
_measuringCollisionShape2D.Position = newRoom.RoomCollisionShape2D.Shape.GetRect().Size / 2;
|
||
await Task.Delay(TimeSpan.FromMilliseconds(50));
|
||
if (_overlapQuantity > 0)
|
||
{
|
||
return null;
|
||
}
|
||
}
|
||
|
||
|
||
return result;
|
||
}
|
||
} |