522 lines
22 KiB
C#
522 lines
22 KiB
C#
//$ Copyright 2015-22, Code Respawn Technologies Pvt Ltd - All Rights Reserved $//
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using System.Linq;
|
|
using DungeonArchitect.Utils;
|
|
using DungeonArchitect.Flow.Domains;
|
|
using DungeonArchitect.Flow.Exec;
|
|
using DungeonArchitect.Flow.Domains.Layout;
|
|
using DungeonArchitect.Flow.Domains.Tilemap;
|
|
using DungeonArchitect.Flow.Domains.Tilemap.Tasks;
|
|
using DungeonArchitect.Flow.Impl.GridFlow;
|
|
using DungeonArchitect.Flow.Items;
|
|
|
|
namespace DungeonArchitect.Builders.GridFlow
|
|
{
|
|
public class GridFlowDungeonBuilder : DungeonBuilder
|
|
{
|
|
private GridFlowDungeonConfig gridFlowConfig;
|
|
private GridFlowDungeonModel gridFlowModel;
|
|
protected FlowExecNodeOutputRegistry execNodeOutputRegistry = null;
|
|
public FlowExecNodeOutputRegistry ExecNodeOutputRegistry
|
|
{
|
|
get
|
|
{
|
|
return execNodeOutputRegistry;
|
|
}
|
|
}
|
|
|
|
public override void BuildDungeon(DungeonConfig config, DungeonModel model)
|
|
{
|
|
gridFlowConfig = config as GridFlowDungeonConfig;
|
|
gridFlowModel = model as GridFlowDungeonModel;
|
|
|
|
if (gridFlowConfig.flowAsset == null)
|
|
{
|
|
Debug.LogError("Missing grid flow asset");
|
|
return;
|
|
}
|
|
|
|
base.BuildDungeon(config, model);
|
|
|
|
GenerateLevelLayout();
|
|
|
|
var minimap = GetComponent<GridFlowMinimap>();
|
|
if (minimap != null && minimap.initMode == GridFlowMinimapInitMode.OnDungeonRebuild)
|
|
{
|
|
minimap.Initialize();
|
|
}
|
|
}
|
|
|
|
public override void EmitMarkers()
|
|
{
|
|
base.EmitMarkers();
|
|
|
|
EmitLevelMarkers();
|
|
|
|
ProcessMarkerOverrideVolumes();
|
|
}
|
|
|
|
void GenerateLevelLayout()
|
|
{
|
|
if (gridFlowConfig == null || gridFlowModel == null || gridFlowConfig.flowAsset == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
gridFlowModel.Reset();
|
|
|
|
var execGraph = gridFlowConfig.flowAsset.execGraph;
|
|
var random = new System.Random((int)gridFlowConfig.Seed);
|
|
var domainExtensions = new FlowDomainExtensions();
|
|
|
|
FlowExecutor executor = new FlowExecutor();
|
|
if (!executor.Execute(execGraph, random, domainExtensions,gridFlowConfig.numGraphRetries, out execNodeOutputRegistry))
|
|
{
|
|
Debug.LogError("Failed to generate level layout. Please check your grid flow graph. Alternatively, increase the 'Num Graph Retries' parameter in the config");
|
|
}
|
|
else
|
|
{
|
|
var resultNode = execGraph.resultNode;
|
|
var execState = execNodeOutputRegistry.Get(resultNode.Id);
|
|
|
|
var layoutGraph = execState.State.GetState<FlowLayoutGraph>();
|
|
var tilemap = execState.State.GetState<FlowTilemap>();
|
|
|
|
var tilemapState = execState.State.GetState<GridFlowTilemapState>();
|
|
if (tilemapState != null)
|
|
{
|
|
gridFlowModel.wallsAsEdges = (tilemapState.WallGenerationMethod == TilemapFlowNodeWallGenerationMethod.WallAsEdges);
|
|
}
|
|
|
|
if (layoutGraph == null || tilemap == null)
|
|
{
|
|
Debug.Log("Failed to generate grid flow tilemap");
|
|
return;
|
|
}
|
|
|
|
gridFlowModel.Initialize(layoutGraph, tilemap, gridFlowConfig.gridSize);
|
|
}
|
|
}
|
|
|
|
bool IsCellOfType(FlowTilemap tilemap, int x, int y, FlowTilemapCellType[] types)
|
|
{
|
|
var cell = tilemap.Cells.GetCell(x, y);
|
|
if (cell == null) return false;
|
|
return types.Contains(cell.CellType);
|
|
}
|
|
|
|
class LayoutGraphLookup
|
|
{
|
|
private Dictionary<IntVector2, FlowLayoutGraphNode> nodesByCoord = new Dictionary<IntVector2, FlowLayoutGraphNode>();
|
|
public LayoutGraphLookup(FlowLayoutGraph layoutGraph)
|
|
{
|
|
foreach (var node in layoutGraph.Nodes)
|
|
{
|
|
if (node.active)
|
|
{
|
|
var coord = new IntVector2(
|
|
Mathf.RoundToInt(node.coord.x),
|
|
Mathf.RoundToInt(node.coord.y));
|
|
nodesByCoord[coord] = node;
|
|
}
|
|
}
|
|
}
|
|
|
|
public FlowLayoutGraphNode GetNode(IntVector2 coord)
|
|
{
|
|
return nodesByCoord.ContainsKey(coord) ? nodesByCoord[coord] : null;
|
|
}
|
|
}
|
|
|
|
GridFlowLayoutNodeRoomType GetCellRoomType(FlowTilemap tilemap, int x, int y, LayoutGraphLookup layoutGraphLookup)
|
|
{
|
|
var cell = tilemap.Cells.GetCell(x, y);
|
|
if (cell == null)
|
|
{
|
|
return GridFlowLayoutNodeRoomType.Unknown;
|
|
}
|
|
|
|
var layoutNode = layoutGraphLookup.GetNode(cell.NodeCoord);
|
|
if (layoutNode == null)
|
|
{
|
|
return GridFlowLayoutNodeRoomType.Unknown;
|
|
}
|
|
|
|
var domainData = layoutNode.GetDomainData<GridFlowTilemapDomainData>();
|
|
return domainData.RoomType;
|
|
}
|
|
|
|
Quaternion GetBaseTransform(FlowTilemap tilemap, int x, int y)
|
|
{
|
|
var cellTypesToTransform = new FlowTilemapCellType[]
|
|
{
|
|
FlowTilemapCellType.Wall,
|
|
FlowTilemapCellType.Door
|
|
};
|
|
|
|
var cell = tilemap.Cells[x, y];
|
|
if (!cellTypesToTransform.Contains(cell.CellType))
|
|
{
|
|
return Quaternion.identity;
|
|
}
|
|
|
|
var validL = IsCellOfType(tilemap, x - 1, y, cellTypesToTransform);
|
|
var validR = IsCellOfType(tilemap, x + 1, y, cellTypesToTransform);
|
|
var validB = IsCellOfType(tilemap, x, y - 1, cellTypesToTransform);
|
|
var validT = IsCellOfType(tilemap, x, y + 1, cellTypesToTransform);
|
|
|
|
var angleY = 0;
|
|
if (validL && validR)
|
|
{
|
|
angleY = validT ? 180 : 0;
|
|
}
|
|
else if (validT && validB)
|
|
{
|
|
angleY = validR ? 270 : 90;
|
|
}
|
|
else if (validL && validT) angleY = 180;
|
|
else if (validL && validB) angleY = 90;
|
|
else if (validR && validT) angleY = 270;
|
|
else if (validR && validB) angleY = 0;
|
|
|
|
return Quaternion.Euler(0, angleY, 0);
|
|
}
|
|
|
|
string GetEdgeMarkerName(FlowTilemapEdgeType edgeType)
|
|
{
|
|
if (edgeType == FlowTilemapEdgeType.Wall) return GridFlowDungeonMarkerNames.Wall;
|
|
else if (edgeType == FlowTilemapEdgeType.Fence) return GridFlowDungeonMarkerNames.Fence;
|
|
else if (edgeType == FlowTilemapEdgeType.Door)
|
|
{
|
|
return GridFlowDungeonMarkerNames.Door;
|
|
}
|
|
else
|
|
{
|
|
return "[Empty]";
|
|
}
|
|
}
|
|
|
|
bool CreateLockItemMetadata(FlowItem item, ref string doorMarker, out FlowItemMetadata lockItemData)
|
|
{
|
|
if (item != null && item.type == FlowGraphItemType.Lock)
|
|
{
|
|
// Turn this into a locked door (lock marker will be spawned instead of a door (or a one way door)
|
|
doorMarker = item.markerName;
|
|
|
|
lockItemData = new FlowItemMetadata();
|
|
lockItemData.itemId = item.itemId;
|
|
lockItemData.itemType = item.type;
|
|
lockItemData.referencedItems = new List<DungeonUID>(item.referencedItemIds).ToArray();
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
lockItemData = null;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool ChunkSupportsWalls(GridFlowLayoutNodeRoomType type)
|
|
{
|
|
return type == GridFlowLayoutNodeRoomType.Room || type == GridFlowLayoutNodeRoomType.Corridor;
|
|
}
|
|
|
|
|
|
void EmitLevelMarkers()
|
|
{
|
|
if (gridFlowConfig == null || gridFlowModel == null || gridFlowModel.LayoutGraph == null)
|
|
{
|
|
Debug.LogError("GridFlowBuilder: Invalid state");
|
|
return;
|
|
}
|
|
|
|
var items = gridFlowModel.LayoutGraph.GetAllItems();
|
|
var itemMap = new Dictionary<DungeonUID, FlowItem>();
|
|
foreach (var item in items)
|
|
{
|
|
itemMap[item.itemId] = item;
|
|
}
|
|
|
|
var tilemap = gridFlowModel.Tilemap;
|
|
if (tilemap == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var basePosition = transform.position;
|
|
var gridSize = gridFlowConfig.gridSize;
|
|
// Emit the cell markers
|
|
for (int x = 0; x < tilemap.Width; x++)
|
|
{
|
|
for (int y = 0; y < tilemap.Height; y++)
|
|
{
|
|
var position = basePosition + Vector3.Scale(new Vector3(x + 0.5f, 0, y + 0.5f), gridSize);
|
|
var baseRotation = GetBaseTransform(tilemap, x, y);
|
|
var markerTransform = Matrix4x4.TRS(position, baseRotation, Vector3.one);
|
|
var cell = tilemap.Cells[x, y];
|
|
int cellId = tilemap.Width * y + x;
|
|
|
|
if (cell.Item != DungeonUID.Empty && itemMap.ContainsKey(cell.Item))
|
|
{
|
|
var item = itemMap[cell.Item];
|
|
if (item.markerName != null && item.markerName.Length > 0 && item.type != FlowGraphItemType.Lock)
|
|
{
|
|
// Emit this item
|
|
var itemData = new FlowItemMetadata();
|
|
itemData.itemId = item.itemId;
|
|
itemData.itemType = item.type;
|
|
itemData.referencedItems = new List<DungeonUID>(item.referencedItemIds).ToArray();
|
|
|
|
EmitMarker(item.markerName, markerTransform, new IntVector(x, 0, y), cellId, itemData);
|
|
}
|
|
}
|
|
|
|
bool removeElevationMarker = false;
|
|
if (cell.Overlay != null && cell.Overlay.markerName != null)
|
|
{
|
|
var heightOffset = 0.0f;
|
|
if (cell.Overlay.mergeConfig != null)
|
|
{
|
|
heightOffset += cell.LayoutCell
|
|
? cell.Overlay.mergeConfig.markerHeightOffsetForLayoutTiles
|
|
: cell.Overlay.mergeConfig.markerHeightOffsetForNonLayoutTiles;
|
|
}
|
|
|
|
var height = cell.Height;
|
|
var overlayPosition = basePosition + Vector3.Scale(new Vector3(x + 0.5f, height + heightOffset, y + 0.5f), gridSize);
|
|
var overlayMarkerTransform = Matrix4x4.TRS(overlayPosition, Quaternion.identity, Vector3.one);
|
|
EmitMarker(cell.Overlay.markerName, overlayMarkerTransform, new IntVector(x, 0, y), cellId);
|
|
|
|
if (cell.Overlay.mergeConfig != null)
|
|
{
|
|
removeElevationMarker = cell.Overlay.mergeConfig.removeElevationMarker;
|
|
}
|
|
}
|
|
|
|
switch (cell.CellType)
|
|
{
|
|
case FlowTilemapCellType.Floor:
|
|
EmitMarker(GridFlowDungeonMarkerNames.Ground, markerTransform, new IntVector(x, 0, y), cellId);
|
|
break;
|
|
|
|
case FlowTilemapCellType.Wall:
|
|
EmitMarker(GridFlowDungeonMarkerNames.Wall, markerTransform, new IntVector(x, 0, y), cellId);
|
|
EmitMarker(GridFlowDungeonMarkerNames.Ground, markerTransform, new IntVector(x, 0, y), cellId);
|
|
break;
|
|
|
|
case FlowTilemapCellType.Door:
|
|
{
|
|
var doorMarker = GridFlowDungeonMarkerNames.Door;
|
|
var doorData = cell.Userdata as FlowTilemapCellDoorInfo;
|
|
if (doorData != null && doorData.oneWay)
|
|
{
|
|
// One way door
|
|
doorMarker = GridFlowDungeonMarkerNames.DoorOneWay;
|
|
|
|
// Apply the correct one-way direction
|
|
var flipDirection = (doorData.nodeA.x > doorData.nodeB.x) || (doorData.nodeA.y > doorData.nodeB.y);
|
|
if (!flipDirection)
|
|
{
|
|
var doorRotation = baseRotation * Quaternion.Euler(0, 180, 0);
|
|
markerTransform = Matrix4x4.TRS(position, doorRotation, Vector3.one);
|
|
}
|
|
}
|
|
|
|
FlowItemMetadata lockItemData = null;
|
|
if (cell.Item != DungeonUID.Empty && itemMap.ContainsKey(cell.Item))
|
|
{
|
|
var item = itemMap[cell.Item];
|
|
CreateLockItemMetadata(item, ref doorMarker, out lockItemData);
|
|
}
|
|
|
|
EmitMarker(doorMarker, markerTransform, new IntVector(x, 0, y), cellId, lockItemData);
|
|
EmitMarker(GridFlowDungeonMarkerNames.Ground, markerTransform, new IntVector(x, 0, y), cellId);
|
|
}
|
|
break;
|
|
|
|
case FlowTilemapCellType.Custom:
|
|
if (cell.CustomCellInfo != null && !removeElevationMarker)
|
|
{
|
|
var markerName = cell.CustomCellInfo.name;
|
|
var height = cell.Height;
|
|
var customPosition = basePosition + Vector3.Scale(new Vector3(x + 0.5f, height, y + 0.5f), gridSize);
|
|
var customMarkerTransform = Matrix4x4.TRS(customPosition, Quaternion.identity, Vector3.one);
|
|
|
|
EmitMarker(markerName, customMarkerTransform, new IntVector(x, 0, y), cellId);
|
|
}
|
|
break;
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
// Emit the edge markers
|
|
{
|
|
|
|
var walkableCellTypes = new FlowTilemapCellType[]
|
|
{
|
|
FlowTilemapCellType.Floor,
|
|
FlowTilemapCellType.Wall,
|
|
FlowTilemapCellType.Door
|
|
};
|
|
|
|
var wallSeparators = new HashSet<IntVector2>();
|
|
var fenceSeparators = new HashSet<IntVector2>();
|
|
|
|
var layoutGraphLookup = new LayoutGraphLookup(gridFlowModel.LayoutGraph);
|
|
|
|
foreach (var edge in tilemap.Edges)
|
|
{
|
|
var coord = edge.EdgeCoord;
|
|
var isGroundTile = IsCellOfType(tilemap, coord.x, coord.y, walkableCellTypes);
|
|
var roomType = GetCellRoomType(tilemap, coord.x, coord.y, layoutGraphLookup);
|
|
|
|
if (edge.EdgeType == FlowTilemapEdgeType.Empty) continue;
|
|
|
|
Vector3 position;
|
|
float angle;
|
|
if (edge.HorizontalEdge)
|
|
{
|
|
position = basePosition + Vector3.Scale(new Vector3(coord.x + 0.5f, 0, coord.y), gridSize);
|
|
angle = isGroundTile ? 0 : 180;
|
|
if (isGroundTile && edge.EdgeType == FlowTilemapEdgeType.Wall)
|
|
{
|
|
var roomTypeBelow = GetCellRoomType(tilemap, coord.x, coord.y - 1, layoutGraphLookup);
|
|
if (!ChunkSupportsWalls(roomType) && ChunkSupportsWalls(roomTypeBelow))
|
|
{
|
|
angle += 180;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
position = basePosition + Vector3.Scale(new Vector3(coord.x, 0, coord.y + 0.5f), gridSize);
|
|
angle = isGroundTile ? 90 : 270;
|
|
if (isGroundTile && edge.EdgeType == FlowTilemapEdgeType.Wall)
|
|
{
|
|
var roomTypeLeft = GetCellRoomType(tilemap, coord.x - 1, coord.y, layoutGraphLookup);
|
|
if (!ChunkSupportsWalls(roomType) && ChunkSupportsWalls(roomTypeLeft))
|
|
{
|
|
angle += 180;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (gridFlowConfig.flipEdgeWalls)
|
|
{
|
|
if (edge.EdgeType == FlowTilemapEdgeType.Wall || edge.EdgeType == FlowTilemapEdgeType.Fence)
|
|
{
|
|
angle += 180;
|
|
}
|
|
}
|
|
|
|
var baseRotation = Quaternion.Euler(0, angle, 0);
|
|
var markerTransform = Matrix4x4.TRS(position, baseRotation, Vector3.one);
|
|
|
|
bool supportedMarker = true;
|
|
var markerName = "";
|
|
FlowItemMetadata itemMetadata = null;
|
|
if (edge.EdgeType == FlowTilemapEdgeType.Wall)
|
|
{
|
|
markerName = GridFlowDungeonMarkerNames.Wall;
|
|
|
|
wallSeparators.Add(coord);
|
|
if (edge.HorizontalEdge)
|
|
{
|
|
wallSeparators.Add(coord + new IntVector2(1, 0));
|
|
}
|
|
else
|
|
{
|
|
wallSeparators.Add(coord + new IntVector2(0, 1));
|
|
}
|
|
}
|
|
else if (edge.EdgeType == FlowTilemapEdgeType.Fence)
|
|
{
|
|
markerName = GridFlowDungeonMarkerNames.Fence;
|
|
|
|
fenceSeparators.Add(coord);
|
|
if (edge.HorizontalEdge)
|
|
{
|
|
fenceSeparators.Add(coord + new IntVector2(1, 0));
|
|
}
|
|
else
|
|
{
|
|
fenceSeparators.Add(coord + new IntVector2(0, 1));
|
|
}
|
|
}
|
|
else if (edge.EdgeType == FlowTilemapEdgeType.Door)
|
|
{
|
|
markerName = GridFlowDungeonMarkerNames.Door;
|
|
var doorData = edge.Userdata as FlowTilemapCellDoorInfo;
|
|
if (doorData != null && doorData.oneWay)
|
|
{
|
|
// One way door
|
|
markerName = GridFlowDungeonMarkerNames.DoorOneWay;
|
|
|
|
// Apply the correct one-way direction
|
|
var flipDirection = (doorData.nodeA.x > doorData.nodeB.x) || (doorData.nodeA.y > doorData.nodeB.y);
|
|
if (!flipDirection)
|
|
{
|
|
var doorRotation = baseRotation * Quaternion.Euler(0, 180, 0);
|
|
markerTransform = Matrix4x4.TRS(position, doorRotation, Vector3.one);
|
|
}
|
|
}
|
|
|
|
if (edge.Item != DungeonUID.Empty && itemMap.ContainsKey(edge.Item))
|
|
{
|
|
var item = itemMap[edge.Item];
|
|
CreateLockItemMetadata(item, ref markerName, out itemMetadata);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
supportedMarker = false;
|
|
}
|
|
|
|
if (supportedMarker)
|
|
{
|
|
EmitMarker(markerName, markerTransform, new IntVector(coord.x, 0, coord.y), 0, itemMetadata);
|
|
}
|
|
}
|
|
|
|
// Emit wall separator markers
|
|
foreach (var gridCoord in wallSeparators)
|
|
{
|
|
var position = basePosition + Vector3.Scale(new Vector3(gridCoord.x, 0, gridCoord.y), gridSize);
|
|
var markerTransform = Matrix4x4.TRS(position, Quaternion.identity, Vector3.one);
|
|
EmitMarker(GridFlowDungeonMarkerNames.WallSeparator, markerTransform, new IntVector(gridCoord.x, 0, gridCoord.y), 0);
|
|
}
|
|
|
|
// Emit fence separator markers
|
|
foreach (var gridCoord in fenceSeparators)
|
|
{
|
|
if (wallSeparators.Contains(gridCoord)) continue;
|
|
|
|
var position = basePosition + Vector3.Scale(new Vector3(gridCoord.x, 0, gridCoord.y), gridSize);
|
|
var markerTransform = Matrix4x4.TRS(position, Quaternion.identity, Vector3.one);
|
|
EmitMarker(GridFlowDungeonMarkerNames.FenceSeparator, markerTransform, new IntVector(gridCoord.x, 0, gridCoord.y), 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void DebugDraw()
|
|
{
|
|
|
|
}
|
|
}
|
|
|
|
public static class GridFlowDungeonMarkerNames
|
|
{
|
|
public static readonly string Ground = "Ground";
|
|
public static readonly string Wall = "Wall";
|
|
public static readonly string WallSeparator = "WallSeparator";
|
|
public static readonly string Fence = "Fence";
|
|
public static readonly string FenceSeparator = "FenceSeparator";
|
|
public static readonly string Door = "Door";
|
|
public static readonly string DoorOneWay = "DoorOneWay";
|
|
|
|
}
|
|
|
|
}
|