TH1/Unity/Assets/Scripts/TH1_Logic/Map/MapGenerator.cs
2026-06-06 03:04:23 +08:00

1519 lines
67 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* @Author: 白哉
* @Description: 地图生成器
* @Date: 2025年04月01日 星期二 11:04:43
* @Modify:
*/
using UnityEngine;
using System;
using System.Collections.Generic;
using System.Linq;
using Logic.Pool;
using RuntimeData;
using TH1_Logic.Core;
using Unity.VisualScripting;
using Random = UnityEngine.Random;
namespace Logic
{
public enum MapWaterType
{
Dryland, // 0.05
Lakes, // 0.15
Pangea, // 0.3
Continents, // 0.5
Archipelago, // 0.7
WaterWorld // 0.9
}
public static class MapWaterTypeHelper
{
public static float GetLandThreshold(MapWaterType waterType)
{
return waterType switch
{
MapWaterType.Dryland => 0.05f,
MapWaterType.Lakes => 0.15f,
MapWaterType.Pangea => 0.3f,
MapWaterType.Continents => 0.5f,
MapWaterType.Archipelago => 0.7f,
MapWaterType.WaterWorld => 0.9f,
_ => 0.3f
};
}
}
public class MapGenerator
{
private static List<GridData> _aroundBuf;
private Main _main;
private MapData _mapData;
//海陆层参数,柏林噪音
private const float Scale = 20f; // 噪声的缩放因子
private float _landThreshold; // 陆地的阈值
private float _mountainThreshold = 0.7f; // 山地的阈值
private float[,] _heightMap;
private const int SmoothIterations = 3; //平滑次数
private const float NearbyEnemyCapitalRateForRealPlayer = 0.15f;
private const int NearbyEnemyCapitalDistance = 3;
private uint _width, _height;
private List<MapPosition> _tribes;
//用来避免treasure连着生成
private List<MapPosition> _treasure;
private List<MapPosition> _innerCityGrid;
private List<MapPosition> _outerCityGrid;
public List<MapPosition> PlayerCivOri;
List<Vector2> dir4 = new List<Vector2>() { Vector2.up, Vector2.right, Vector2.down, Vector2.left };
List<Vector2> dir8 = new List<Vector2>() { Vector2.down , Vector2.right , Vector2.left , Vector2.up ,Vector2.left+Vector2.up , Vector2.up+Vector2.right, Vector2.down+Vector2.right,Vector2.down+Vector2.left };
// 初始化地图
public MapGenerator(Main main,MapData mapData)
{
_main = main;
_mapData = mapData;
Table.Instance.GeoDataAssets.OnMapGenerator();
}
public void GenerateMap(MapData mapData)
{
_width = mapData.MapConfig.Width;
_height = mapData.MapConfig.Height;
_heightMap = new float[_width, _height];
if (DebugCenter.Instance.DebugLandThreshold >= 0)
_landThreshold = DebugCenter.Instance.DebugLandThreshold;
else
_landThreshold = MapWaterTypeHelper.GetLandThreshold(mapData.MapConfig.WaterType);
_tribes = new List<MapPosition>();
_treasure = new List<MapPosition>();
_innerCityGrid = new List<MapPosition>();
_outerCityGrid = new List<MapPosition>();
//用柏林噪音生成地图高度
GenerateHeightMap();
//平滑地图
SmoothHeightMap();
//归一化处理地图,均匀分布0~1范围
NormalizeHeightMap();
//Dryland模式强制将所有水域格子抬升为陆地地图上不存在任何水域
if (mapData.MapConfig.WaterType == MapWaterType.Dryland)
{
for (var x = 0; x < _width; x++)
for (var y = 0; y < _height; y++)
if (_heightMap[x, y] <= _landThreshold)
_heightMap[x, y] = _landThreshold + 0.01f;
}
//在连续的大片海洋上生成岛屿
GenerateIsland();
//在连续的陆地上生成湖泊
GenerateLakes();
//生成所有tribe
GenerateTribes();
//创建文明摇篮城市(每个玩家的原始首都)
GenerateCradleCity(mapData);
//初始化所有Grid信息
InitGridMapData(mapData);
//修复摇篮城市附近的地形保底和资源保底
CivTerrainFeatureCountControl(mapData);
//生成地脉(所有资源确定后执行)
GenerateLeyLines(mapData); //TODO 暂时屏蔽地脉生成
//生成所有GeoInf
Table.Instance.GeoDataAssets.InitGeoData(mapData);
}
//在MapRenderer初始化之后再进行一些mapgenerator的其他操作
public void GenerateMapAfterMapRenderer(MapData mapData)
{
//初始化所有玩家的初始视野信息
InitAllPlayerSight(mapData);
//初始化Debug的参数
InitDebugInfo(mapData);
//初始化所有玩家的特殊领地技能(目前主要是kaguya)
InitPlayerSpecialSkill(mapData);
}
//海陆层使用的地形高度数据
private void GenerateHeightMap()
{
for (var x = 0; x < _width; x++)
{
for (var y = 0; y < _height; y++)
{
float seda = new System.Random().Next(1, 30);
float sedb = new System.Random().Next(2, 40);
// 使用种子值来改变噪声的生成,保证每次生成的噪声不同
var xCoord = (float)x / _width * Scale / seda;
var yCoord = (float)y / _height * Scale / sedb;
_heightMap[x, y] = Mathf.PerlinNoise(xCoord, yCoord);
//Debug.Log($"sed = {seda}\\{sedb} xC = {xCoord} yC = {yCoord} height = {heightMap[x, y]}");
}
}
}
//海陆层使用的地形平滑工具
private void SmoothHeightMap()
{
for (var iteration = 0; iteration < SmoothIterations; iteration++)
{
var newHeightMap = new float[_width, _height];
for (int x = 0; x < _width; x++)
{
for (int y = 0; y < _height; y++)
{
// 计算该位置的邻域平均值
var average = GetAverageHeight(x, y);
newHeightMap[x, y] = average;
}
}
_heightMap = newHeightMap;
}
}
// 获取该位置的邻域平均高度值
private float GetAverageHeight(int x, int y)
{
var total = 0f;
var count = 0;
// 获取周围的邻域高度值
for (var dx = -1; dx <= 1; dx++)
{
for (var dy = -1; dy <= 1; dy++)
{
var nx = x + dx;
var ny = y + dy;
if (nx < 0 || nx >= _width || ny < 0 || ny >= _height)
continue;
total += _heightMap[nx, ny];
count++;
// 确保在有效范围内
}
}
return total / count;
}
//归一化处理高度
private void NormalizeHeightMap()
{
var minHeight = Mathf.Infinity;
var maxHeight = -Mathf.Infinity;
// 计算最小值和最大值
for (var x = 0; x < _width; x++)
{
for (int y = 0; y < _height; y++)
{
if (_heightMap[x, y] < minHeight)
minHeight = _heightMap[x, y];
if (_heightMap[x, y] > maxHeight)
maxHeight = _heightMap[x, y];
}
}
// 将高度图归一化到 [0, 1] 范围
for (var x = 0; x < _width; x++)
{
for (var y = 0; y < _height; y++)
{
_heightMap[x, y] = (_heightMap[x, y] - minHeight) / (maxHeight - minHeight);
}
}
}
//生成湖泊
private void GenerateLakes()
{
//Step #0 根据WaterType决定河流上限
var waterType = _mapData.MapConfig.WaterType;
if (waterType == MapWaterType.Dryland) return; // 干旱:不生成河流
//Step #1 每次寻找一个最高的山,开始制造河流,每次流往附近最低的格子,直到格子周围没有比自己更低的格子,停下。
//每次找的山头周围2格内不能有水流
List<(Vector2Int pos, float height)> sorth = GetSortedTilesByHeight(_heightMap);
//目前先暂定river最多占掉0.05的陆地格子
int riverCount = (int)(sorth.Count * 0.05);
for (int i = sorth.Count - 1; i >= 0; i--)
if(sorth[i].height <= _landThreshold)
{
riverCount = (int)((sorth.Count - i) * 0.05);
if (i * 2 < _width * _height)
{
//30%的概率是干旱图70%的概率是多河图
if (Random.Range(0f, 1f) < 0.7)
{
riverCount = (int)((sorth.Count - i) * 0.2);
//Debug.Log("More River!!");
}
}
break;
}
// 湖泊模式最多生成1条河流的长度
if (waterType == MapWaterType.Lakes)
riverCount = Mathf.Min(riverCount, 1);
for(int i = sorth.Count - 1;i >= 0 && riverCount > 0; i-- )
{
//寻找下一个河流源头
if (!CheckGridRiverHead(sorth[i].pos.x, sorth[i].pos.y, 2)) continue;
var x = sorth[i].pos.x;
var y = sorth[i].pos.y;
for (;riverCount > 0;)//p用来保底
{
//当前水流x,y,找四个方向最低的
int tx = x, ty = y;
if (x - 1 >= 0 && _heightMap[x - 1, y] < _heightMap[tx, ty] && _heightMap[x - 1, y] > _landThreshold) {tx = x - 1; ty = y; }
if (x + 1 < _width && _heightMap[x + 1, y] < _heightMap[tx, ty] && _heightMap[x + 1, y] > _landThreshold) {tx = x + 1; ty = y; }
if (y - 1 >= 0 && _heightMap[x, y - 1] < _heightMap[tx, ty]&& _heightMap[x, y - 1] > _landThreshold) {tx = x; ty = y - 1; }
if (y + 1 < _height && _heightMap[x, y + 1] < _heightMap[tx, ty] && _heightMap[x, y + 1] > _landThreshold) {tx = x; ty = y + 1; }
//让当前格子变为水流
_heightMap[x, y] = 0;
riverCount--;
//如果不能再流
if (tx == x && ty == y) break;
x = tx;
y = ty;
}
}
}
//给生成湖泊用的函数 给格子排序高度
public static List<(Vector2Int pos, float height)> GetSortedTilesByHeight(float[,] heights)
{
int width = heights.GetLength(0);
int height = heights.GetLength(1);
// 使用 LINQ 将二维数组展平、转换、排序并生成列表,一行搞定。
return Enumerable.Range(0, width)
.SelectMany(x => Enumerable.Range(0, height)
.Select(y => (pos: new Vector2Int(x, y), height: heights[x, y])))
.OrderBy(tile => tile.height)
.ToList();
}
//给生成湖泊用的函数 确认一个点周围x范围内有没有水
public bool CheckGridRiverHead(int X,int Y,int r)
{
for (var x = X - r; x < X + r; x++)
for (var y = Y - r; y < Y + r; y++)
{
if(x < 0 || x >= _width || y < 0 || y >= _height) return false;
if (_heightMap[x, y] <= _landThreshold) return false;
}
return true;
}
//在连续大片海洋中生成岛屿
private void GenerateIsland()
{
//WaterWorld模式下大幅降低岛屿生成概率10%其他模式50%
int islandChance = _mapData.MapConfig.WaterType == MapWaterType.WaterWorld ? 10 : 50;
for (var x = 1; x < _width - 1; x++)
for (var y = 1; y < _height - 1; y++)
{
var t = GenerateTerrain(x, y);
var canBeIsland = false;
if (t == TerrainType.DeepSea)
{
canBeIsland = true;
var landNearby = 0;
for (var i = -2; i <= 2; i++)
for (var j = -2; j <= 2; j++)
if (x + i >= 0 && x + i < _width && y + j >= 0 && y + j < _height &&
GenerateTerrain(x + i, y + j) != TerrainType.DeepSea)
landNearby++;
if (landNearby > 9)
canBeIsland = false;
}
if (canBeIsland && UnityEngine.Random.Range(0, 100) < islandChance)
_heightMap[x, y] = _landThreshold + 1;
}
}
//生成所有tirbes 以及对应的innergrids
private void GenerateTribes()
{
var cant = new bool[_width, _height];
var count = _width * _height; //剩余多少格子能用
//WaterWorld模式下按陆地格子数计算村庄数量避免小面积陆地上村庄过密
//同时保证最少有玩家数*2个村庄位确保所有阵营能正常生成
int cityNum;
if (_mapData.MapConfig.WaterType == MapWaterType.WaterWorld)
{
int landTiles = 0;
for (var x = 0; x < _width; x++)
for (var y = 0; y < _height; y++)
if (_heightMap[x, y] > _landThreshold)
landTiles++;
int minCities = (int)_mapData.MapConfig.PlayerCount * 2;
cityNum = Mathf.Max(landTiles / 18, minCities);
}
else
{
cityNum = (int)(count / 12);
}
Array.Clear(cant, 0, cant.Length);
for (var x = 0; x < _width; x++)
for (var y = 0; y < _height; y++)
if (_heightMap[x, y] <= _landThreshold || x == 0 || x == _width - 1 || y == 0 || y == _height - 1)
{
cant[x, y] = true;
count--;
}
for (int c = 0; count > 0 && c <= cityNum; c++)
{
var choose = (int)UnityEngine.Random.Range(1, count); //每次随机一个可用的格子(序号)
//Debug.Log("this time " + count + " choose : " + choose);
var tmp = 0;
for (int x = 0; x < _width; x++)
{
for (int y = 0; y < _height; y++)
{
if (cant[x, y])
continue;
tmp++;
if (tmp != choose)
continue;
_tribes.Add(new MapPosition(x,y));
for (var xx = -2; xx <= 2; xx++)
for (var yy = -2; yy <= 2; yy++)
{
var nx = x + xx;
var ny = y + yy;
if (nx < 0 || nx >= _width || ny < 0 || ny >= _height || cant[nx, ny])
continue;
cant[nx, ny] = true; // 把新城市方圆2距离的格子都ban了
count--;
}
break;
}
if (tmp == choose) break;
}
if (count <= 0) break;
}
foreach (var cent in _tribes)
{
for(int i = cent.X - 1; i<= cent.X + 1 ; i++)
for(int j = cent.Y - 1; j<= cent.Y + 1 ; j++)
if(i >= 0 && i < _width && j >= 0 && j < _height)
_innerCityGrid.Add(new MapPosition(i,j));
}
foreach (var cent in _tribes)
{
for(int i = cent.X - 2; i<= cent.X + 2 ; i++)
for(int j = cent.Y - 2; j<= cent.Y + 2 ; j++)
if(i >= 0 && i < _width && j >= 0 && j < _height)
_outerCityGrid.Add(new MapPosition(i,j));
}
//List<Vector2Int> LandCenters = new List<Vector2Int>();
//_map.cityCount = map.cityCenters.Count;
}
private void InitGridMapData(MapData mapData)
{
//标记innerCity和OnterCity
for (int x = 0; x < _width; x++)
{
for (int y = 0; y < _height; y++)
{
Vector2 curV2 = new Vector2(x, y);
// 生成文明层
var civId = GenerateCivilization(mapData, x, y);
CivEnum civEnum = (CivEnum)(civId + 1);
InnerOuterCity innerOuterCity = InnerOuterCity.Edge;
if (_innerCityGrid.Contains(new MapPosition(x, y)))
innerOuterCity = InnerOuterCity.InnerCity;
else if (_outerCityGrid.Contains(new MapPosition(x, y)))
innerOuterCity = InnerOuterCity.OuterCity;
// 生成海陆层(陆地、浅海、深海)
var terrain = GenerateTerrain(x, y, civEnum);
// 生成地形层(是否有山)
var feature = (terrain == TerrainType.Land) ? GenerateFeature(x, y, civEnum) : TerrainFeature.None;
//如果是摇篮城市terrain设置为road
if (mapData.GridMap.GetGridDataByPos(x, y, out var gg)
&& mapData.GetCityDataByGid(gg.Id, out var cc))
feature = TerrainFeature.Road;
// 生成植被层(是否有树木)
var vegetation = (feature == TerrainFeature.None && terrain == TerrainType.Land)
? GenerateVegetation(x, y, civEnum)
: Vegetation.None;
// 生成资源层
var resource = GenerateResource(x, y, terrain, feature, vegetation, civEnum, innerOuterCity);
//生成四个角的塔
if (HasTower(x, y))
{
feature = TerrainFeature.None;
resource = ResourceType.Tower;
vegetation = Vegetation.None;
}
// 设置这个地块的所有层信息
//Debug.Log(map.grid[x,y]);
if (mapData.GridMap.GetGridDataByPos(x, y, out GridData g))
g.SetGridData(terrain, feature, vegetation, resource, civId);
}
}
}
private void CivTerrainFeatureCountControl(MapData mapData)
{
foreach (var city in mapData.CityMap.CityList)
{
var grid = city.Grid(mapData);
var player = city.Player(mapData);
if (grid == null) continue;
if(player == null) continue;
dir8 = new List<Vector2>()
{
Vector2.up, Vector2.down, Vector2.left, Vector2.right, Vector2.up + Vector2.right,
Vector2.up + Vector2.left, Vector2.down + Vector2.left, Vector2.down + Vector2.right
};
//随机打乱dir8的顺序使得每次强制生成保底的时候没有明显规律
int n = dir8.Count;
while (n > 0)
{
n--;
int k = Random.Range(0,n + 1);
(dir8[k], dir8[n]) = (dir8[n], dir8[k]);
}
dir4 = new List<Vector2>()
{ Vector2.up, Vector2.down, Vector2.left, Vector2.right, Vector2.up + Vector2.right };
//处理land保底
if (Table.Instance.PlayerDataAssets.GetLandformCount(TerrainType.Land,player.CivEnum, out var landCount))
{
var cur = grid.Pos.V2();
int count = 0;
foreach (var dir in dir8)
{
if (!mapData.GridMap.GetGridDataByV2(cur + dir, out var tgrid)) continue;
if (tgrid.Terrain == TerrainType.Land) count++;
}
//如果land太少 ,要补充
if (count < landCount)
{
foreach (var dir in dir8)
{
if (count >= landCount) break;
if (!mapData.GridMap.GetGridDataByV2(cur + dir, out var tgrid)) continue;
if (tgrid.Terrain != TerrainType.Land)
{
//将该海洋格子改为陆地格子
if (tgrid.Resource == ResourceType.Tower) continue; // 保护结界塔
tgrid.Terrain = TerrainType.Land;
tgrid.Resource = ResourceType.None;
//将该格子上下左右dir4方向的深海都变成浅海
foreach (var dirp in dir4)
{
if (!mapData.GridMap.GetGridDataByV2(cur + dir + dirp, out var ttgrid)) continue;
if (ttgrid.Terrain == TerrainType.DeepSea)
ttgrid.Terrain = TerrainType.ShallowSea;
}
count++;
}
}
}
}
//处理mountain保底
if (Table.Instance.PlayerDataAssets.GetLandformCount(TerrainFeature.Mountain,player.CivEnum, out var mountainCount))
{
var cur = grid.Pos.V2();
int count = 0;
foreach (var dir in dir8)
{
if (!mapData.GridMap.GetGridDataByV2(cur + dir, out var tgrid)) continue;
if (tgrid.Feature == TerrainFeature.Mountain) count++;
}
//Debug.Log($"原来只有{count}座山脉");
//如果mountain太少 ,要补充
if (count < mountainCount)
{
foreach (var dir in dir8)
{
if (count >= mountainCount) break;
if (!mapData.GridMap.GetGridDataByV2(cur + dir, out var tgrid)) continue;
if (tgrid.Resource == ResourceType.Tower) continue;
if (tgrid.Terrain == TerrainType.Land && tgrid.Feature != TerrainFeature.Mountain)
{
//将该陆地格子改山脉格子
tgrid.Feature = TerrainFeature.Mountain;
tgrid.Vegetation = Vegetation.None;
//Debug.Log($"新增山脉位置:{cur+dir}");
count++;
}
}
}
//Debug.Log($"新增到{count}座山脉");
}
//处理Tree保底
if (Table.Instance.PlayerDataAssets.GetLandformCount(Vegetation.Trees,player.CivEnum, out var treeCount))
{
var cur = grid.Pos.V2();
int count = 0;
foreach (var dir in dir8)
{
if (!mapData.GridMap.GetGridDataByV2(cur + dir, out var tgrid)) continue;
if (tgrid.Vegetation == Vegetation.Trees) count++;
}
//Debug.Log($"原来只有{count}个森林");
//如果tree太少 ,要补充
if (count < treeCount)
{
foreach (var dir in dir8)
{
if (count >= treeCount) break;
if (!mapData.GridMap.GetGridDataByV2(cur + dir, out var tgrid)) continue;
if (tgrid.Resource == ResourceType.Tower) continue;
if (tgrid.Terrain == TerrainType.Land && tgrid.Feature != TerrainFeature.Mountain && tgrid.Vegetation != Vegetation.Trees && tgrid.Resource == ResourceType.None)
{
//将该陆地格子改山森林格子
tgrid.Vegetation = Vegetation.Trees;
//Debug.Log($"新增1个森林位于{cur+dir}");
count++;
}
}
}
//Debug.Log($"新增后有{count}个森林");
}
//处理Resource保底
var resourceList = new List<ResourceType>() {ResourceType.Fish, ResourceType.Animal, ResourceType.Fruit, ResourceType.Metal, ResourceType.Crop };
foreach (var resource in resourceList)
{
if (Table.Instance.PlayerDataAssets.GetLandformCount(resource,player.CivEnum, innerOuterCity:InnerOuterCity.InnerCity,out var recourceCount))
{
var cur = grid.Pos.V2();
int count = 0;
foreach (var dir in dir8)
{
if (!mapData.GridMap.GetGridDataByV2(cur + dir, out var tgrid)) continue;
if (tgrid.Resource == resource) count++;
}
//Debug.Log($"原来只有{count}个{resource}");
//如果resource太少要补充
if (count < recourceCount)
{
//第一轮:在满足条件的格子上放置资源
foreach (var dir in dir8)
{
if (count >= recourceCount) break;
if (!mapData.GridMap.GetGridDataByV2(cur + dir, out var tgrid)) continue;
if (tgrid.Resource == ResourceType.Tower) continue;
switch (resource)
{
case ResourceType.Fish:
if (tgrid.Terrain != TerrainType.Land && tgrid.Resource != ResourceType.Fish)
{
tgrid.Resource = ResourceType.Fish;
count++;
}
break;
case ResourceType.Fruit:
if (tgrid.Terrain == TerrainType.Land && tgrid.Vegetation != Vegetation.Trees && tgrid.Feature != TerrainFeature.Mountain && tgrid.Resource != ResourceType.Fruit)
{
tgrid.Resource = ResourceType.Fruit;
count++;
}
break;
case ResourceType.Animal:
if (tgrid.Vegetation == Vegetation.Trees && tgrid.Resource != ResourceType.Animal)
{
tgrid.Resource = ResourceType.Animal;
count++;
}
break;
case ResourceType.Metal:
if (tgrid.Feature == TerrainFeature.Mountain && tgrid.Resource != ResourceType.Metal)
{
tgrid.Resource = ResourceType.Metal;
count++;
}
break;
case ResourceType.Crop:
if (tgrid.Terrain == TerrainType.Land && tgrid.Vegetation != Vegetation.Trees && tgrid.Feature != TerrainFeature.Mountain && tgrid.Resource != ResourceType.Crop)
{
tgrid.Resource = ResourceType.Crop;
count++;
}
break;
default:
break;
}
}
//第二轮:如果第一轮没有凑够,强制改造地形来放置资源
if (count < recourceCount)
{
foreach (var dir in dir8)
{
if (count >= recourceCount) break;
if (!mapData.GridMap.GetGridDataByV2(cur + dir, out var tgrid)) continue;
if (tgrid.Resource == ResourceType.Tower) continue;
if (tgrid.Resource == ResourceType.CityCenter) continue;
switch (resource)
{
case ResourceType.Animal:
//Animal需要树林如果当前是陆地平地无树强制种树并放动物
if (tgrid.Terrain == TerrainType.Land && tgrid.Feature != TerrainFeature.Mountain
&& tgrid.Vegetation != Vegetation.Trees && tgrid.Resource != ResourceType.Animal)
{
tgrid.Vegetation = Vegetation.Trees;
tgrid.Resource = ResourceType.Animal;
count++;
}
break;
case ResourceType.Metal:
//Metal需要山脉如果当前是陆地无山强制造山并放矿
if (tgrid.Terrain == TerrainType.Land && tgrid.Feature != TerrainFeature.Mountain
&& tgrid.Resource != ResourceType.Metal)
{
tgrid.Feature = TerrainFeature.Mountain;
tgrid.Vegetation = Vegetation.None;
tgrid.Resource = ResourceType.Metal;
count++;
}
break;
default:
break;
}
}
}
}
//Debug.Log($"新增后有{count}个{resource}");
}
}
}
}
private bool HasTower(int x, int y)
{
if (x == 0 && y == 0) return true;
if (x == 0 && y == _height - 1) return true;
if (x == _width - 1 && y == 0) return true;
if (x == _width - 1 && y == _height - 1) return true;
return false;
}
//生成文明摇篮城市(每个玩家的原始首都)
private void GenerateCradleCity(MapData mapData)
{
//制作文明摇篮地点候选列表
var cityIndices = new System.Collections.Generic.List<MapPosition>();
foreach (var c in _tribes)
{
var nearbyLand = 0;
var cx = (int)c.PosId / 1000;
var cy = (int)c.PosId % 1000;
for (int x = cx - 1; x <= cx + 1; x++)
for (int y = cy - 1; y <= cy + 1; y++)
if (x >= 0 && x < _width && y >= 0 && y < _height && _heightMap[x, y] > _landThreshold)
nearbyLand++;
if (nearbyLand > 0)
cityIndices.Add(new MapPosition(cx,cy));
}
if (cityIndices.Count < mapData.MapConfig.PlayerCount)
{
mapData.MapConfig.PlayerCount = (uint)cityIndices.Count;
// 移除多余的玩家,保留与可用出生点数量一致的玩家
mapData.PlayerMap.TrimPlayerCount(cityIndices.Count);
// 重新初始化结算信息,使其与裁剪后的玩家列表一致
mapData.MatchSettlement.Init(mapData, mapData.MapConfig);
}
PlayerCivOri = GeneratePlayerCivOrigins(mapData, cityIndices);
//建立玩家档案,建立具体城市,建立初始单位
int rk = 0;
foreach (var p in mapData.PlayerMap.PlayerDataList)
{
GridData g;
mapData.GridMap.GetGridDataByPos(PlayerCivOri[rk].X, PlayerCivOri[rk].Y, out g);
//建设一个新城市
CityData c = mapData.AddCityData(g.Id, p.Id);
//如果玩家拥有七河之地科技初始城市等级为4
if (p.TechTree.CheckIfHasTechAtom(TechAtom.KomeijiIndianSevenRiver))
c.Level = 4;
//设置该城市为首都
Main.CityLogic.SetCapital(mapData, c.Id);
//设置该玩家的文明摇篮城市为该城市
p.CradleCityId = c.Id;
//Main.CityLogic.SetCradle(mapData, c.Id);
//TODO grid还没初始化好这里不应该先建立unit的应该在grid初始化结束后建单位
//建立文明的开国单位,并额外赋予初始行动点
if(mapData.AddUnitData(g.Id,c.Id,new UnitFullType(UnitType.Warrior,GiantType.None,0),out var newUnit))
newUnit.SetFullActionPoint();
rk++;
}
}
private List<MapPosition> GeneratePlayerCivOrigins(MapData mapData, List<MapPosition> cityIndices)
{
var playerCount = mapData.PlayerMap.PlayerDataList.Count;
var origins = new List<MapPosition>(playerCount);
for (int i = 0; i < playerCount; i++)
origins.Add(null);
var available = new List<MapPosition>(cityIndices);
var realPlayerIndices = new List<int>();
for (int i = 0; i < playerCount; i++)
if (IsRealPlayerIndex(mapData, i))
realPlayerIndices.Add(i);
ShuffleList(realPlayerIndices);
foreach (var realPlayerIndex in realPlayerIndices)
{
if (UnityEngine.Random.Range(0f, 1f) >= NearbyEnemyCapitalRateForRealPlayer) continue;
TryAssignNearbyEnemyCapital(mapData, origins, available, realPlayerIndex);
}
FillRemainingPlayerCivOrigins(origins, available);
return origins;
}
private bool TryAssignNearbyEnemyCapital(
MapData mapData,
List<MapPosition> origins,
List<MapPosition> available,
int realPlayerIndex)
{
if (realPlayerIndex < 0 || realPlayerIndex >= origins.Count) return false;
var realOrigin = origins[realPlayerIndex];
if (realOrigin == null)
{
var realCandidates = new List<MapPosition>();
foreach (var candidate in available)
if (HasNearbyEnemyCandidate(mapData, origins, available, realPlayerIndex, candidate))
realCandidates.Add(candidate);
if (realCandidates.Count == 0) return false;
realOrigin = SelectBestOriginCandidate(realCandidates, origins);
AssignPlayerCivOrigin(origins, available, realPlayerIndex, realOrigin);
}
if (HasAssignedNearbyEnemy(mapData, origins, realPlayerIndex, realOrigin))
return true;
var enemyIndices = new List<int>();
for (int i = 0; i < origins.Count; i++)
if (origins[i] == null && IsEnemyPlayerIndex(mapData, realPlayerIndex, i))
enemyIndices.Add(i);
if (enemyIndices.Count == 0) return false;
var enemyCandidates = new List<MapPosition>();
foreach (var candidate in available)
if (ChebyshevDistance(realOrigin, candidate) == NearbyEnemyCapitalDistance)
enemyCandidates.Add(candidate);
if (enemyCandidates.Count == 0) return false;
var enemyIndex = enemyIndices[UnityEngine.Random.Range(0, enemyIndices.Count)];
var enemyOrigin = enemyCandidates[UnityEngine.Random.Range(0, enemyCandidates.Count)];
AssignPlayerCivOrigin(origins, available, enemyIndex, enemyOrigin);
return true;
}
private bool HasNearbyEnemyCandidate(
MapData mapData,
List<MapPosition> origins,
List<MapPosition> available,
int realPlayerIndex,
MapPosition realOrigin)
{
for (int i = 0; i < origins.Count; i++)
{
if (!IsEnemyPlayerIndex(mapData, realPlayerIndex, i)) continue;
if (origins[i] != null)
{
if (ChebyshevDistance(realOrigin, origins[i]) == NearbyEnemyCapitalDistance)
return true;
continue;
}
foreach (var candidate in available)
if (candidate != realOrigin && ChebyshevDistance(realOrigin, candidate) == NearbyEnemyCapitalDistance)
return true;
}
return false;
}
private bool HasAssignedNearbyEnemy(
MapData mapData,
List<MapPosition> origins,
int realPlayerIndex,
MapPosition realOrigin)
{
for (int i = 0; i < origins.Count; i++)
{
if (origins[i] == null) continue;
if (!IsEnemyPlayerIndex(mapData, realPlayerIndex, i)) continue;
if (ChebyshevDistance(realOrigin, origins[i]) == NearbyEnemyCapitalDistance)
return true;
}
return false;
}
private void FillRemainingPlayerCivOrigins(List<MapPosition> origins, List<MapPosition> available)
{
for (int i = 0; i < origins.Count; i++)
{
if (origins[i] != null) continue;
if (available.Count == 0) break;
var origin = SelectBestOriginCandidate(available, origins);
AssignPlayerCivOrigin(origins, available, i, origin);
}
}
private void AssignPlayerCivOrigin(
List<MapPosition> origins,
List<MapPosition> available,
int playerIndex,
MapPosition origin)
{
origins[playerIndex] = origin;
available.Remove(origin);
}
private bool IsRealPlayerIndex(MapData mapData, int playerIndex)
{
if (mapData?.PlayerMap?.PlayerDataList == null
|| playerIndex < 0
|| playerIndex >= mapData.PlayerMap.PlayerDataList.Count)
return false;
if (mapData.Net?.Mode == NetMode.Multi)
{
var slot = mapData.MapConfig?.MultiCivs != null && playerIndex < mapData.MapConfig.MultiCivs.Count
? mapData.MapConfig.MultiCivs[playerIndex]
: null;
return slot != null && slot.MemberId != 0 && !slot.IsAI;
}
return playerIndex == 0 || mapData.PlayerMap.PlayerDataList[playerIndex].Id == mapData.PlayerMap.SelfPlayerId;
}
private bool IsEnemyPlayerIndex(MapData mapData, int selfIndex, int targetIndex)
{
if (selfIndex == targetIndex) return false;
if (mapData?.PlayerMap?.PlayerDataList == null) return false;
if (selfIndex < 0 || selfIndex >= mapData.PlayerMap.PlayerDataList.Count) return false;
if (targetIndex < 0 || targetIndex >= mapData.PlayerMap.PlayerDataList.Count) return false;
var selfPlayer = mapData.PlayerMap.PlayerDataList[selfIndex];
var targetPlayer = mapData.PlayerMap.PlayerDataList[targetIndex];
return !mapData.MapConfig.ArePlayersInSameTeam(selfPlayer.Id, targetPlayer.Id);
}
private MapPosition SelectBestOriginCandidate(List<MapPosition> candidates, List<MapPosition> origins)
{
var hasSeed = false;
foreach (var origin in origins)
{
if (origin == null) continue;
hasSeed = true;
break;
}
if (!hasSeed)
return FarthestPointSampling(candidates, 1)[0];
var bestScore = -1f;
var bestCandidates = new List<MapPosition>();
foreach (var candidate in candidates)
{
var minDist = float.MaxValue;
foreach (var origin in origins)
{
if (origin == null) continue;
var dx = candidate.X - origin.X;
var dy = candidate.Y - origin.Y;
var dist = dx * dx + dy * dy;
if (dist < minDist) minDist = dist;
}
var score = minDist * GetEdgeWeight(candidate);
if (score > bestScore)
{
bestScore = score;
bestCandidates.Clear();
bestCandidates.Add(candidate);
}
else if (Mathf.Approximately(score, bestScore))
{
bestCandidates.Add(candidate);
}
}
return bestCandidates[UnityEngine.Random.Range(0, bestCandidates.Count)];
}
private float GetEdgeWeight(MapPosition pos)
{
float cx = (_width - 1) / 2f;
float cy = (_height - 1) / 2f;
float distToEdge = Mathf.Min(
Mathf.Min(pos.X, _width - 1 - pos.X),
Mathf.Min(pos.Y, _height - 1 - pos.Y)
);
float halfSize = Mathf.Min(cx, cy);
float ratio = halfSize > 0 ? Mathf.Clamp01(distToEdge / halfSize) : 1f;
return 0.3f + 0.7f * ratio;
}
private int ChebyshevDistance(MapPosition a, MapPosition b)
{
return Mathf.Max(Mathf.Abs(a.X - b.X), Mathf.Abs(a.Y - b.Y));
}
private void ShuffleList<T>(List<T> list)
{
for (int i = list.Count - 1; i > 0; i--)
{
int j = UnityEngine.Random.Range(0, i + 1);
(list[i], list[j]) = (list[j], list[i]);
}
}
//返回x,y位置的海陆层信息
private TerrainType GenerateTerrain(int x, int y,CivEnum civ = CivEnum.Common,InnerOuterCity innerOuterCity = InnerOuterCity.All)
{
// 走柏林噪音判断
var heightValue = _heightMap[x, y];
if (heightValue > _landThreshold)
return TerrainType.Land;
if (x + 1 < _width && _heightMap[x + 1, y] > _landThreshold)
return TerrainType.ShallowSea;
if (x - 1 > 0 && _heightMap[x - 1, y] > _landThreshold)
return TerrainType.ShallowSea;
if (y + 1 < _height && _heightMap[x, y + 1] > _landThreshold)
return TerrainType.ShallowSea;
if (y - 1 > 0 && _heightMap[x, y - 1] > _landThreshold)
return TerrainType.ShallowSea;
return TerrainType.DeepSea;
}
//返回x,y位置的地形层信息
private TerrainFeature GenerateFeature(int x, int y,CivEnum civ = CivEnum.Common,InnerOuterCity innerOuterCity = InnerOuterCity.All)
{
if (_tribes.Contains(new MapPosition(x, y)))
return TerrainFeature.None;//如果是城市中心那必没有山
var table = Table.Instance.PlayerDataAssets;
return UnityEngine.Random.Range(0f, 1f) < table.GetLandformRate(TerrainFeature.Mountain, civ) ? TerrainFeature.Mountain : TerrainFeature.None;
}
//返回x,y位置的植被层信息
private Vegetation GenerateVegetation(int x, int y,CivEnum civ = CivEnum.Common,InnerOuterCity innerOuterCity = InnerOuterCity.All)
{
if (_tribes.Contains(new MapPosition(x,y)))
return Vegetation.None;//如果是城市中心那必没有树
var table = Table.Instance.PlayerDataAssets;
var a = UnityEngine.Random.Range(0f, 1f);
var b = table.GetLandformRate(Vegetation.Trees, civ);
//Debug.Log($"{x},{y},{civ},{innerOuterCity} : {a}/{b}, Tree");
return a < b ? Vegetation.Trees : Vegetation.None;
}
//返回x,y位置的资源层信息
private ResourceType GenerateResource(int x, int y, TerrainType terrain, TerrainFeature feature, Vegetation vegetation,CivEnum civ = CivEnum.Common,InnerOuterCity innerOuterCity = InnerOuterCity.All)
{
if (_tribes.Contains(new MapPosition(x,y))) return ResourceType.CityCenter;
var table = Table.Instance.PlayerDataAssets;
//Step #1 不再二环以内的,只能考虑遗迹和海星,且不能有其他资源
if (innerOuterCity == InnerOuterCity.Edge)
{
//先考虑遗迹
if (UnityEngine.Random.Range(0f, 1f) <
table.GetLandformRate(ResourceType.Treasure, civ, innerOuterCity))
{
//如果遗迹附近有别的遗迹,不连着生成
if (_treasure.Any(P => Math.Abs(P.X - x) + Math.Abs(P.Y - y) <= 2))
return ResourceType.None;
_treasure.Add(new MapPosition(x,y));
return ResourceType.Treasure;
}
//再考虑鲸鱼
if (terrain != TerrainType.Land)
{
if (UnityEngine.Random.Range(0f, 1f) < table.GetLandformRate(ResourceType.Starfish, civ, innerOuterCity))
return ResourceType.Starfish;
}
return ResourceType.None;
}
//Step #2 如果是海洋
if (terrain != TerrainType.Land)
{
//先考虑渔业再考虑鲸鱼最后考虑Treasure
if (UnityEngine.Random.Range(0f, 1f) < table.GetLandformRate(ResourceType.Fish, civ, innerOuterCity))
return ResourceType.Fish;
if (UnityEngine.Random.Range(0f, 1f) < table.GetLandformRate(ResourceType.Starfish, civ, innerOuterCity))
return ResourceType.Starfish;
if (UnityEngine.Random.Range(0f, 1f) <
table.GetLandformRate(ResourceType.Treasure, civ, innerOuterCity))
{
//如果遗迹附近有别的遗迹,不连着生成
if (_treasure.Any(P => Math.Abs(P.X - x) + Math.Abs(P.Y - y) <= 2))
return ResourceType.None;
_treasure.Add(new MapPosition(x,y));
return ResourceType.Treasure;
}
return ResourceType.None;
}
//Step #3 如果是陆地先处理山再处理树最后处理Fruit和Crop
if (feature == TerrainFeature.Mountain)
{
var a = UnityEngine.Random.Range(0f, 1f);
var b = table.GetLandformRate(ResourceType.Metal, civ, innerOuterCity);
//Debug.Log($"{x},{y},{a}/{b},Metal");
//return UnityEngine.Random.Range(0f, 1f) < table.GetLandformRate(ResourceType.Metal, civ, innerOuterCity)
return a < b
? ResourceType.Metal : ResourceType.None;
}
if (vegetation == Vegetation.Trees)
{
var a = UnityEngine.Random.Range(0f, 1f);
var b = table.GetLandformRate(ResourceType.Animal, civ, innerOuterCity);
//Debug.Log($"{x},{y},{a}/{b},Animal");
return a < b
? ResourceType.Animal : ResourceType.None;
}
if (UnityEngine.Random.Range(0f, 1f) < table.GetLandformRate(ResourceType.Fruit, civ, innerOuterCity))
return ResourceType.Fruit;
if (UnityEngine.Random.Range(0f, 1f) < table.GetLandformRate(ResourceType.Crop, civ, innerOuterCity))
return ResourceType.Crop;
if (UnityEngine.Random.Range(0f, 1f) < table.GetLandformRate(ResourceType.Treasure, civ, innerOuterCity))
{
//如果遗迹附近有别的遗迹,不连着生成
if (_treasure.Any(P => Math.Abs(P.X - x) + Math.Abs(P.Y - y) <= 2))
return ResourceType.None;
_treasure.Add(new MapPosition(x,y));
return ResourceType.Treasure;
}
return ResourceType.None;
}
//返回x,y位置的文化层信息
private uint GenerateCivilization(MapData mapData,int x, int y)
{
// 简单的随机分配一个文明,或者使用更复杂的算法进行分布
var minDis = 2139062143;
var civSeed = -1;
var tt = 0;
//Debug.Log(PlayerCivOri.Count);
foreach (var p in PlayerCivOri)
{
if (Mathf.Abs(p.X - x) + Mathf.Abs(p.Y - y) < minDis)
{
minDis = (int)(Mathf.Abs(p.X - x) + Mathf.Abs(p.Y - y));
civSeed = (int)_mapData.PlayerMap.PlayerDataList[tt].PlayerCivId;
}
else if ((Mathf.Abs(p.X - x) + Mathf.Abs(p.Y - y) == minDis) && UnityEngine.Random.Range(0, 2) == 1)
{
minDis = (int)(Mathf.Abs(p.X - x) + Mathf.Abs(p.Y - y));
civSeed = (int)_mapData.PlayerMap.PlayerDataList[tt].PlayerCivId;
}
else if ((Mathf.Abs(p.X - x) + Mathf.Abs(p.Y - y) == minDis + 1) && UnityEngine.Random.Range(0, 3) == 1)
{
minDis = (int)(Mathf.Abs(p.X - x) + Mathf.Abs(p.Y - y));
civSeed = (int)_mapData.PlayerMap.PlayerDataList[tt].PlayerCivId;
}
tt++;
}
if (civSeed == -1)
return (uint)UnityEngine.Random.Range(1, mapData.MapConfig.PlayerCount + 1);
return (uint)civSeed;
}
//初始化所有玩家的视野信息
private void InitAllPlayerSight(MapData mapData)
{
foreach (var cityData in mapData.CityMap.CityList)
{
mapData.GetPlayerDataByCityId(cityData.Id, out var playerData);
mapData.GetGridDataByCityId(cityData.Id, out var gridData);
Main.PlayerLogic.UpdateSight_LogicView(mapData, playerData, mapData.GridMap.GetAroundGridIdList(2,gridData,true),false,0.01f);
}
}
private void InitDebugInfo(MapData mapData)
{
if (DebugCenter.Instance.DebugSelfPlayerAllSight)
{
System.Collections.Generic.List<uint> tt = new System.Collections.Generic.List<uint>();
foreach (var gridData in mapData.GridMap.GridList)
tt.Add(gridData.Id);
Main.PlayerLogic.UpdateSight_LogicView(mapData,mapData.PlayerMap.SelfPlayerData,tt);
}
if (DebugCenter.Instance.DebugAIAllTech)
{
foreach (var playerData in mapData.PlayerMap.PlayerDataList)
{
if (playerData == mapData.PlayerMap.SelfPlayerData)
continue;
playerData.TechTree.LearnTech(TechType.Fishing, playerData);
playerData.TechTree.LearnTech(TechType.Organization, playerData);
playerData.TechTree.LearnTech(TechType.Farming, playerData);
playerData.TechTree.LearnTech(TechType.Mining, playerData);
playerData.TechTree.LearnTech(TechType.Forestry, playerData);
playerData.TechTree.LearnTech(TechType.Hunting, playerData);
playerData.TechTree.LearnTech(TechType.Smithery, playerData);
playerData.TechTree.LearnTech(TechType.Mathematics, playerData);
playerData.TechTree.LearnTech(TechType.FreeSpirit, playerData);
playerData.TechTree.LearnTech(TechType.Chivalry, playerData);
playerData.TechTree.LearnTech(TechType.Sailing, playerData);
playerData.TechTree.LearnTech(TechType.Navigation, playerData);
playerData.TechTree.LearnTech(TechType.Ramming, playerData);
playerData.TechTree.LearnTech(TechType.Riding, playerData);
playerData.TechTree.LearnTech(TechType.Archery, playerData);
playerData.TechTree.LearnTech(TechType.Spiritualism, playerData);
playerData.TechTree.LearnTech(TechType.Construction, playerData);
playerData.TechTree.LearnTech(TechType.Meditation, playerData);
playerData.TechTree.LearnTech(TechType.Aquatism, playerData);
}
}
if (DebugCenter.Instance.DebugAIMoreMoney)
{
foreach (var playerData in mapData.PlayerMap.PlayerDataList)
{
if (playerData == mapData.PlayerMap.SelfPlayerData)
continue;
playerData.AddCoin(10);
}
}
}
//初始化所有玩家的特殊领地技能(目前主要是kaguya)
private void InitPlayerSpecialSkill(MapData mapData)
{
//遍历每一个玩家处理每一种可能得specialSkill
foreach (var player in mapData.PlayerMap.PlayerDataList)
{
//首先通过Tech 赋予玩家技能
//处理kaguya专属skill
if (player.TechTree.CheckIfHasTechAtom(TechAtom.KaguyaFrenchNapoleonicCode))
{
//设置为永远亭领地
var cityList = new List<CityData>();
mapData.GetCityDataListByPlayerId(player.Id,cityList);
foreach (var city in cityList)
{
Main.PlayerLogic.SetCityTerritoryGridSp(mapData,city,GridSpType.KaguyaGrid);
//获得首都4格内的遗迹视野
if(city.Id !=player.CradleCityId)continue;
_aroundBuf ??= new List<GridData>();
_aroundBuf.Clear();
mapData.GridMap.GetAroundGridData(4, 4, city.Grid(mapData), _aroundBuf);
var gidList = new List<uint>();
foreach(var grid in _aroundBuf)
if(grid.Resource == ResourceType.Treasure)
gidList.Add(grid.Id);
Main.PlayerLogic.UpdateSight_LogicView(mapData, player, gidList);
}
}
}
}
/// <summary>
/// 最远点采样从候选列表中选出count个点使得它们之间的最小距离尽量大。
/// 先随机选一个起始点,之后每轮选择离已选点集最远的候选点。
/// </summary>
private List<MapPosition> FarthestPointSampling(List<MapPosition> candidates, int count)
{
if (candidates.Count <= count)
return new List<MapPosition>(candidates);
// 预计算每个候选点的边缘权重:越靠近地图边缘,权重越低,避免总是选到角落
float cx = (_width - 1) / 2f;
float cy = (_height - 1) / 2f;
var edgeWeight = new float[candidates.Count];
for (int i = 0; i < candidates.Count; i++)
{
// 计算到四条边的最小距离占半幅的比例 [0,1]
float distToEdge = Mathf.Min(
Mathf.Min(candidates[i].X, _width - 1 - candidates[i].X),
Mathf.Min(candidates[i].Y, _height - 1 - candidates[i].Y)
);
float halfSize = Mathf.Min(cx, cy);
float ratio = halfSize > 0 ? Mathf.Clamp01(distToEdge / halfSize) : 1f;
// 权重范围 [0.3, 1.0]最边缘处仍有0.3的权重,不会完全排除
edgeWeight[i] = 0.3f + 0.7f * ratio;
}
var selected = new List<MapPosition>();
// 第一个点:按边缘权重加权随机选取,偏好远离边缘的位置
float totalWeight = 0f;
for (int i = 0; i < candidates.Count; i++) totalWeight += edgeWeight[i];
float roll = Random.Range(0f, totalWeight);
int firstIdx = 0;
float acc = 0f;
for (int i = 0; i < candidates.Count; i++)
{
acc += edgeWeight[i];
if (acc >= roll) { firstIdx = i; break; }
}
selected.Add(candidates[firstIdx]);
// minDist[i] = 候选点i到已选点集的最小距离
var minDist = new float[candidates.Count];
for (int i = 0; i < candidates.Count; i++)
minDist[i] = float.MaxValue;
for (int sel = 1; sel < count; sel++)
{
// 用上一轮新选入的点更新所有候选点的最小距离
var lastSelected = selected[sel - 1];
for (int i = 0; i < candidates.Count; i++)
{
float dx = candidates[i].X - lastSelected.X;
float dy = candidates[i].Y - lastSelected.Y;
float dist = dx * dx + dy * dy; // 用平方距离即可,无需开根号
if (dist < minDist[i])
minDist[i] = dist;
}
// 选择加权后 minDist 最大的候选点(兼顾远离已选点 + 远离边缘)
int bestIdx = -1;
float bestScore = -1f;
for (int i = 0; i < candidates.Count; i++)
{
float score = minDist[i] * edgeWeight[i];
if (score > bestScore)
{
bestScore = score;
bestIdx = i;
}
}
selected.Add(candidates[bestIdx]);
minDist[bestIdx] = 0f; // 标记已选
}
return selected;
}
private void GenerateLeyLines(MapData mapData)
{
// Step #1 计算目标数量:地脉总数 = 城市中心总数 / 3
int leyLineCount = _tribes.Count / 3;
bool hasIndianPlayer = false;
foreach (var player in mapData.PlayerMap.PlayerDataList)
if (player.CivEnum == CivEnum.Indian)
{
hasIndianPlayer = true;
break;
}
if (leyLineCount <= 0 && !hasIndianPlayer) return;
// Step #2 构建首都排除区仅首都±1范围非首都村庄不排除
using var pooledCradleExclude = THCollectionPool.GetHashSetHandle<uint>(out var cradleExclude);
foreach (var cradle in PlayerCivOri)
for (int x = cradle.X - 1; x <= cradle.X + 1; x++)
for (int y = cradle.Y - 1; y <= cradle.Y + 1; y++)
cradleExclude.Add(MapPosition.CalculatePosId(x, y));
// Step #3 收集候选格子:陆地 + 非CityCenter + 不在首都1格范围内
using var pooledCandidates = THCollectionPool.GetListHandle<GridData>(out var candidates);
foreach (var gridData in mapData.GridMap.GridList)
{
if (!CanPlaceLeyLine(gridData, cradleExclude)) continue;
candidates.Add(gridData);
}
// Step #4 Fisher-Yates 随机打乱
for (int i = candidates.Count - 1; i > 0; i--)
{
int j = Random.Range(0, i + 1);
(candidates[i], candidates[j]) = (candidates[j], candidates[i]);
}
// Step #5 取前N个标记为地脉
int placed = 0;
foreach (var grid in candidates)
{
if (placed >= leyLineCount) break;
if (TryAddLeyLine(mapData, grid, cradleExclude, true))
placed++;
}
// Step #6 古明地帝国保底首都2格内必定有地脉
for (int rk = 0; rk < mapData.PlayerMap.PlayerDataList.Count && rk < PlayerCivOri.Count; rk++)
{
var player = mapData.PlayerMap.PlayerDataList[rk];
if (player.CivEnum != CivEnum.Indian) continue;
if (!mapData.GridMap.GetGridDataByPos(PlayerCivOri[rk].X, PlayerCivOri[rk].Y, out var cradleGrid)) continue;
// 检查2格内是否已有地脉
_aroundBuf ??= new List<GridData>();
_aroundBuf.Clear();
mapData.GridMap.GetAroundGridData(2, 2, cradleGrid, _aroundBuf);
bool hasLeyLine = false;
foreach (var g in _aroundBuf)
if (g.HasSpType(GridSpType.LeyLine) && CanPlaceLeyLine(g, cradleExclude)) { hasLeyLine = true; break; }
if (hasLeyLine) continue;
// 没有则强制在2格内随机选一个合法格子放置
bool placedNearCradle = TryPlaceLeyLineAround(mapData, cradleGrid, 2, cradleExclude, true);
if (placedNearCradle) continue;
int maxRadius = (int)(_width > _height ? _width : _height);
bool placedByExpand = false;
for (int radius = 3; radius <= maxRadius; radius++)
{
if (TryPlaceLeyLineAround(mapData, cradleGrid, radius, cradleExclude, true))
{
placedByExpand = true;
break;
}
}
if (placedByExpand) continue;
for (int radius = 2; radius <= maxRadius; radius++)
if (TryPlaceLeyLineAround(mapData, cradleGrid, radius, cradleExclude, false))
break;
}
}
private bool TryPlaceLeyLineAround(MapData mapData, GridData centerGrid, int radius, HashSet<uint> cradleExclude, bool avoidNearbyLeyLine)
{
_aroundBuf ??= new List<GridData>();
_aroundBuf.Clear();
mapData.GridMap.GetAroundGridData(radius, radius, centerGrid, _aroundBuf);
for (int i = _aroundBuf.Count - 1; i > 0; i--)
{
int j = Random.Range(0, i + 1);
(_aroundBuf[i], _aroundBuf[j]) = (_aroundBuf[j], _aroundBuf[i]);
}
foreach (var grid in _aroundBuf)
{
if (!CanPlaceLeyLine(grid, cradleExclude)) continue;
if (TryAddLeyLine(mapData, grid, cradleExclude, avoidNearbyLeyLine))
return true;
}
return false;
}
private bool TryAddLeyLine(MapData mapData, GridData grid, HashSet<uint> cradleExclude, bool avoidNearbyLeyLine)
{
if (!CanPlaceLeyLine(grid, cradleExclude)) return false;
if (grid.HasSpType(GridSpType.LeyLine)) return false;
if (avoidNearbyLeyLine && HasNearbyLeyLine(mapData, grid, 1)) return false;
return grid.AddSpType(GridSpType.LeyLine, mapData, null);
}
private bool HasNearbyLeyLine(MapData mapData, GridData grid, int radius)
{
for (int x = grid.Pos.X - radius; x <= grid.Pos.X + radius; x++)
for (int y = grid.Pos.Y - radius; y <= grid.Pos.Y + radius; y++)
{
if (x == grid.Pos.X && y == grid.Pos.Y) continue;
if (!mapData.GridMap.GetGridDataByPos(x, y, out var aroundGrid)) continue;
if (aroundGrid.HasSpType(GridSpType.LeyLine)) return true;
}
return false;
}
private bool CanPlaceLeyLine(GridData grid, HashSet<uint> cradleExclude)
{
if (grid == null) return false;
if (grid.Terrain != TerrainType.Land) return false;
if (grid.Feature == TerrainFeature.Mountain) return false;
if (grid.Resource == ResourceType.CityCenter) return false;
if (IsMapEdgeGrid(grid)) return false;
if (cradleExclude.Contains(grid.Pos.PosId)) return false;
return true;
}
private bool IsMapEdgeGrid(GridData grid)
{
return grid.Pos.X <= 0 || grid.Pos.Y <= 0 || grid.Pos.X >= _width - 1 || grid.Pos.Y >= _height - 1;
}
}
}