using UnityEngine; using System; using System.Text; using System.Collections; using System.Collections.Generic; using TGS.Geom; using TGS.PathFinding; namespace TGS { public partial class TerrainGridSystem : MonoBehaviour { [NonSerialized] public readonly List territories = new List(); public Texture2D territoriesTexture; [ColorUsage(true, true)] public Color territoriesTextureNeutralColor; [SerializeField] bool _territoriesHideNeutralCells; /// /// Gets or sets if neutral cells are visible. /// public bool territoriesHideNeutralCells { get { return _territoriesHideNeutralCells; } set { if (_territoriesHideNeutralCells != value) { _territoriesHideNeutralCells = value; Redraw(); isDirty = true; } } } [SerializeField] int _numTerritories = 3; /// /// Gets or sets the number of initial territories. /// public int numTerritories { get { return _numTerritories; } set { if (_numTerritories != value) { _numTerritories = Mathf.Clamp(value, 0, MAX_TERRITORIES); needGenerateMap = true; isDirty = true; } } } /// /// Recreate territories and optionally keep current cells /// /// /// If true, cells and their visibility state will be preserved public void SetNumTerritories(int numTerritories, bool preserveCells) { if (_numTerritories == numTerritories) return; _numTerritories = numTerritories; isDirty = true; if (preserveCells) { GenerateMap(reuseTerrainData: true, keepCells: true); } else { needGenerateMap = true; } } [SerializeField] int _territoriesMaxIterations; /// /// Maximum number of iterations when generation territories /// public int territoriesMaxIterations { get { return _territoriesMaxIterations; } set { if (_territoriesMaxIterations != value) { _territoriesMaxIterations = Mathf.Max(0, value); needGenerateMap = true; isDirty = true; } } } [SerializeField] float _territoriesAsymmetry; /// /// Determines the distribution of cells among territories /// public float territoriesAsymmetry { get { return _territoriesAsymmetry; } set { if (_territoriesAsymmetry != value) { _territoriesAsymmetry = Mathf.Clamp01(value); needGenerateMap = true; isDirty = true; } } } [SerializeField] float _territoriesOrganic; /// /// Makes territory contour more organic /// public float territoriesOrganic { get { return _territoriesOrganic; } set { if (_territoriesOrganic != value) { _territoriesOrganic = Mathf.Clamp01(value); needGenerateMap = true; isDirty = true; } } } [SerializeField] bool _showTerritories; /// /// Toggle frontiers visibility. /// public bool showTerritories { get { return _showTerritories; } set { if (value != _showTerritories) { _showTerritories = value; isDirty = true; if (territoryLayer != null) { territoryLayer.gameObject.SetActive(_showTerritories); ClearLastOver(); } else { Redraw(); } } } } [SerializeField] bool _showTerritoriesInteriorBorders; /// /// Toggle interior frontiers visibility. /// public bool showTerritoriesInteriorBorders { get { return _showTerritoriesInteriorBorders; } set { if (value != _showTerritoriesInteriorBorders) { _showTerritoriesInteriorBorders = value; isDirty = true; if (territoryInteriorBorderLayer != null) { territoryInteriorBorderLayer.gameObject.SetActive(_showTerritoriesInteriorBorders); } else { Redraw(); } } } } [SerializeField] bool _territoryInteriorBorderOnTop; /// /// Show interior borders on top of regular territory borders. /// public bool territoryInteriorBorderOnTop { get { return _territoryInteriorBorderOnTop; } set { if (value != _territoryInteriorBorderOnTop) { _territoryInteriorBorderOnTop = value; isDirty = true; Redraw(); } } } [SerializeField] bool _territoryInteriorBorderIncludeEnclaves = true; /// /// Show interior borders on top of regular territory borders. /// public bool territoryInteriorBorderIncludeEnclaves { get { return _territoryInteriorBorderIncludeEnclaves; } set { if (value != _territoryInteriorBorderIncludeEnclaves) { _territoryInteriorBorderIncludeEnclaves = value; isDirty = true; Redraw(); } } } [SerializeField] bool _colorizeTerritories; /// /// Toggle colorize countries. /// public bool colorizeTerritories { get { return _colorizeTerritories; } set { if (value != _colorizeTerritories) { _colorizeTerritories = value; isDirty = true; if (!_colorizeTerritories && surfacesLayer != null) { DestroyTerritorySurfaces(); } else { Redraw(); } } } } [SerializeField] float _colorizedTerritoriesAlpha = 0.7f; public float colorizedTerritoriesAlpha { get { return _colorizedTerritoriesAlpha; } set { if (_colorizedTerritoriesAlpha != value) { _colorizedTerritoriesAlpha = value; isDirty = true; UpdateColorizedTerritoriesAlpha(); } } } [SerializeField] [ColorUsage(true, true)] Color _territoryHighlightColor = new Color(1, 0, 0, 0.8f); /// /// Fill color to use when the mouse hovers a territory's region. /// public Color territoryHighlightColor { get { return _territoryHighlightColor; } set { if (value != _territoryHighlightColor) { _territoryHighlightColor = value; isDirty = true; if (hudMatTerritoryOverlay != null && _territoryHighlightColor != hudMatTerritoryOverlay.color) { hudMatTerritoryOverlay.color = _territoryHighlightColor; } if (hudMatTerritoryGround != null && _territoryHighlightColor != hudMatTerritoryGround.color) { hudMatTerritoryGround.color = _territoryHighlightColor; } } } } [SerializeField] [ColorUsage(true, true)] Color _territoryHighlightColor2 = new Color(0, 1, 0, 0.8f); /// /// Alternate fill color to use when the mouse hovers a territory's region. /// public Color territoryHighlightColor2 { get { return _territoryHighlightColor2; } set { if (value != _territoryHighlightColor2) { _territoryHighlightColor2 = value; isDirty = true; if (hudMatTerritoryOverlay != null) { hudMatTerritoryOverlay.SetColor(ShaderParams.Color2, _territoryHighlightColor2); } if (hudMatTerritoryGround != null) { hudMatTerritoryGround.SetColor(ShaderParams.Color2, _territoryHighlightColor2); } } } } [SerializeField] [ColorUsage(true, true)] Color _territoryFrontierColor = new Color(0, 1, 0, 1.0f); /// /// Territories border color /// public Color territoryFrontiersColor { get { if (territoriesMat != null) { return territoriesMat.color; } else { return _territoryFrontierColor; } } set { if (value != _territoryFrontierColor) { _territoryFrontierColor = value; isDirty = true; if (territoriesThinMat != null && _territoryFrontierColor != territoriesThinMat.color) { territoriesThinMat.color = _territoryFrontierColor; } if (territoriesGeoMat != null && _territoryFrontierColor != territoriesGeoMat.color) { territoriesGeoMat.color = _territoryFrontierColor; } } } } public float territoryFrontiersAlpha { get { return _territoryFrontierColor.a; } set { if (_territoryFrontierColor.a != value) { _territoryFrontierColor = new Color(_territoryFrontierColor.r, _territoryFrontierColor.g, _territoryFrontierColor.b, value); } if (_territoryDisputedFrontierColor.a != value) { _territoryDisputedFrontierColor = new Color(_territoryDisputedFrontierColor.r, _territoryDisputedFrontierColor.g, _territoryDisputedFrontierColor.b, value); } } } [SerializeField] float _territoryFrontiersThickness = 1f; /// /// Territory frontier thickness /// public float territoryFrontiersThickness { get { return _territoryFrontiersThickness; } set { if (value != _territoryFrontiersThickness) { _territoryFrontiersThickness = Mathf.Max(1f, value); UpdateMaterialThickness(); if (_showTerritories) { DrawTerritoryFrontiers(); } isDirty = true; } } } [SerializeField] float _territoryInteriorBorderThickness = 3f; /// /// Territory interior border thickness /// public float territoryInteriorBorderThickness { get { return _territoryInteriorBorderThickness; } set { if (value != territoryInteriorBorderThickness) { _territoryInteriorBorderThickness = Mathf.Max(1f, value); if (_showTerritoriesInteriorBorders) { DrawInteriorTerritoryFrontiers(); } isDirty = true; } } } [SerializeField] float _territoryInteriorBorderPadding = -0.6f; /// /// Territory interior border padding /// public float territoryInteriorBorderPadding { get { return _territoryInteriorBorderPadding; } set { if (value != _territoryInteriorBorderPadding) { _territoryInteriorBorderPadding = value; if (_showTerritoriesInteriorBorders) { DrawInteriorTerritoryFrontiers(); } isDirty = true; } } } [SerializeField] [ColorUsage(true, true)] Color _territoryDisputedFrontierColor = new Color(0, 1, 0, 1.0f); /// /// Territories disputed borders color /// public Color territoryDisputedFrontierColor { get { if (territoriesDisputedMat != null) { return territoriesDisputedMat.color; } else { return _territoryDisputedFrontierColor; } } set { if (value != _territoryDisputedFrontierColor) { _territoryDisputedFrontierColor = value; isDirty = true; if (territoriesDisputedThinMat != null && _territoryDisputedFrontierColor != territoriesDisputedThinMat.color) { territoriesDisputedThinMat.color = _territoryDisputedFrontierColor; } if (territoriesDisputedGeoMat != null && _territoryDisputedFrontierColor != territoriesDisputedGeoMat.color) { territoriesDisputedGeoMat.color = _territoryDisputedFrontierColor; } } } } [SerializeField] bool _showTerritoriesOuterBorder = true; /// /// Shows perimetral/outer border of territories? /// /// true if show territories outer borders; otherwise, false. public bool showTerritoriesOuterBorders { get { return _showTerritoriesOuterBorder; } set { if (_showTerritoriesOuterBorder != value) { _showTerritoriesOuterBorder = value; isDirty = true; Redraw(); } } } [SerializeField] bool _allowTerritoriesInsideTerritories; /// /// Set this property to true to allow territories to be surrounded by other territories. /// public bool allowTerritoriesInsideTerritories { get { return _allowTerritoriesInsideTerritories; } set { if (_allowTerritoriesInsideTerritories != value) { _allowTerritoriesInsideTerritories = value; isDirty = true; } } } [SerializeField] bool _territoriesIgnoreHiddenCells; /// /// Set this property to true to allow territories to be surrounded by other territories. /// public bool territoriesIgnoreHiddenCells { get { return _territoriesIgnoreHiddenCells; } set { if (_territoriesIgnoreHiddenCells != value) { _territoriesIgnoreHiddenCells = value; isDirty = true; } } } /// /// Returns Territory under mouse position or null if none. /// public Territory territoryHighlighted { get { return _territoryHighlighted; } } /// /// Returns currently highlighted territory index in the countries list. /// public int territoryHighlightedIndex { get { return _territoryHighlightedIndex; } } /// /// Returns currently highlighted territory region index. /// public int territoryHighlightedRegionIndex { get { return _territoryHighlightedRegionIndex; } } /// /// Returns Territory index which has been clicked /// public int territoryLastClickedIndex { get { return _territoryLastClickedIndex; } } /// /// Returns Territory region index which has been clicked /// public int territoryRegionLastClickedIndex { get { return _territoryRegionLastClickedIndex; } } #region Public Territories Functions /// /// Uncolorize/hide all territories. /// public void TerritoryHideRegionSurfaces() { int terrCount = territories.Count; for (int k = 0; k < terrCount; k++) { TerritoryHideRegionSurface(k); } } /// /// Uncolorize/hide specified territory by index in the territories collection. /// public void TerritoryHideRegionSurface(int territoryIndex, int regionIndex = 0) { if (!ValidTerritoryIndex(territoryIndex, regionIndex)) return; if ((_territoryHighlightedIndex != territoryIndex && _territoryHighlightedRegionIndex != regionIndex) || _highlightedObj == null) { int cacheIndex = GetCacheIndexForTerritoryRegion(territoryIndex, regionIndex); GameObject surf; if (surfaces.TryGetValue(cacheIndex, out surf)) { if (surf == null) { surfaces.Remove(cacheIndex); } else { surf.SetActive(false); } } } territories[territoryIndex].regions[regionIndex].customMaterial = null; } /// /// Assigns a custom material to a territory /// public GameObject TerritorySetMaterial(int territoryIndex, Material material, int regionIndex = 0) { if (!ValidTerritoryIndex(territoryIndex, regionIndex)) return null; GameObject o = TerritoryToggleRegionSurface(territoryIndex, true, Color.white, regionIndex: regionIndex); Region region = territories[territoryIndex].regions[regionIndex]; region.customMaterial = material; region.customRotateInLocalSpace = false; region.customTextureOffset = Vector2.zero; region.customTextureRotation = 0; region.customTextureScale = Vector2.one; ApplyMaterialToSurface(region, material); return o; } /// /// Sets the color of a territory /// public GameObject TerritorySetColor(Territory territory, Color color, int regionIndex = 0) { int territoryIndex = TerritoryGetIndex(territory); return TerritorySetColor(territoryIndex, color, regionIndex); } /// /// Sets the color of a territory /// public GameObject TerritorySetColor(int territoryIndex, Color color, int regionIndex = 0) { return TerritoryToggleRegionSurface(territoryIndex, true, color, regionIndex: regionIndex); } /// /// Sets the texture of a territory /// public GameObject TerritorySetTexture(Territory territory, Texture2D texture, int regionIndex = 0) { int territoryIndex = TerritoryGetIndex(territory); return TerritorySetTexture(territoryIndex, texture, regionIndex); } /// /// Sets the texture of a territory /// public GameObject TerritorySetTexture(int territoryIndex, Texture2D texture, int regionIndex = 0) { return TerritoryToggleRegionSurface(territoryIndex, true, Color.white, false, texture, regionIndex); } /// /// Colorize specified territory by index. /// /// Territory. /// If the colored surface will be visible or not. /// Color. public GameObject TerritoryToggleRegionSurface(Territory territory, bool visible, Color color, bool refreshGeometry = false, int regionIndex = 0) { int territoryIndex = TerritoryGetIndex(territory); return TerritoryToggleRegionSurface(territoryIndex, visible, color, refreshGeometry, null, Misc.Vector2one, Misc.Vector2zero, 0, false, regionIndex); } /// /// Colorize specified territory by index. /// /// Territory index. /// If the colored surface will be visible or not. /// Color. public GameObject TerritoryToggleRegionSurface(int territoryIndex, bool visible, Color color, bool refreshGeometry = false, int regionIndex = 0) { return TerritoryToggleRegionSurface(territoryIndex, visible, color, refreshGeometry, null, Misc.Vector2one, Misc.Vector2zero, 0, false, regionIndex); } /// /// Colorize specified territory by index. /// /// Territory index. /// If the colored surface will be visible or not. /// Color. /// If set to true any cached surface will be destroyed and regenerated. Usually you pass false to improve performance. /// Texture, which will be tinted according to the color. Use Color.white to preserve original texture colors. public GameObject TerritoryToggleRegionSurface(int territoryIndex, bool visible, Color color, bool refreshGeometry, Texture2D texture, int regionIndex = 0) { return TerritoryToggleRegionSurface(territoryIndex, visible, color, refreshGeometry, texture, Misc.Vector2one, Misc.Vector2zero, 0, false, regionIndex); } /// /// Colorize specified territory by index. /// /// Territory index. /// If the colored surface will be visible or not. /// Color. /// If set to true any cached surface will be destroyed and regenerated. Usually you pass false to improve performance. /// Texture, which will be tinted according to the color. Use Color.white to preserve original texture colors. /// Texture scale. /// Texture offset. /// Texture rotation. public GameObject TerritoryToggleRegionSurface(int territoryIndex, bool visible, Color color, bool refreshGeometry, Texture2D texture, Vector2 textureScale, Vector2 textureOffset, float textureRotation, bool rotateInLocalSpace, int regionIndex = 0) { return TerritoryToggleRegionSurface(territoryIndex, visible, color, refreshGeometry, texture, textureScale, textureOffset, textureRotation, false, rotateInLocalSpace, regionIndex); } /// /// Colorize specified territory by index. /// /// Territory. /// If the colored surface will be visible or not. /// Color. /// If set to true any cached surface will be destroyed and regenerated. Usually you pass false to improve performance. /// Texture, which will be tinted according to the color. Use Color.white to preserve original texture colors. /// Texture scale. /// Texture offset. /// Texture rotation. /// If set to true the colored surface will be shown over any object. public GameObject TerritoryToggleRegionSurface(Territory territory, bool visible, Color color, bool refreshGeometry, Texture2D texture, Vector2 textureScale, Vector2 textureOffset, float textureRotation, bool overlay, bool rotateInLocalSpace, int regionIndex = 0) { int territoryIndex = TerritoryGetIndex(territory); return TerritoryToggleRegionSurface(territoryIndex, visible, color, refreshGeometry, texture, textureScale, textureOffset, textureRotation, overlay, rotateInLocalSpace, regionIndex); } /// /// Colorize specified territory by index. /// /// Territory index. /// If the colored surface will be visible or not. /// Color. /// If set to true any cached surface will be destroyed and regenerated. Usually you pass false to improve performance. /// Texture, which will be tinted according to the color. Use Color.white to preserve original texture colors. /// Texture scale. /// Texture offset. /// Texture rotation. /// If set to true the colored surface will be shown over any object. public GameObject TerritoryToggleRegionSurface(int territoryIndex, bool visible, Color color, bool refreshGeometry, Texture2D texture, Vector2 textureScale, Vector2 textureOffset, float textureRotation, bool overlay, bool rotateInLocalSpace, int regionIndex = 0) { if (needGenerateMap || needResortCells || issueRedraw != RedrawType.None) { CheckGridChanges(); } if (!ValidTerritoryIndex(territoryIndex, regionIndex)) return null; if (!visible) { TerritoryHideRegionSurface(territoryIndex); return null; } Region region = territories[territoryIndex].regions[regionIndex]; int cacheIndex = GetCacheIndexForTerritoryRegion(territoryIndex, regionIndex); GameObject surf; // Checks if current cached surface contains a material with a texture, if it exists but it has not texture, destroy it to recreate with uv mappings bool existsInCache = surfaces.TryGetValue(cacheIndex, out surf); if (existsInCache && surf == null) { surfaces.Remove(cacheIndex); existsInCache = false; } if (refreshGeometry && existsInCache) { surfaces.Remove(cacheIndex); DestroyImmediate(surf); existsInCache = false; surf = null; } Material coloredMat = overlay ? coloredMatOverlayTerritory : coloredMatGroundTerritory; Material texturizedMat = overlay ? texturizedMatOverlayTerritory : texturizedMatGroundTerritory; // Should the surface be recreated? Material surfMaterial; if (surf != null) { surfMaterial = surf.GetComponent().sharedMaterial; if (texture != null && (region.customMaterial == null || textureScale != region.customTextureScale || textureOffset != region.customTextureOffset || textureRotation != region.customTextureRotation || !region.customMaterial.name.Equals(texturizedMat.name))) { surfaces.Remove(cacheIndex); DestroyImmediate(surf); surf = null; } } // If it exists, activate and check proper material, if not create surface bool isHighlighted = territoryHighlightedIndex == territoryIndex && _highlightEffect != HighlightEffect.None; if (surf != null) { if (!surf.activeSelf) { surf.SetActive(true); } // Check if material is ok Renderer renderer = surf.GetComponent(); surfMaterial = renderer.sharedMaterial; if ((texture == null && !surfMaterial.name.Equals(coloredMat.name)) || (texture != null && !surfMaterial.name.Equals(texturizedMat.name)) || (surfMaterial.color != color && !isHighlighted) || (texture != null && (region.customMaterial == null || region.customMaterial.mainTexture != texture))) { Material goodMaterial = GetColoredTexturedMaterialForTerritory(region, color, texture, overlay); region.customMaterial = goodMaterial; ApplyMaterialToSurface(region, goodMaterial); } } else { surfMaterial = GetColoredTexturedMaterialForTerritory(region, color, texture, overlay); surf = GenerateTerritoryRegionSurface(territoryIndex, surfMaterial, textureScale, textureOffset, textureRotation, rotateInLocalSpace, regionIndex); region.customMaterial = surfMaterial; region.customTextureOffset = textureOffset; region.customTextureRotation = textureRotation; region.customTextureScale = textureScale; region.customRotateInLocalSpace = rotateInLocalSpace; } // If it was highlighted, highlight it again if (isHighlighted && region.customMaterial != null && _highlightedObj != null) { if (hudMatTerritory.HasProperty(ShaderParams.MainTex)) { if (region.customMaterial != null) { hudMatTerritory.mainTexture = region.customMaterial.mainTexture; } else { hudMatTerritory.mainTexture = null; } } surf.GetComponent().sharedMaterial = hudMatTerritory; _highlightedObj = surf; } return surf; } /// /// Specifies if a given territory border is visible. /// public void TerritorySetBorderVisible(int territoryIndex, bool visible) { if (territoryIndex < 0 || territoryIndex >= territories.Count) return; territories[territoryIndex].borderVisible = visible; } /// /// Returns a list of neighbour territories for specificed cell index. /// public List TerritoryGetNeighbours(int territoryIndex) { if (!ValidTerritoryIndex(territoryIndex)) return null; return territories[territoryIndex].neighbours; } /// /// Returns a list of neighbour territories for specificed territory index in the territories parameter. /// public int TerritoryGetNeighbours(int territoryIndex, List territories) { if (!ValidTerritoryIndex(territoryIndex)) return 0; territories.Clear(); territories.AddRange(territories[territoryIndex].neighbours); return territories.Count; } /// /// Returns a list of cells that form the frontiers of a given territory /// /// The number of cells found. /// Territory index. /// Cells that form the frontier. You need to pass an already initialized list, which will be cleared and filled with the cells. /// If the territory has several regions, the index of the region /// If a cell is located in the edge of the grid, include it in the results public int TerritoryGetFrontierCells(int territoryIndex, List cellIndices, int regionIndex = -1, bool includeGridEdges = false) { return TerritoryGetFrontierCells(territoryIndex, -1, cellIndices, regionIndex, includeGridEdges); } /// /// Returns a copy of all cells belonging to a territory. Use Territory.cells to access the list without making a copy /// public List TerritoryGetCells(int territoryIndex) { if (!ValidTerritoryIndex(territoryIndex)) return null; List cells = new List(); Territory terr = territories[territoryIndex]; if (terr.cells != null) { cells.AddRange(terr.cells); } return cells; } /// /// Returns all cells belonging to a territory into an user given list. See alto territory.cells list. /// public void TerritoryGetCells(int territoryIndex, List cells) { if (!ValidTerritoryIndex(territoryIndex)) return; cells.Clear(); Territory terr = territories[territoryIndex]; if (terr.cells != null) { cells.AddRange(terr.cells); } } /// /// Returns all cells belonging to a territory region /// public List TerritoryGetCells(int territoryIndex, int regionIndex) { List cells = new List(); TerritoryGetCells(territoryIndex, regionIndex, cells); return cells; } /// /// Returns all cells belonging to a territory region into an user given cell list /// public void TerritoryGetCells(int territoryIndex, int regionIndex, List cells) { if (!ValidTerritoryIndex(territoryIndex, regionIndex)) return; Territory territory = territories[territoryIndex]; Region region = territory.regions[regionIndex]; if (territory.isDirty || region.cells == null || region.cells.Count == 0) { ComputeTerritoryRegionCells(region); } cells.Clear(); cells.AddRange(region.cells); } /// /// Finds and updates the region.cells list based on the location of the cells inside territories /// public void ComputeTerritoryRegionCells(Region region) { Territory territory = (Territory)region.entity; // Optimization: if territory only contains one region, make region.cells point to territory.cells if (territory.regions.Count == 1) { region.cells = territory.cells; return; } if (region.cells == null || region.cells == territory.cells) { region.cells = new List(); } else { region.cells.Clear(); } int cellsCount = territory.cells.Count; for (int k = 0; k < cellsCount; k++) { Cell cell = territory.cells[k]; Vector2 scaledCenter = cell.scaledCenter; if (region.Contains(scaledCenter.x, scaledCenter.y)) { region.cells.Add(cell); } } } /// /// Returns a list of cells that forms the frontiers between a given territory and another one. /// /// The get frontier cells. /// Territory index. /// Other territory index. /// Cells that form the frontier. You need to pass an already initialized list, which will be cleared and filled with the cells. /// Limit search to a given region. -1 means include all regions. /// If a cell is located in the edge of the grid, include it in the results public int TerritoryGetFrontierCells(int territoryIndex, int otherTerritoryIndex, List cellIndices, int regionIndex = -1, bool includeGridEdges = false) { if (territoryIndex < 0 || territoryIndex >= territories.Count || territories[territoryIndex].cells == null || cells == null) return 0; List regionCells = null; if (regionIndex >= 0) regionCells = TerritoryGetCells(territoryIndex, regionIndex); cellIndices.Clear(); foreach (KeyValuePair kv in territoryNeighbourHit) { Segment segment = kv.Key; Frontier frontier = kv.Value; if (frontier.region1 == null || frontier.region2 == null) continue; Cell cell1 = (Cell)frontier.region1.entity; Cell cell2 = (Cell)frontier.region2.entity; if (cell1.territoryIndex == territoryIndex && (otherTerritoryIndex < 0 || cell2.territoryIndex == otherTerritoryIndex)) { if (!cellIndices.Contains(cell1.index) && (regionIndex < 0 || regionCells.Contains(cell1))) { cellIndices.Add(cell1.index); } } else if (cell2.territoryIndex == territoryIndex && (otherTerritoryIndex < 0 || cell1.territoryIndex == otherTerritoryIndex)) { if (!cellIndices.Contains(cell2.index) && (regionIndex < 0 || regionCells.Contains(cell2))) { cellIndices.Add(cell2.index); } } } if (includeGridEdges) { Territory territory = territories[territoryIndex]; foreach (Cell cell in territory.cells) { if (cellIndices.Contains(cell.index)) continue; if (CellIsBorder(cell.index)) { cellIndices.Add(cell.index); } } } return cellIndices.Count; } /// /// Similar to TerritoryGetFrontierCells but returns the cells of the adjacent territory /// public int TerritoryGetAdjacentCells(int territoryIndex, List cellIndices, int regionIndex = -1, int otherTerritoryIndex = -1) { if (territoryIndex < 0 || territoryIndex >= territories.Count || territories[territoryIndex].cells == null || cells == null) return 0; List regionCells = null; if (regionIndex >= 0) regionCells = TerritoryGetCells(territoryIndex, regionIndex); cellIndices.Clear(); foreach (KeyValuePair kv in territoryNeighbourHit) { Segment segment = kv.Key; Frontier frontier = kv.Value; if (frontier.region1 == null || frontier.region2 == null) continue; Cell cell1 = (Cell)frontier.region1.entity; Cell cell2 = (Cell)frontier.region2.entity; if (cell1.territoryIndex == territoryIndex && (otherTerritoryIndex < 0 || cell2.territoryIndex == otherTerritoryIndex)) { if (!cellIndices.Contains(cell2.index) && (regionIndex < 0 || regionCells.Contains(cell1))) { cellIndices.Add(cell2.index); } } else if (cell2.territoryIndex == territoryIndex && (otherTerritoryIndex < 0 || cell1.territoryIndex == otherTerritoryIndex)) { if (!cellIndices.Contains(cell1.index) && (regionIndex < 0 || regionCells.Contains(cell2))) { cellIndices.Add(cell1.index); } } } return cellIndices.Count; } /// /// Colors a territory and fades it out during "duration" in seconds. /// public void TerritoryFadeOut(int territoryIndex, Color color, float duration, int repetitions = 1) { TerritoryAnimate(FaderStyle.FadeOut, territoryIndex, color, duration, repetitions); } /// /// Flashes a territory with "color" and "duration" in seconds. /// public void TerritoryFlash(int territoryIndex, Color color, float duration, int repetitions = 1) { TerritoryAnimate(FaderStyle.Flash, territoryIndex, color, duration, repetitions); } /// /// Blinks a territory with "color" and "duration" in seconds. /// public void TerritoryBlink(int territoryIndex, Color color, float duration, int repetitions = 1) { TerritoryAnimate(FaderStyle.Blink, territoryIndex, color, duration, repetitions); } /// /// Temporarily colors a territory for "duration" in seconds. /// public void TerritoryColorTemp(int territoryIndex, Color color, float duration) { TerritoryAnimate(FaderStyle.ColorTemp, territoryIndex, color, duration, 1); } /// /// Cancels any ongoing visual effect on a territory /// /// Cell index. public void TerritoryCancelAnimations(int territoryIndex, float fadeOutDuration = 0) { TerritoryCancelAnimation(territoryIndex, fadeOutDuration); } /// /// Specifies if a given territory is visible. /// public void TerritorySetVisible(int territoryIndex, bool visible) { if (!ValidTerritoryIndex(territoryIndex)) return; territories[territoryIndex].visible = visible; if (territoryIndex == _territoryLastOverIndex) { ClearLastOver(); } needUpdateTerritories = true; issueRedraw = RedrawType.Full; } /// /// Returns true if territory is visible /// public bool TerritoryIsVisible(int territoryIndex) { if (territoryIndex < 0 || territoryIndex >= territories.Count) return false; return territories[territoryIndex].visible; } /// /// Specifies if a given territory is neutral. /// public void TerritorySetNeutral(int territoryIndex, bool neutral) { if (territoryIndex < 0 || territoryIndex >= territories.Count) return; territories[territoryIndex].neutral = neutral; needUpdateTerritories = true; issueRedraw = RedrawType.Full; } /// /// Returns true if territory is neutral /// public bool TerritoryIsNeutral(int territoryIndex) { if (territoryIndex < 0 || territoryIndex >= territories.Count) return false; return territories[territoryIndex].neutral; } /// /// Specifies the color of the territory borders. /// public void TerritorySetFrontierColor(int territoryIndex, Color color) { if (territoryIndex < 0 || territoryIndex >= territories.Count) return; Territory terr = territories[territoryIndex]; if (terr.frontierColor != color) { terr.frontierColor = color; DrawTerritoryFrontiers(); } } /// /// Creates a gameobject with the frontier for the given territory. Optionally, the frontier could be limited to those segments adjacent to another territory. /// /// public GameObject TerritoryDrawFrontier(Territory territory, int adjacentTerritoryIndex = -1, Material material = null, Color color = default(Color), float thickness = 0) { int territoryIndex = TerritoryGetIndex(territory); return TerritoryDrawFrontier(territoryIndex, adjacentTerritoryIndex, material, color, thickness); } /// /// Creates a gameobject with the frontier for the given territory. Optionally, the frontier could be limited to those segments adjacent to another territory. /// /// public GameObject TerritoryDrawFrontier(int territoryIndex, int adjacentTerritoryIndex = -1, Material material = null, Color color = default(Color), float thickness = 0) { if (needGenerateMap || needResortCells || issueRedraw != RedrawType.None) { CheckGridChanges(); } TerritoryMesh tm = new TerritoryMesh { territoryIndex = territoryIndex }; if (!GenerateTerritoryMesh(tm, true, adjacentTerritoryIndex)) return null; if (material == null) { if (tm.territoryIndex < 0) { material = territoriesDisputedMat; } else { if (territoryIndex >= territories.Count) return null; Color frontierColor = territories[tm.territoryIndex].frontierColor; if (frontierColor.a == 0 && frontierColor.r == 0 && frontierColor.g == 0 && frontierColor.b == 0) { material = territoriesMat; } else { material = GetFrontierColorMaterial(frontierColor); } } } material = Instantiate(material); material.renderQueue--; // ensure it writes to stencil before normal territory material if (color != default(Color)) { material.color = color; } if (thickness > 0) { UpdateMaterialTerritoryThickness(material, thickness); } else { thickness = _territoryFrontiersThickness; } bool useVertexDisplacement = thickness > 1f & !canUseGeometryShaders; Transform root = CheckTerritoriesCustomFrontiersRoot(); GameObject go = DrawTerritoryFrontier(tm, material, root, TERRITORY_FRONTIER_NAME, useVertexDisplacement); Territory territory = territories[territoryIndex]; if (territory.customFrontiersGameObject != null) DestroyImmediate(territory.customFrontiersGameObject); territory.customFrontiersGameObject = go; return go; } /// /// Hides all territories interior boders /// public void TerritoryHideInteriorBorders() { if (territoryInteriorBorderLayer != null) { DestroyImmediate(territoryInteriorBorderLayer.gameObject); } } /// /// Hides territory border of a territory /// /// -1 will destroy all interior borders for the territory public void TerritoryHideInteriorBorder(int territoryIndex, int regionIndex = -1) { if (!ValidTerritoryIndex(territoryIndex)) return; Territory territory = territories[territoryIndex]; if (regionIndex >= 0) { if (!ValidTerritoryIndex(territoryIndex, regionIndex)) return; foreach (Region region in territory.regions) { if (region.customBorderGameObject != null) { DestroyImmediate(region.customBorderGameObject); } } return; } { Region region = territory.regions[regionIndex]; if (region.customBorderGameObject != null) { DestroyImmediate(region.customBorderGameObject); } } } /// /// Creates a gameobject with the interior border for the given territory with optional padding and thickness. /// /// public GameObject TerritoryDrawInteriorBorder(int territoryIndex, float padding, float thickness, Color color = default(Color), Color secondColor = default(Color), int regionIndex = 0, float animationSpeed = 0, bool includeEnclaves = false) { if (!ValidTerritoryIndex(territoryIndex, regionIndex)) return null; Territory territory = territories[territoryIndex]; return TerritoryDrawInteriorBorder(territory, padding, thickness, color, secondColor, regionIndex, animationSpeed, includeEnclaves); } public GameObject ForceDrawTerritoryDrawInteriorBorder(Territory territory, float padding, float thickness, Color color = default(Color), Color secondColor = default(Color), int regionIndex = 0, float animationSpeed = 0, bool includeEnclaves = false) { return TerritoryDrawInteriorBorder(territory, padding, thickness, color, secondColor, regionIndex, animationSpeed, includeEnclaves); } /// /// Creates a gameobject with the interior border for the given territory with optional padding and thickness. /// /// Draw additional interior borders for other regions contained inside this territory region /// public GameObject TerritoryDrawInteriorBorder(Territory territory, float padding = -0.7f, float thickness = 3f, Color color = default(Color), Color secondColor = default(Color), int regionIndex = 0, float animationSpeed = 0, bool includeEnclaves = false) { GetInteriorBorderColors(territory, ref color, ref secondColor); GameObject border = TerritoryDrawInteriorBorderSingle(territory, padding, thickness, color, secondColor, regionIndex, animationSpeed); if (border == null) return null; // Check other regions inside this region if (includeEnclaves) { Region thisRegion = territory.regions[regionIndex]; int territoryCount = territories.Count; for (int k = 0; k < territoryCount; k++) { Territory otherTerritory = territories[k]; if (otherTerritory == territory) continue; int otherRegionCount = otherTerritory.regions.Count; for (int r = 0; r < otherRegionCount; r++) { Region otherRegion = otherTerritory.regions[r]; if (thisRegion.ContainsRegion(otherRegion)) { GameObject enclaveBorder = TerritoryDrawInteriorBorderSingle(otherTerritory, -padding, thickness, secondColor, color, r, animationSpeed); enclaveBorder.transform.SetParent(border.transform); } } } } return border; } void GetInteriorBorderColors(Territory territory, ref Color color, ref Color secondColor) { if (color == default) { color = territory.fillColor; } if (secondColor == default) { secondColor = territory.fillColor; secondColor.r *= 0.5f; secondColor.g *= 0.5f; secondColor.b *= 0.5f; secondColor.a = 0; } } /// /// Creates a gameobject with the interior border for the given territory with optional padding and thickness. /// /// GameObject TerritoryDrawInteriorBorderSingle(Territory territory, float padding = -0.7f, float thickness = 3f, Color color = default(Color), Color secondColor = default(Color), int regionIndex = 0, float animationSpeed = 0) { if (territory == null) return null; if (needGenerateMap || needResortCells || issueRedraw != RedrawType.None) { CheckGridChanges(); } if (regionIndex < 0 || territory.regions == null || regionIndex >= territory.regions.Count) return null; TerritoryMesh tm = new TerritoryMesh(); if (!GenerateRegionMesh(tm, territory.regions[regionIndex], thickness)) return null; Material material = Instantiate(territoriesGradientMat); GetInteriorBorderColors(territory, ref color, ref secondColor); material.color = color; material.SetColor(ShaderParams.TerritorySecondColor, secondColor); material.renderQueue += _territoryInteriorBorderOnTop ? 3 : 1; // ensure it draws behind regular territory frontiers material.SetFloat(ShaderParams.TerritoryAnimationSpeed, animationSpeed); material.EnableKeyword(ShaderParams.SKW_GRADIENT); material.DisableKeyword(ShaderParams.SKW_NEAR_CLIP_FADE); SetBlend(territoriesThickHackMat); UpdateMaterialTerritoryThickness(material, thickness); territoryInteriorBorderLayer = transform.Find(TERRITORIES_CUSTOM_FRONTIERS_ROOT); if (territoryInteriorBorderLayer == null) { GameObject rootGO = new GameObject(TERRITORIES_CUSTOM_FRONTIERS_ROOT); disposalManager.MarkForDisposal(rootGO); rootGO.layer = gameObject.layer; territoryInteriorBorderLayer = rootGO.transform; territoryInteriorBorderLayer.SetParent(transform, false); territoryInteriorBorderLayer.localRotation = Quaternion.Euler(Misc.Vector3zero); } territoryInteriorBorderLayer.gameObject.SetActive(true); territoryInteriorBorderLayer.localPosition = new Vector3(0, 0, -0.001f * _gridMinElevationMultiplier); GameObject go = DrawTerritoryFrontier(tm, material, territoryInteriorBorderLayer, TERRITORY_INTERIOR_BORDER_NAME, useVertexDisplacementForTerritoryThickness: true, usesGradient: true, thickness, padding); Region region = territory.regions[regionIndex]; if (region.customBorderGameObject != null) DestroyImmediate(region.customBorderGameObject); region.customBorderGameObject = go; return go; } /// /// Returns the territory object under position in local coordinates /// public Territory TerritoryGetAtPosition(Vector2 localPosition) { return GetTerritoryAtPoint(localPosition, false); } /// /// Returns the territory object under position in local or worldSpace coordinates /// public Territory TerritoryGetAtPosition(Vector3 position, bool worldSpace) { return GetTerritoryAtPoint(position, worldSpace); } /// /// Gets the territory's center position in world space. /// /// The accuracy of the algorithm. Defaults to betterCentroId which is slower but more accurate. public Vector3 TerritoryGetPosition(int territoryIndex, CentroidType centroidType = CentroidType.BetterCentroid, bool worldSpace = true, int regionIndex = 0) { if (!ValidTerritoryIndex(territoryIndex)) return Misc.Vector3zero; Vector3 territoryCenter; if (centroidType == CentroidType.BetterCentroid) { territoryCenter = territories[territoryIndex].GetBetterCentroid(regionIndex); } else if (centroidType == CentroidType.Centroid) { territoryCenter = territories[territoryIndex].GetCentroid(regionIndex); } else { territoryCenter = territories[territoryIndex].scaledCenter; } if (worldSpace) { territoryCenter = GetWorldSpacePosition(territoryCenter); } return territoryCenter; } /// /// Gets the territory's center position in world space. /// /// The accuracy of the algorithm. Defaults to betterCentroId which is slower but more accurate. public Vector3 TerritoryGetPosition(Territory territory, CentroidType centroidType = CentroidType.BetterCentroid, bool worldSpace = true, int regionIndex = 0) { int territoryIndex = TerritoryGetIndex(territory); return TerritoryGetPosition(territoryIndex, centroidType, worldSpace, regionIndex); } /// /// Returns the rect enclosing the territory in world space /// public Bounds TerritoryGetRectWorldSpace(int territoryIndex, int regionIndex = 0) { if (!ValidTerritoryIndex(territoryIndex, regionIndex)) return new Bounds(Misc.Vector3zero, Misc.Vector3zero); Rect rect = territories[territoryIndex].regions[regionIndex].rect2D; Vector3 min = GetWorldSpacePosition(rect.min); Vector3 max = GetWorldSpacePosition(rect.max); Bounds bounds = new Bounds((min + max) * 0.5f, max - min); return bounds; } /// /// Returns the number of vertices of the territory /// public int TerritoryGetVertexCount(int territoryIndex, int regionIndex = 0) { if (!ValidTerritoryIndex(territoryIndex, regionIndex)) return 0; return territories[territoryIndex].regions[regionIndex].points.Count; } /// /// Returns the world space position of the vertex of a territory /// public Vector3 TerritoryGetVertexPosition(int territoryIndex, int vertexIndex, int regionIndex = 0) { if (!ValidTerritoryIndex(territoryIndex, regionIndex)) return Misc.Vector3zero; Vector2 localPosition = territories[territoryIndex].regions[regionIndex].points[vertexIndex]; return GetWorldSpacePosition(localPosition); } /// /// Returns the shape/surface gameobject of the territory. /// /// The get game object. /// Cell index. public GameObject TerritoryGetGameObject(int territoryIndex, int regionIndex) { if (!ValidTerritoryIndex(territoryIndex, regionIndex)) return null; Territory territory = territories[territoryIndex]; if (territory == null) return null; if (territory.regions[regionIndex].surfaceGameObject != null) { return territory.regions[regionIndex].surfaceGameObject; } GameObject go = TerritoryToggleRegionSurface(territoryIndex, true, Misc.ColorNull); TerritoryToggleRegionSurface(territoryIndex, false, Misc.ColorNull); return go; } /// /// Automatically generates territories based on the different colors included in the texture. /// /// This color won't generate any texture. public void CreateTerritories(Texture2D texture, Color neutral, bool hideNeutralCells = false) { if (texture == null || cells == null) return; List dsColors = new List(); Dictionary dsColorDict = new Dictionary(); int cellCount = cells.Count; Color[] colors; try { colors = texture.GetPixels(); } catch { Debug.Log("Texture used to create territories is not readable. Check import settings."); return; } for (int k = 0; k < cellCount; k++) { Cell cell = cells[k]; if (cell == null) continue; cell.territoryIndex = -1; if (!cell.visible) continue; Vector2 uv = cell.center; uv.x += 0.5f; uv.y += 0.5f; int x = (int)(uv.x * texture.width); int y = (int)(uv.y * texture.height); int pos = y * texture.width + x; if (pos < 0 || pos >= colors.Length) continue; Color pixelColor = colors[pos]; int territoryIndex; if (!dsColorDict.TryGetValue(pixelColor, out territoryIndex)) { dsColors.Add(pixelColor); territoryIndex = dsColors.Count - 1; dsColorDict[pixelColor] = territoryIndex; } cell.territoryIndex = (short)territoryIndex; if (territoryIndex >= MAX_TERRITORIES - 1) break; } needUpdateTerritories = true; if (dsColors.Count > 0) { _numTerritories = dsColors.Count; _showTerritories = true; territories.Clear(); for (int c = 0; c < _numTerritories; c++) { Territory territory = new Territory(c.ToString()); Color territoryColor = dsColors[c]; if (territoryColor.r != neutral.r || territoryColor.g != neutral.g || territoryColor.b != neutral.b) { territory.fillColor = territoryColor; } else { territory.fillColor = new Color(0, 0, 0, 0); territory.visible = false; } // Add cells to territories for (int k = 0; k < cellCount; k++) { Cell cell = cells[k]; if (cell.territoryIndex == c) { territory.cells.Add(cell); territory.center += cell.center; } } if (territory.cells.Count > 0) { territory.center /= territory.cells.Count; // Ensure center belongs to territory Cell cellAtCenter = CellGetAtPosition(territory.center); if (cellAtCenter != null && cellAtCenter.territoryIndex != c) { territory.center = territory.cells[0].center; } } territories.Add(territory); } isDirty = true; issueRedraw = RedrawType.Full; Redraw(); } } /// /// Scales the gameobject of a colored/textured surface /// public void TerritoryScaleSurface(int territoryIndex, float scale, int regionIndex = 0) { if (!ValidTerritoryIndex(territoryIndex, regionIndex)) return; Territory territory = territories[territoryIndex]; GameObject surf = territory.regions[regionIndex].surfaceGameObject; ScaleSurface(surf, territory.center, scale); } /// /// Exports all territories as independent meshes /// public void ExportTerritoriesMesh() { int territoryCount = territories.Count; for (int t = 0; t < territoryCount; t++) { Territory terr = territories[t]; if (terr.regions == null) continue; int regionCount = terr.regions.Count; for (int tr = 0; tr < regionCount; tr++) { ExportTerritoryMesh(t, tr); } } } /// /// Exports the specified territory mesh /// /// /// public void ExportTerritoryMesh(int territoryIndex, int regionIndex = 0) { if (territoryIndex < 0 || territoryIndex >= territories.Count || regionIndex < 0 || regionIndex >= territories[territoryIndex].regions.Count) return; GameObject surf = TerritoryGetGameObject(territoryIndex, regionIndex); if (surf == null) return; MeshFilter mf = surf.GetComponent(); if (mf == null || mf.sharedMesh == null) return; Mesh mesh = mf.sharedMesh; if (mesh != null && (mesh.uv == null || mesh.uv.Length == 0)) { // forces mesh to have UV mappings Material oldMat = territories[territoryIndex].regions[regionIndex].customMaterial; Color color = oldMat != null ? oldMat.color : Misc.ColorNull; surf = TerritoryToggleRegionSurface(territoryIndex, true, color, true, Texture2D.whiteTexture); TerritoryToggleRegionSurface(territoryIndex, false, Misc.ColorNull); mf = surf.GetComponent(); if (mf == null || mf.sharedMesh == null) return; } mesh = Instantiate(mf.sharedMesh); mesh.name = "Territory " + territoryIndex; mesh.hideFlags = 0; GameObject newSurf = new GameObject("Copy of territory " + territoryIndex); newSurf.layer = gameObject.layer; newSurf.transform.position = transform.position; newSurf.transform.rotation = transform.rotation; newSurf.transform.localScale = transform.localScale; mf = newSurf.AddComponent(); mf.mesh = mesh; MeshRenderer mr = newSurf.AddComponent(); mr.material = new Material(Shader.Find("Diffuse")); } /// /// Returns the points that form the frontier of a territory/region (a territory usually has a single region but could include more than one if it gets split) /// Points returned already have the offset and scale of the grid applied /// public List TerritoryGetFrontier(int territoryIndex, int regionIndex) { if (territoryIndex < 0 || territoryIndex >= territories.Count || regionIndex < 0 || regionIndex >= territories[territoryIndex].regions.Count) return null; return territories[territoryIndex].regions[regionIndex].points; } /// /// Create a new territory with a single cell /// /// The newly created territory public Territory TerritoryCreate(Cell cell) { if (cell == null) return null; Territory territory = new Territory(); territories.Add(territory); _numTerritories = territories.Count; int terrIndex = _numTerritories - 1; lastTerritoryLookupCount = -1; CellSetTerritory(cell.index, terrIndex); return territory; } /// /// Create a new territory with a single cell /// /// The newly created territory public Territory TerritoryCreate(int cellIndex) { if (!ValidCellIndex(cellIndex)) return null; return TerritoryCreate(cells[cellIndex]); } /// /// Create a new territory from a list of cells /// /// The newly created territory public Territory TerritoryCreate(List cells) { tempListCells.Clear(); foreach (Cell cell in cells) { if (cell != null) tempListCells.Add(cell.index); } if (tempListCells.Count == 0) return null; return TerritoryCreate(tempListCells); } /// /// Create a new territory from a list of cells /// /// The newly created territory public Territory TerritoryCreate(List cellIndices) { Territory territory = new Territory(); territories.Add(territory); _numTerritories = territories.Count; int terrIndex = _numTerritories - 1; lastTerritoryLookupCount = -1; CellSetTerritory(cellIndices, terrIndex); return territory; } /// /// Removes an existing territory. Cells belonging to the territory will be freed. /// /// True if successful public bool TerritoryDestroy(int territoryIndex) { if (!ValidTerritoryIndex(territoryIndex)) return false; Territory territory = territories[territoryIndex]; foreach (Cell cell in territory.cells) { cell.territoryIndex = -1; } DestroyTerritorySurfaces(territoryIndex); territories.RemoveAt(territoryIndex); // update territory indices for other cells foreach (Cell cell in cells) { if (cell.territoryIndex >= territoryIndex) { cell.territoryIndex--; } } _numTerritories = territories.Count; lastTerritoryLookupCount = -1; needUpdateTerritories = true; issueRedraw = RedrawType.Full; return true; } /// /// Destroys all territories /// public void TerritoryDestroyAll() { foreach (Cell cell in cells) { cell.territoryIndex = -1; } DestroyTerritorySurfaces(); TerritoryHideInteriorBorders(); territories.Clear(); _numTerritories = 0; lastTerritoryLookupCount = -1; needUpdateTerritories = true; issueRedraw = RedrawType.IncrementalTerritories; } #endregion } }