using DunGen.Tags;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Serialization;
namespace DunGen.Graph
{
///
/// A graph representing the flow of a dungeon
///
[Serializable]
[CreateAssetMenu(fileName = "New Dungeon", menuName = "DunGen/Dungeon Flow", order = 700)]
public class DungeonFlow : ScriptableObject, ISerializationCallbackReceiver
{
public const int FileVersion = 1;
#region Nested Types
[Serializable]
public sealed class GlobalPropSettings
{
public int ID;
public IntRange Count;
public GlobalPropSettings()
{
ID = 0;
Count = new IntRange(0, 1);
}
public GlobalPropSettings(int id, IntRange count)
{
ID = id;
Count = count;
}
}
///
/// Defines how tile connection rules are applied.
///
public enum TagConnectionMode
{
///
/// Tiles will only connect if they have tags that appear in the TileConnectionTags list
///
Accept,
///
/// Tiles will always connect unless they have tags that appear in the TileConnectionTags list
///
Reject,
}
///
/// Defines how tags are used to determine which branch tip tiles to prune
///
public enum BranchPruneMode
{
///
/// Removes tiles at the end of a branch when they have any of the specified tags
///
AnyTagPresent,
///
/// Removes tiles at the end of a branch when they don't have any of the specified tags
///
AllTagsMissing,
}
#endregion
#region Legacy Properties
[SerializeField]
[FormerlySerializedAs("GlobalPropGroupIDs")]
private List globalPropGroupID_obsolete = new List();
[SerializeField]
[FormerlySerializedAs("GlobalPropRanges")]
private List globalPropRanges_obsolete = new List();
#endregion
///
/// The minimum and maximum length of the dungeon
///
public IntRange Length = new IntRange(5, 10);
///
/// Determines how the number of branches from the main path is calculated
///
public BranchMode BranchMode = BranchMode.Local;
///
/// The number of branches to appear across the entire dungeon
/// Only used if is set to
///
public IntRange BranchCount = new IntRange(1, 5);
///
/// Information about which (and how many) global props should appear throughout the dungeon
///
public List GlobalProps = new List();
///
/// The asset that handles all of the keys that this dungeon needs to know about
///
public KeyManager KeyManager = null;
///
/// The percentage chance of two unconnected but overlapping doorways being connected (0-1)
///
[Range(0f, 1f)]
public float DoorwayConnectionChance = 0f;
///
/// If true, only doorways belonging to tiles on the same section of the dungeon can be connected
/// This will prevent some unexpected shortcuts from opening up through the dungeon
///
public bool RestrictConnectionToSameSection = false;
///
/// Simple rules for injecting special tiles into the dungeon generation process
///
public List TileInjectionRules = new List();
///
/// Defined how tile connection rules are applied see
///
public TagConnectionMode TileTagConnectionMode;
///
/// A list of tag pairs that define how tiles are allowed to connect. If empty, all
/// tiles can connect to oneanother, otherwise the tiles must have a pair of
/// matching tags from this list (omni-directional)
///
public List TileConnectionTags = new List();
///
/// Determines how tags are used to prune tiles at the end of branches
///
public BranchPruneMode BranchTagPruneMode = BranchPruneMode.AllTagsMissing;
///
/// A list of tags used to decide if a tile at the end of a branch should be deleted
///
public List BranchPruneTags = new List();
///
/// Global settings for straightening the dungeon path. Can be overridden in Archetypes and on Nodes in the flow graph
///
public PathStraighteningSettings GlobalStraighteningSettings = new PathStraighteningSettings();
public List Nodes = new List();
public List Lines = new List();
[SerializeField]
private int currentFileVersion;
///
/// Creates the default graph
///
public void Reset()
{
var emptyTileSet = new TileSet[0];
var emptyArchetype = new DungeonArchetype[0];
var builder = new DungeonFlowBuilder(this)
.AddNode(emptyTileSet, "Start")
.AddLine(emptyArchetype, 1.0f)
.AddNode(emptyTileSet, "Goal");
builder.Complete();
}
public GraphLine GetLineAtDepth(float normalizedDepth)
{
normalizedDepth = Mathf.Clamp(normalizedDepth, 0, 1);
if (normalizedDepth == 0)
return Lines[0];
else if (normalizedDepth == 1)
return Lines[Lines.Count - 1];
foreach (var line in Lines)
if (normalizedDepth >= line.Position && normalizedDepth < line.Position + line.Length)
return line;
Debug.LogError("GetLineAtDepth was unable to find a line at depth " + normalizedDepth + ". This shouldn't happen.");
return null;
}
public DungeonArchetype[] GetUsedArchetypes()
{
return Lines.SelectMany(x => x.DungeonArchetypes).ToArray();
}
public TileSet[] GetUsedTileSets()
{
List tileSets = new List();
foreach (var node in Nodes)
tileSets.AddRange(node.TileSets);
foreach(var line in Lines)
foreach (var archetype in line.DungeonArchetypes)
{
tileSets.AddRange(archetype.TileSets);
tileSets.AddRange(archetype.BranchCapTileSets);
}
return tileSets.ToArray();
}
public bool ShouldPruneTileWithTags(TagContainer tileTags)
{
switch (BranchTagPruneMode)
{
case BranchPruneMode.AnyTagPresent:
return tileTags.HasAnyTag(BranchPruneTags.ToArray());
case BranchPruneMode.AllTagsMissing:
return !tileTags.HasAnyTag(BranchPruneTags.ToArray());
default:
throw new NotImplementedException(string.Format("BranchPruneMode {0} is not implemented", BranchTagPruneMode));
}
}
public void OnBeforeSerialize()
{
currentFileVersion = FileVersion;
}
public void OnAfterDeserialize()
{
// Convert to new format for Global Props
if(currentFileVersion < 1)
{
for (int i = 0; i < globalPropGroupID_obsolete.Count; i++)
{
int id = globalPropGroupID_obsolete[i];
var count = globalPropRanges_obsolete[i];
GlobalProps.Add(new GlobalPropSettings(id, count));
}
globalPropGroupID_obsolete.Clear();
globalPropRanges_obsolete.Clear();
}
}
///
/// Checks the connection rules (if any) to see if two tiles are allowed
/// to connect by checking their tags
///
/// The first tile
/// The second tile
/// True if the tiles are allowed to connect
public bool CanTilesConnect(Tile tileA, Tile tileB)
{
if (TileConnectionTags.Count == 0)
return true;
switch (TileTagConnectionMode)
{
case TagConnectionMode.Accept:
return HasMatchingTagPair(tileA, tileB);
case TagConnectionMode.Reject:
return !HasMatchingTagPair(tileA, tileB);
default:
throw new NotImplementedException(string.Format("{0}.{1} is not implemented", typeof(TagConnectionMode).Name, TileTagConnectionMode));
}
}
public bool CanDoorwaysConnect(ProposedConnection connection)
{
foreach (var rule in DoorwayPairFinder.CustomConnectionRules)
{
TileConnectionRule.ConnectionResult result = TileConnectionRule.ConnectionResult.Passthrough;
if (rule.ConnectionDelegate != null)
result = rule.ConnectionDelegate(connection);
#pragma warning disable CS0618 // For now, we allow rules that haven't been updated to the new delegate format
else if (rule.Delegate != null)
result = rule.Delegate(connection.PreviousTile.PrefabTile, connection.NextTile.PrefabTile, connection.PreviousDoorway.DoorwayComponent, connection.NextDoorway.DoorwayComponent);
#pragma warning restore
if (result == TileConnectionRule.ConnectionResult.Passthrough)
continue;
else
return result == TileConnectionRule.ConnectionResult.Allow;
}
// No custom rules handled this connection, use default behaviour
return DoorwaySocket.CanSocketsConnect(connection.PreviousDoorway.DoorwayComponent.Socket, connection.NextDoorway.DoorwayComponent.Socket) && CanTilesConnect(connection.PreviousTile.PrefabTile, connection.NextTile.PrefabTile);
}
private bool HasMatchingTagPair(Tile tileA, Tile tileB)
{
foreach(var tagPair in TileConnectionTags)
{
if ((tileA.Tags.HasTag(tagPair.TagA) && tileB.Tags.HasTag(tagPair.TagB)) ||
(tileB.Tags.HasTag(tagPair.TagA) && tileA.Tags.HasTag(tagPair.TagB)))
return true;
}
return false;
}
}
}