using System; using System.Collections.Generic; #if UNITY_EDITOR using UnityEditor; #endif using UnityEngine; namespace FIMSpace.Generating.Rules { public enum ESR_Space { Empty, Occupied, OutOfGrid, InGrid } public enum ESR_NeightbourCondition { [Tooltip("All selected cells must met node rule")] AllNeeded, [Tooltip("Just one cell need to met node rule then condition is completed")] AtLeastOne } public enum ESR_DirectionMode { NoOffset, WorldDirection, CellRotateDirection } public enum ESR_OffsetSpace { WorldDirection, CellRotateDirection } public enum ESR_AngleRemovalMode { Any, OnTheLeft, OnTheRight, InFront, Behind, Below, Above } public enum ESR_DistanceRule { Lower, Greater, Equal } public enum ESR_Measuring { Units, Cells } public enum ESR_Transforming { Position, Rotation, Scale } public enum ESR_RotationOrScale { Rotation, Scale } public enum ESR_TileScaleAxis { X, Y, Z } public enum ESR_Origin { [Tooltip("Measure distance using prefabs origin positions")] SpawnPosition, [Tooltip("Measure distance using first-mesh model bounds center position")] BoundsCenter, [Tooltip("Measure distance using first found renderer bounds center position")] RendererCenter } public enum ESR_Details { Tag, SpawnStigma, CellData, Name } public static class SpawnRules { static readonly Quaternion Q0 = Quaternion.Euler(0, 0, 0); static readonly Quaternion Q90 = Quaternion.Euler(0, 90, 0); static readonly Quaternion Q180 = Quaternion.Euler(0, 180, 0); static readonly Quaternion Q270 = Quaternion.Euler(0, 270, 0); static readonly Quaternion QX90 = Quaternion.Euler(90, 0, 0); static readonly Quaternion QX270 = Quaternion.Euler(270, 0, 0); public static Quaternion GetRemovalAngle(ESR_AngleRemovalMode mode) { switch (mode) { case ESR_AngleRemovalMode.OnTheLeft: return Q270; case ESR_AngleRemovalMode.OnTheRight: return Q90; case ESR_AngleRemovalMode.InFront: return Q0; case ESR_AngleRemovalMode.Behind: return Q180; case ESR_AngleRemovalMode.Below: return QX90; case ESR_AngleRemovalMode.Above: return QX270; } return Q0; } public static Vector3 GetAxis(ESR_AngleRemovalMode angleMode) { if (angleMode == ESR_AngleRemovalMode.Above || angleMode == ESR_AngleRemovalMode.Below) return Vector3.right; else return Vector3.up; } public static bool IsNull(this FieldCell data) { return FGenerators.CheckIfIsNull(data); } public static bool NotNull(this FieldCell data) { return FGenerators.CheckIfExist_NOTNULL(data); } public static bool IsNull(this SpawnData data) { return FGenerators.CheckIfIsNull(data); } public static bool NotNull(this SpawnData data) { return FGenerators.CheckIfExist_NOTNULL(data); } private static bool CheckCellAllowOR(FieldCell cell, string occupyTags, ESR_Details checkMode, bool useStateORTag = false) { if (useStateORTag == false) return false; else { if (string.IsNullOrEmpty(occupyTags)) return false; else return CheckNeightbourCellAllow(ESR_Space.Occupied, cell, occupyTags, checkMode); } } public static bool CheckNeightbourCellAllow(this ESR_Space check, FieldCell cell, string occupyTags, ESR_Details checkMode, bool useStateORTag = false) { if (cell == null) { if (check == ESR_Space.OutOfGrid) return true; else { // Not occupied at all (no cell) - so false return false; //if (checkMode == ESR_Details.CellData) //{ // if (occupyTags.Length > 0) if (occupyTags[0] == '!') return true; //} //return CheckCellAllowOR(cell, occupyTags, checkMode, useStateORTag); } } else { if (check == ESR_Space.InGrid) { if (cell.InTargetGridArea) { return true; } else { return CheckCellAllowOR(cell, occupyTags, checkMode, useStateORTag); } } } if (check == ESR_Space.Occupied) { if (checkMode == ESR_Details.CellData) { if (FGenerators.CheckIfExist_NOTNULL(SpawnRuleBase.CellSpawnsHaveSpecifics(cell, occupyTags, ESR_Details.CellData))) //if (SpawnRuleBase.CellHaveData(cell, occupyTags)) return true; else return false; } if (string.IsNullOrEmpty(occupyTags)) { if (cell.CollectSpawns().Count > 0) return true; } else { if (cell.CollectSpawns().Count <= 0) return false; else { if (FGenerators.CheckIfExist_NOTNULL(SpawnRuleBase.CellSpawnsHaveSpecifics(cell, occupyTags, checkMode))) return true; } } } else if (check == ESR_Space.Empty) { if (cell.CollectSpawns().Count < 1) return true; return CheckCellAllowOR(cell, occupyTags, checkMode, useStateORTag); } else if (check == ESR_Space.OutOfGrid) { if (cell.InTargetGridArea == false) return true; return CheckCellAllowOR(cell, occupyTags, checkMode, useStateORTag); } else if (check == ESR_Space.InGrid) { if (cell.InTargetGridArea) return true; return CheckCellAllowOR(cell, occupyTags, checkMode, useStateORTag); } return false; } public static bool CheckNeightbourCellAllowAngled(this ESR_Space check, FieldCell cell, string occupyTags, Vector3 checkDirection, float ignoreAboveAngle, ESR_Details checkMode, bool useStateORTag = false) { if (cell == null) { if (check == ESR_Space.OutOfGrid) return true; else return CheckCellAllowOR(cell, occupyTags, checkMode, useStateORTag); } else { if (check == ESR_Space.InGrid) if (cell.InTargetGridArea) return true; else return CheckCellAllowOR(cell, occupyTags, checkMode, useStateORTag); } if (check == ESR_Space.Occupied) { if (string.IsNullOrEmpty(occupyTags)) { if (cell.CollectSpawns().Count > 0) { if (ignoreAboveAngle > 0f) { var spawns = cell.CollectSpawns(); for (int i = 0; i < spawns.Count; i++) { var spawn = spawns[i]; float angle = Vector3.Angle(checkDirection, spawn.GetFullOffset().normalized); //float angle = Quaternion.Angle(checkDirection, Quaternion.Euler(spawn.LocalRotationOffset + spawn.RotationOffset)); if (angle <= ignoreAboveAngle) return true; } } } else return true; } else { if (cell.CollectSpawns().Count <= 0) return false; else { if (ignoreAboveAngle > 0f) { var spawns = SpawnRuleBase.GetAllSpecificSpawns(cell, occupyTags, checkMode); for (int i = 0; i < spawns.Count; i++) { var spawn = spawns[i]; float angle = Vector3.Angle(checkDirection, spawn.GetFullOffset().normalized); //UnityEngine.Debug.Log("Angle " + angle + " " + cell.FlatPosInt); //float angle = Quaternion.Angle(checkDirection, Quaternion.Euler(spawn.LocalRotationOffset + spawn.RotationOffset)); if (angle <= ignoreAboveAngle) return true; } } else if (FGenerators.CheckIfExist_NOTNULL(SpawnRuleBase.CellSpawnsHaveSpecifics(cell, occupyTags, checkMode))) { return true; } } } } else if (check == ESR_Space.Empty) { if (cell.CollectSpawns().Count < 1) return true; return CheckCellAllowOR(cell, occupyTags, checkMode, useStateORTag); } else if (check == ESR_Space.OutOfGrid) { if (cell.InTargetGridArea == false) return true; return CheckCellAllowOR(cell, occupyTags, checkMode, useStateORTag); } else if (check == ESR_Space.InGrid) { if (cell.InTargetGridArea) return true; return CheckCellAllowOR(cell, occupyTags, checkMode, useStateORTag); } return false; } /// Get all neightbours around cell with option to generate ghost cells public static List GetTargetNeightboursAround(FieldCell cell, FGenGraph grid, ESR_Space targetState, string tags, ESR_Details checkMode, bool useStateORTag = false) { List cells = new List(); for (int x = -1; x <= 1; x++) for (int z = -1; z <= 1; z++) { if (x == 0 && z == 0) continue; // Ignore self FieldCell nCell = grid.GetCell(cell.Pos.x + x, cell.Pos.y, cell.Pos.z + z, false); if (SpawnRules.CheckNeightbourCellAllow(targetState, nCell, tags, checkMode, useStateORTag) == false) continue; if (nCell != null) cells.Add(nCell); else if (targetState == ESR_Space.OutOfGrid) cells.Add(new FieldCell() { Pos = new Vector3Int(cell.Pos.x + x, cell.Pos.y, cell.Pos.z + z), InTargetGridArea = false }); } return cells; } /// Get all neightbours around cell and generate ghost cells instead of nulls public static List Get3x3Square(FieldCell cell, FGenGraph grid) { List cells = new List(); for (int x = -1; x <= 1; x++) for (int z = -1; z <= 1; z++) { if (x == 0 && z == 0) continue; // Ignore self FieldCell nCell = grid.GetCell(cell.Pos.x + x, cell.Pos.y, cell.Pos.z + z, false); if (nCell != null) cells.Add(nCell); else cells.Add(new FieldCell() { Pos = new Vector3Int(cell.Pos.x + x, cell.Pos.y, cell.Pos.z + z), InTargetGridArea = false }); } return cells; } public static List GetTargetNeightboursDiagonal(FieldCell cell, FGenGraph grid) { List cells = new List(); for (int x = -1; x <= 1; x++) for (int z = -1; z <= 1; z++) { if (x == 0 && z == 0) continue; // Ignore self if (x == 0 || z == 0) continue; FieldCell nCell = grid.GetCell(cell.Pos.x + x, cell.Pos.y, cell.Pos.z + z, false); if (nCell != null) cells.Add(nCell); else cells.Add(new FieldCell() { Pos = new Vector3Int(cell.Pos.x + x, cell.Pos.y, cell.Pos.z + z) }); } return cells; } public static List GetTargetNeightboursDiagonal(FieldCell cell, FGenGraph grid, ESR_Space targetState, string tags, ESR_Details checkMode, bool useStateORTag = false) { List cells = new List(); for (int x = -1; x <= 1; x++) for (int z = -1; z <= 1; z++) { if (x == 0 && z == 0) continue; // Ignore self if (x == 0 || z == 0) continue; FieldCell nCell = grid.GetCell(cell.Pos.x + x, cell.Pos.y, cell.Pos.z + z, false); if (SpawnRules.CheckNeightbourCellAllow(targetState, nCell, tags, checkMode, useStateORTag) == false) continue; if (nCell != null) cells.Add(nCell); else if (targetState == ESR_Space.OutOfGrid) cells.Add(new FieldCell() { Pos = new Vector3Int(cell.Pos.x + x, cell.Pos.y, cell.Pos.z + z) }); } return cells; } public static SpawnData GetSpawnRotated(FieldCell cell, Quaternion targetRot, float tolerance = 1f) { var spawns = cell.CollectSpawns(); for (int i = 0; i < spawns.Count; i++) { var s = spawns[i]; if (Quaternion.Angle(Quaternion.Euler(s.RotationOffset), targetRot) < tolerance) return s; } return null; } public static SpawnData GetNearest(FieldCell cell, Vector3 wpos, FieldSetup forNearestCheck, float maxDistance = 1000f, System.Func iterationCondition = null) { var spawns = cell.CollectSpawns(); float nearest = float.MaxValue; float maxDistSqrt = maxDistance * maxDistance; SpawnData nesrSp = null; //if (spawn.PreviewMesh) // wpos = cell.WorldPos(forNearestCheck) + spawn.GetMeshFilterOrColliderBounds().center; for (int i = 0; i < spawns.Count; i++) { var s = spawns[i]; if (s.DontSpawnMainPrefab) continue; //if (Quaternion.Angle(Quaternion.Euler(s.RotationOffset), targetRot) < tolerance) { Vector3 refPos; float dist; //if (s.PreviewMesh) // refPos = cell.WorldPos(forNearestCheck) + s.GetMeshFilterOrColliderBounds().center; //else refPos = (s.GetWorldPositionWithFullOffset(forNearestCheck)); dist = (refPos - wpos).sqrMagnitude; if (dist > maxDistSqrt) continue; if (iterationCondition != null) { if (iterationCondition.Invoke(s) == false) continue; } if (dist < nearest) { nearest = dist; nesrSp = s; } } } return nesrSp; } public static SpawnData GetSpawnRotated(FieldCell cell, Quaternion targetRot, FieldModification mustHaveMod, string cantHaveStigma, float tolerance = 1f) { var spawns = cell.CollectSpawns(); for (int i = 0; i < spawns.Count; i++) { var s = spawns[i]; bool mod = false; if (mustHaveMod != null) mod = s.OwnerMod == mustHaveMod; if (mod) if (!string.IsNullOrEmpty(cantHaveStigma)) if (s.GetCustomStigma(cantHaveStigma)) mod = false; if (mod) if (Quaternion.Angle(Quaternion.Euler(s.RotationOffset), targetRot) < tolerance) return s; } return null; } public static SpawnData GetSpawnWithStigma(FieldCell cell, string stigma, FieldModification onlyMod = null) { var spawns = cell.CollectSpawns(); if (onlyMod == null) { for (int i = 0; i < spawns.Count; i++) if (spawns[i].GetCustomStigma(stigma)) return spawns[i]; } else { for (int i = 0; i < spawns.Count; i++) if (spawns[i].OwnerMod == onlyMod) if (spawns[i].GetCustomStigma(stigma)) return spawns[i]; } return null; } public static SpawnData GetSpawnWithExactMod(FieldCell cell, FieldModification mod = null) { var spawns = cell.CollectSpawns(); if (mod == null) { return null; } else { for (int i = 0; i < spawns.Count; i++) if (spawns[i].OwnerMod == mod) return spawns[i]; } return null; } public static bool IsSpawnRotated(SpawnData spawn, Quaternion targetRotation) { if (Quaternion.Angle(Quaternion.Euler(spawn.RotationOffset), targetRotation) < 1f) return true; return false; } public static List GetTargetNeightboursPLUS(FieldCell cell, FGenGraph grid) { List cells = new List(); for (int x = -1; x <= 1; x++) for (int z = -1; z <= 1; z++) { if (x == 0 && z == 0) continue; // Ignore self if (x != 0 && z != 0) continue; // Ignore diagonals FieldCell nCell = grid.GetCell(cell.Pos.x + x, cell.Pos.y, cell.Pos.z + z, false); if (FGenerators.CheckIfExist_NOTNULL(nCell)) { cells.Add(nCell); } else // null cell but it's desired state cells.Add(new FieldCell() { Pos = new Vector3Int(cell.Pos.x + x, cell.Pos.y, cell.Pos.z + z) }); } return cells; } public static List GetTargetNeightboursPLUS(FieldCell cell, FGenGraph grid, ESR_Space targetState, string tags, ESR_Details checkMode, bool useStateORTag = false) { List cells = new List(); for (int x = -1; x <= 1; x++) for (int z = -1; z <= 1; z++) { if (x == 0 && z == 0) continue; // Ignore self if (x != 0 && z != 0) continue; // Ignore diagonals FieldCell nCell = grid.GetCell(cell.Pos.x + x, cell.Pos.y, cell.Pos.z + z, false); if (SpawnRules.CheckNeightbourCellAllow(targetState, nCell, tags, checkMode, useStateORTag) == false) continue; if (FGenerators.CheckIfExist_NOTNULL(nCell)) { cells.Add(nCell); } else // null cell but it's desired state if (targetState == ESR_Space.OutOfGrid) cells.Add(new FieldCell() { Pos = new Vector3Int(cell.Pos.x + x, cell.Pos.y, cell.Pos.z + z) }); } return cells; } public static readonly Quaternion Step0 = Quaternion.Euler(0f, 0f, 0f); public static readonly Quaternion Step45 = Quaternion.Euler(0f, 45f, 0f); public static readonly Quaternion Step90 = Quaternion.Euler(0f, 90f, 0f); public static readonly Quaternion Step135 = Quaternion.Euler(0f, 135f, 0f); public static readonly Quaternion Step180 = Quaternion.Euler(0f, 180f, 0f); public static readonly Quaternion Step225 = Quaternion.Euler(0f, 225, 0f); public static readonly Quaternion Step270 = Quaternion.Euler(0f, 270f, 0f); public static readonly Quaternion Step315 = Quaternion.Euler(0f, 315f, 0f); public enum CellOffset { Angle0, Angle90, Angle180, Angle270, Angle45, Angle135, Angle225, Angle315 } public static Vector3Int GetCellOffset(CellOffset off) { switch (off) { case CellOffset.Angle0: return new Vector3Int(0, 0, 1); case CellOffset.Angle90: return new Vector3Int(1, 0, 0); case CellOffset.Angle180: return new Vector3Int(0, 0, -1); case CellOffset.Angle270: return new Vector3Int(-1, 0, 0); case CellOffset.Angle45: return new Vector3Int(1, 0, 1); case CellOffset.Angle135: return new Vector3Int(-1, 0, 1); case CellOffset.Angle225: return new Vector3Int(-1, 0, -1); case CellOffset.Angle315: return new Vector3Int(1, 0, -1); default: return Vector3Int.zero; } } //public static Vector3 GetCellOffsetV3(CellOffset off) //{ // return V2toV3(GetCellOffset(off)); //} public static Quaternion GetCellOffsetQ(CellOffset off) { switch (off) { case CellOffset.Angle0: return Step0; case CellOffset.Angle45: return Step45; case CellOffset.Angle90: return Step90; case CellOffset.Angle135: return Step135; case CellOffset.Angle180: return Step180; case CellOffset.Angle225: return Step225; case CellOffset.Angle270: return Step270; case CellOffset.Angle315: return Step315; default: return Step0; } } public static CellOffset GetCellOffset45Step(int id) { switch (id) { case 0: return CellOffset.Angle0; case 1: return CellOffset.Angle45; case 2: return CellOffset.Angle90; case 3: return CellOffset.Angle135; case 4: return CellOffset.Angle180; case 5: return CellOffset.Angle225; case 6: return CellOffset.Angle270; case 7: return CellOffset.Angle315; default: return CellOffset.Angle0; } } public static Vector3 V2toV3(Vector2 flat) { return new Vector3(flat.x, 0, flat.y); } public static Vector3Int V2toV3Int(Vector2 flat) { return new Vector3Int(Mathf.RoundToInt(flat.x), 0, Mathf.RoundToInt(flat.y)); } public static Vector2Int V2toV2Int(Vector2 flat) { return new Vector2Int(Mathf.RoundToInt(flat.x), Mathf.RoundToInt(flat.y)); } public static CellOffset Get90Offset(int step) { if (step == 0) return CellOffset.Angle0; else if (step == 1) return CellOffset.Angle90; else if (step == 2) return CellOffset.Angle180; else return CellOffset.Angle270; } public static FieldCell GetAngledNeightbour(FieldCell cell, FGenGraph grid, CellOffset angle) { FieldCell c = grid.GetCell(cell.Pos + GetCellOffset(angle), false); if (FGenerators.CheckIfIsNull(c)) c = new FieldCell() { Pos = cell.Pos + (GetCellOffset(angle)), InTargetGridArea = false }; return c; } public static FieldCell GetAngledNeightbour45(FieldCell cell, FGenGraph grid, int angleId) { if (angleId > 7) angleId -= 8; if (angleId < 0) angleId += 8; FieldCell c = grid.GetCell(cell.Pos + GetCellOffset(GetCellOffset45Step(angleId)), false); if (FGenerators.CheckIfIsNull(c)) c = new FieldCell() { Pos = cell.Pos + (GetCellOffset(GetCellOffset45Step(angleId))), InTargetGridArea = false }; return c; } public static FieldCell GetAngledNeightbour90(FieldCell cell, FGenGraph grid, int angleId) { if (angleId > 3) angleId -= 4; if (angleId < 0) angleId += 4; FieldCell c = grid.GetCell(cell.Pos + GetCellOffset(SpawnRules.Get90Offset(angleId)), false); if (FGenerators.CheckIfIsNull(c)) c = new FieldCell() { Pos = cell.Pos + (GetCellOffset(SpawnRules.Get90Offset(angleId))), InTargetGridArea = false }; return c; } } }