1519 lines
67 KiB
C#
1519 lines
67 KiB
C#
/*
|
||
* @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;
|
||
}
|
||
|
||
|
||
}
|
||
}
|