1446 lines
38 KiB
C#
1446 lines
38 KiB
C#
using System.Collections.Generic;
|
||
using System.IO;
|
||
|
||
using TGS;
|
||
using Unity.Mathematics;
|
||
using UnityEngine;
|
||
using NLDPB;
|
||
|
||
|
||
using System.Diagnostics;
|
||
using Gameplay;
|
||
|
||
|
||
public class MapHex
|
||
{
|
||
public const float Length = 1.73205f;
|
||
public const float Radius = 1.5f;
|
||
|
||
public int index;
|
||
public int Nx;
|
||
public int Nz;
|
||
|
||
public int Ax;
|
||
public int Ay;
|
||
public int Az;
|
||
|
||
public float Wx;
|
||
public float Wz;
|
||
|
||
public int CityID;
|
||
|
||
public int StaticCamp;
|
||
|
||
public int Camp;
|
||
|
||
public bool isChecked;
|
||
|
||
public void SetData( int nWidth, int nHeight, int nIndex )
|
||
{
|
||
//index = Nz * nWidth + Nx
|
||
index = nIndex;
|
||
|
||
Nz = nIndex/nWidth;
|
||
Nx = nIndex - Nz *nWidth;
|
||
|
||
Az = Nz;
|
||
Ax = Nx-Nz/2;
|
||
Ay = -Ax-Az;
|
||
|
||
Wx = (Nx + Nz * 0.5f - Nz / 2 ) * (Radius*2.0f);
|
||
Wz = Nz * (Length*1.5f);
|
||
}
|
||
|
||
public override string ToString() {
|
||
return $"index={index}, NC=({Nx}, {Nz}), AC=({Ax}, {Ay}, {Az}), WC=({Wx}, 0, {Wz}), City ID={CityID}";
|
||
}
|
||
|
||
public bool IsMyNext(MapHex hex)
|
||
{
|
||
int xOffset = math.abs(hex.Ax- Ax);
|
||
int zOffset = math.abs(hex.Az-Az);
|
||
int yOffset = math.abs(hex.Ay-Ay);
|
||
|
||
return xOffset <= 1 && zOffset <= 1 && yOffset <= 1;
|
||
}
|
||
|
||
public bool IsMe(MapHex hex)
|
||
{
|
||
return hex.Nx == Nx && hex.Nz == Nz;
|
||
}
|
||
|
||
public int Distance(MapHex hex)
|
||
{
|
||
int xOffset = math.abs(hex.Ax- Ax);
|
||
int zOffset = math.abs(hex.Az-Az);
|
||
int yOffset = math.abs(hex.Ay-Ay);
|
||
|
||
return math.max(yOffset, math.max(xOffset, zOffset));
|
||
}
|
||
|
||
public bool CanPass(int startCityID, int endCityID)
|
||
{
|
||
if(CityID > 0) {
|
||
return CityID == startCityID || CityID == endCityID;
|
||
} else {
|
||
return true;
|
||
}
|
||
}
|
||
|
||
public bool IsCampSource() {
|
||
return Camp == (int)BattlefieldActorCamp.CampAsource || Camp == (int)BattlefieldActorCamp.CampBsource;
|
||
}
|
||
|
||
public int GetRealCamp() {
|
||
switch (Camp) {
|
||
case (int)BattlefieldActorCamp.CampAsource:
|
||
return (int)BattlefieldActorCamp.CampA;
|
||
case (int)BattlefieldActorCamp.CampBsource:
|
||
return (int)BattlefieldActorCamp.CampB;
|
||
default:
|
||
return Camp;
|
||
}
|
||
}
|
||
|
||
public bool IsRealCamp() {
|
||
switch(Camp) {
|
||
case (int)BattlefieldActorCamp.CampAsource:
|
||
case (int)BattlefieldActorCamp.CampBsource:
|
||
case (int)BattlefieldActorCamp.CampA:
|
||
case (int)BattlefieldActorCamp.CampB:
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
}
|
||
|
||
public class PathNode
|
||
{
|
||
public MapHex hex;
|
||
public HexDir dir;
|
||
|
||
//public List<PathNode> childs;
|
||
|
||
public PathNode parent;
|
||
|
||
public CityRoad cr;
|
||
|
||
public int crIndex;
|
||
|
||
public bool goRight;
|
||
|
||
public PathNode()
|
||
{
|
||
//childs = new List<PathNode>();
|
||
}
|
||
}
|
||
|
||
public class CityRoad
|
||
{
|
||
public MapHex center;
|
||
|
||
public MapHex [] road;
|
||
|
||
public int Cover;
|
||
|
||
public void GetNextHex(MapHex hex, out MapHex left, out int leftIndex, out MapHex right, out int rightIndex)
|
||
{
|
||
int index = -1;
|
||
for(int i = 0; i < road.Length; i ++)
|
||
{
|
||
if(road[i].IsMe(hex))
|
||
{
|
||
index = i;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if(index == -1)
|
||
{
|
||
UnityEngine.Debug.LogError($"CityRoad GetNextHex not found index, hex={hex}");
|
||
left = null;
|
||
right = null;
|
||
leftIndex = 0;
|
||
rightIndex = 0;
|
||
return;
|
||
}
|
||
|
||
leftIndex = index -1;
|
||
if(leftIndex < 0)
|
||
leftIndex = road.Length - 1;
|
||
|
||
rightIndex = index +1;
|
||
if(rightIndex >= road.Length)
|
||
rightIndex = 0;
|
||
|
||
left = road[leftIndex];
|
||
right = road[rightIndex];
|
||
|
||
//UnityEngine.Debug.Log($"GetNextHex 1 rsult, index={index}, leftIndex={leftIndex}, rightIndex={rightIndex}");
|
||
//UnityEngine.Debug.Log($"hex={hex}");
|
||
//UnityEngine.Debug.Log($"left={left}");
|
||
//UnityEngine.Debug.Log($"right={right}");
|
||
}
|
||
|
||
public void GetNextHex(int index, bool goRight, out MapHex next, out int nextIndex)
|
||
{
|
||
if(goRight)
|
||
{
|
||
index ++;
|
||
if(index >= road.Length)
|
||
index = 0;
|
||
}
|
||
else
|
||
{
|
||
index --;
|
||
if(index < 0)
|
||
index = road.Length - 1;
|
||
}
|
||
|
||
nextIndex = index;
|
||
next = road[index];
|
||
|
||
//UnityEngine.Debug.Log($"GetNextHex 2 index={index}, goRight={goRight}, nextIndex={nextIndex}");
|
||
//UnityEngine.Debug.Log($"next={next}");
|
||
}
|
||
}
|
||
|
||
public enum HexDir
|
||
{
|
||
None = 0,
|
||
RightUp,
|
||
RightDown,
|
||
LeftUp,
|
||
LeftDown,
|
||
Right,
|
||
Left,
|
||
UpRL,
|
||
DownRL,
|
||
}
|
||
|
||
public class MapArea {
|
||
public bool hasSource;
|
||
public List<MapHex> hexes;
|
||
|
||
public MapArea()
|
||
{
|
||
hexes = new List<MapHex>();
|
||
}
|
||
|
||
public void Reverse() {
|
||
for (int i = 0; i < hexes.Count; i ++) {
|
||
var h = hexes[i];
|
||
if (h.Camp == (int)BattlefieldActorCamp.CampA) {
|
||
h.Camp = (int)BattlefieldActorCamp.CampB;
|
||
} else if (h.Camp == (int)BattlefieldActorCamp.CampB) {
|
||
h.Camp = (int)BattlefieldActorCamp.CampA;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
public class MapGo {
|
||
public int Camp;
|
||
public GameObject go;
|
||
}
|
||
|
||
public class MapActor {
|
||
public MapPathfind m_mp;
|
||
|
||
public MapHex m_Current;
|
||
public int m_Camp;
|
||
public float m_Speed;
|
||
|
||
bool IsMoving;
|
||
|
||
GameObject go;
|
||
|
||
List<MapHex> m_path;
|
||
int pathIndex;
|
||
|
||
public void Init(MapPathfind m, MapHex hex, int camp, float speed) {
|
||
m_mp = m;
|
||
m_Current = hex;
|
||
m_Camp = camp;
|
||
m_Speed = speed;
|
||
|
||
go = GameObject.CreatePrimitive(PrimitiveType.Capsule);
|
||
go.transform.position = new Vector3(m_Current.Wx, 0, m_Current.Wz);
|
||
go.transform.localScale = new Vector3(2, 1, 2);
|
||
|
||
m_mp.SetRoundCamp(false, hex, m_Camp, 3);
|
||
}
|
||
|
||
public void SetMove() {
|
||
m_path = m_mp.DoFind2(this);
|
||
if(m_path != null) {
|
||
pathIndex = m_path.Count-1;
|
||
IsMoving = true;
|
||
}
|
||
}
|
||
|
||
public void Update() {
|
||
if(IsMoving) {
|
||
var curPos = go.transform.position;
|
||
var target = new Vector3(m_path[pathIndex].Wx, 0, m_path[pathIndex].Wz);
|
||
var moveDis = m_Speed * Time.deltaTime;
|
||
var disCheck = Vector3.Distance(curPos, target);
|
||
if(moveDis > disCheck) {
|
||
|
||
var nextHex = m_path[pathIndex];
|
||
UnityEngine.Debug.Log($"m_Current.CityID={m_Current.CityID}, next City id={nextHex.CityID}");
|
||
if(m_Current.CityID > 0 && nextHex.CityID <= 0) { // 离开城了
|
||
UnityEngine.Debug.Log("离开城了,m_Current.CityID=" +m_Current.CityID);
|
||
var cr = m_mp.GetCityRoad(m_Current.CityID);
|
||
m_mp.SetRoundCamp(true, cr.center, (int)BattlefieldActorCamp.BacNone, cr.Cover);
|
||
} else if (m_Current.CityID <= 0 && nextHex.CityID > 0) {
|
||
UnityEngine.Debug.Log("进入城了,m_Current.CityID=" +m_Current.CityID);
|
||
var cr = m_mp.GetCityRoad(nextHex.CityID);
|
||
m_mp.SetRoundCamp(true, cr.center, m_Camp, cr.Cover);
|
||
}
|
||
|
||
m_Current = nextHex;
|
||
m_mp.SetRoundCamp(false, m_Current, m_Camp, 3);
|
||
|
||
go.transform.position = target;
|
||
pathIndex --;
|
||
if(pathIndex < 0) {
|
||
IsMoving = false;
|
||
}
|
||
} else {
|
||
var dir = target - curPos;
|
||
dir.Normalize();
|
||
go.transform.position = curPos + dir * moveDis;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
public class MapPathfind : MonoBehaviour
|
||
{
|
||
public TerrainGridSystem TGS;
|
||
|
||
MapData m_mapData;
|
||
|
||
public bool Reload;
|
||
|
||
public MapHex [] m_hexes;
|
||
public MapGo [] m_hexCamps;
|
||
|
||
// width x轴
|
||
// height z轴
|
||
|
||
public int ANx;
|
||
public int ANz;
|
||
public bool AMove;
|
||
|
||
public int BNx;
|
||
public int BNz;
|
||
|
||
public bool BMove;
|
||
|
||
public bool FindPath;
|
||
|
||
MapHex m_Start;
|
||
MapHex m_End;
|
||
|
||
Dictionary<int, CityRoad> m_Roads;
|
||
|
||
public GameObject PathGo;
|
||
|
||
public GameObject RoundGo;
|
||
|
||
public GameObject CityGo;
|
||
|
||
public GameObject StartGo;
|
||
public GameObject EndGo;
|
||
|
||
PathNode m_EndNode;
|
||
|
||
public HashSet<int> m_CloseList;
|
||
|
||
public bool DestroyPath;
|
||
|
||
List<MapHex> BornPosA;
|
||
List<MapHex> BornPosB;
|
||
|
||
public GameObject Blue;
|
||
public GameObject BlueSource;
|
||
public GameObject BlueBuilding;
|
||
public GameObject Red;
|
||
public GameObject RedSource;
|
||
public GameObject RedBuilding;
|
||
|
||
public Transform CampRoot;
|
||
|
||
MapActor A;
|
||
MapActor B;
|
||
|
||
void Start()
|
||
{
|
||
Reload = true;
|
||
// 19,3 0,15
|
||
}
|
||
|
||
void CreateMap()
|
||
{
|
||
m_Roads = new Dictionary<int, CityRoad>();
|
||
|
||
string jsonPath = Application.dataPath + "/Editor/MapPath/MapData_MapsTest.json";
|
||
string txt = File.ReadAllText(jsonPath);
|
||
m_mapData = JsonUtility.FromJson<MapData>(txt);
|
||
|
||
UnityEngine.Debug.Log($"width={m_mapData.Width}, height={m_mapData.Height}, data length={m_mapData.BlocksData.Count}");
|
||
|
||
m_hexes = new MapHex[m_mapData.BlocksData.Count];
|
||
for(int i = 0; i < m_mapData.BlocksData.Count; i ++)
|
||
{
|
||
MapHex hex = new MapHex();
|
||
hex.SetData(m_mapData.Width, m_mapData.Height, i);
|
||
m_hexes[i] = hex;
|
||
//UnityEngine.Debug.Log(hex.ToString());
|
||
}
|
||
|
||
|
||
AddCity(1, 5, 4, 1, 3 );
|
||
//AddCity(2, 12, 21, 1 );
|
||
//AddCity(3, 4, 19, 1 );
|
||
//AddCity(4, 16, 19, 1 );
|
||
//AddCity(5, 14, 18, 1 );
|
||
//AddCity(6, 12, 17, 1 );
|
||
//AddCity(7, 4, 16, 1 );
|
||
//AddCity(8, 2, 15, 1 );
|
||
//AddCity(9, 14, 13, 1, 3 );
|
||
//AddCity(10, 6, 12, 1 );
|
||
//AddCity(11, 15, 11, 1 );
|
||
//AddCity(12, 9, 10, 1 );
|
||
//AddCity(13, 9, 6, 1, 2 );
|
||
|
||
AddCity(14, 15, 6, 2, 4 );
|
||
//AddCity(15, 16, 16, 2 );
|
||
//AddCity(16, 3, 11, 2 );
|
||
AddCity(17, 14, 24, 2, 4 );
|
||
|
||
AddCity(18, 9, 15, 3, 5 );
|
||
|
||
m_hexCamps = new MapGo[m_hexes.Length];
|
||
for(int i = 0; i < m_hexes.Length; i ++) {
|
||
m_hexCamps[i] = new MapGo();
|
||
}
|
||
|
||
BornPosA = new List<MapHex>
|
||
{
|
||
m_hexes[GetIndex(0, 0)],
|
||
m_hexes[GetIndex(1, 0)],
|
||
m_hexes[GetIndex(0, 1)]
|
||
};
|
||
|
||
|
||
|
||
BornPosB = new List<MapHex>
|
||
{
|
||
m_hexes[GetIndex(19, 29)],
|
||
m_hexes[GetIndex(18, 29)],
|
||
m_hexes[GetIndex(19, 28)]
|
||
};
|
||
}
|
||
|
||
public void AddCity(int nCityID, int Nx, int Nz, int range, int cover )
|
||
{
|
||
MapHex center = m_hexes[GetIndex(Nx, Nz)];
|
||
|
||
// 找最外一圈路径
|
||
List<MapHex> RoundRoad = new List<MapHex>();
|
||
|
||
// 获取建筑所在的格子
|
||
int MaxX = center.Ax + range;
|
||
int MinX = center.Ax - range;
|
||
int MaxY = center.Ay + range;
|
||
int MinY = center.Ay - range;
|
||
int MaxZ = center.Az + range;
|
||
int MinZ = center.Az - range;
|
||
|
||
for(int x = MinX; x <= MaxX; x ++)
|
||
{
|
||
for(int y = MinY; y <= MaxY; y ++)
|
||
{
|
||
int z = -x-y;
|
||
if(z >= MinZ && z <= MaxZ)
|
||
{
|
||
MapHex hex = GetMapHex(x, y, z);
|
||
|
||
|
||
if(x == MinX || x == MaxX
|
||
|| y == MinY || y == MaxY
|
||
|| z == MinZ || z == MaxZ)
|
||
{
|
||
RoundRoad.Add(hex);
|
||
}
|
||
else
|
||
{
|
||
hex.CityID = nCityID;
|
||
//UnityEngine.Debug.Log(hex.ToString());
|
||
|
||
var go = Instantiate(CityGo);
|
||
go.transform.SetParent(transform);
|
||
go.transform.position = new Vector3(hex.Wx, 0, hex.Wz);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
int RoadNodeCount = RoundRoad.Count;
|
||
|
||
List<MapHex> rounds = new List<MapHex>();
|
||
MapHex next = RoundRoad[0];
|
||
rounds.Add(next);
|
||
RoundRoad.RemoveAt(0);
|
||
|
||
while(RoundRoad.Count > 0)
|
||
{
|
||
MapHex nextHex = null;
|
||
for(int i = 0; i < RoundRoad.Count; i ++)
|
||
{
|
||
if(next.IsMyNext(RoundRoad[i]))
|
||
{
|
||
next = RoundRoad[i];
|
||
RoundRoad.RemoveAt(i);
|
||
rounds.Add(next);
|
||
nextHex = next;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if(nextHex == null)
|
||
{
|
||
UnityEngine.Debug.LogError("nextHex not found ...");
|
||
break;
|
||
}
|
||
}
|
||
|
||
for(int i = 0; i < rounds.Count; i ++)
|
||
{
|
||
UnityEngine.Debug.Log($"find next, city={nCityID}, count={rounds.Count}, current={rounds[i]}");
|
||
}
|
||
|
||
if(rounds.Count == RoadNodeCount)
|
||
{
|
||
CityRoad cr = new CityRoad();
|
||
cr.center = center;
|
||
cr.road = rounds.ToArray();
|
||
cr.Cover = cover;
|
||
m_Roads.Add(nCityID, cr);
|
||
|
||
/*
|
||
for(int i = 0; i < cr.road.Length; i ++)
|
||
{
|
||
var go = Instantiate(RoundGo);
|
||
go.transform.SetParent(transform);
|
||
go.transform.position = new Vector3(cr.road[i].Wx, 0, cr.road[i].Wz);
|
||
}
|
||
//*/
|
||
}
|
||
else
|
||
{
|
||
UnityEngine.Debug.LogError($"Road create error, city={nCityID}, rounds.Count:{rounds.Count} != RoadNodeCount:{RoadNodeCount}");
|
||
}
|
||
|
||
|
||
/*
|
||
UnityEngine.Debug.Log("Road Start ========================= count=" +rounds.Count);
|
||
for(int i = 0; i < rounds.Count; i ++)
|
||
{
|
||
UnityEngine.Debug.Log(rounds[i]);
|
||
}
|
||
UnityEngine.Debug.Log("Road End =========================");
|
||
*/
|
||
}
|
||
|
||
int GetIndex(int Nx, int Nz)
|
||
{
|
||
return Nz*m_mapData.Width+Nx;
|
||
}
|
||
|
||
void clearChecked() {
|
||
for(int i = 0; i < m_hexes.Length; i ++) {
|
||
m_hexes[i].isChecked = false;
|
||
}
|
||
}
|
||
|
||
void Update()
|
||
{
|
||
if(Reload)
|
||
{
|
||
Reload = false;
|
||
CreateMap();
|
||
InitRange(BornPosA, BornPosB);
|
||
A = new MapActor();
|
||
A.Init(this, BornPosA[0], (int)BattlefieldActorCamp.CampA, 3.0f);
|
||
|
||
B = new MapActor();
|
||
B.Init(this, BornPosB[0], (int)BattlefieldActorCamp.CampB, 3.0f);
|
||
|
||
|
||
}
|
||
|
||
// if(FindPath)
|
||
// {
|
||
|
||
// Stopwatch stopwatch = new Stopwatch();
|
||
// stopwatch.Start();
|
||
// DoFind2();
|
||
// stopwatch.Stop();
|
||
// FindPath = false;
|
||
|
||
// UnityEngine.Debug.Log($"DoFind2 执行时间: {stopwatch.ElapsedMilliseconds} 毫秒");
|
||
// }
|
||
|
||
if(DestroyPath)
|
||
{
|
||
DestroyPath = false;
|
||
//删除本节点下所有的GameObect
|
||
while (transform.childCount > 0)
|
||
{
|
||
DestroyImmediate(transform.GetChild(0).gameObject);
|
||
}
|
||
}
|
||
|
||
if(A != null) {
|
||
if(AMove) {
|
||
AMove = false;
|
||
A.SetMove();
|
||
}
|
||
|
||
A.Update();
|
||
}
|
||
|
||
if(B != null) {
|
||
if(BMove) {
|
||
BMove = false;
|
||
B.SetMove();
|
||
}
|
||
B.Update();
|
||
}
|
||
|
||
CheckRange(BornPosA[0]);
|
||
|
||
for(int i = 0; i < m_hexes.Length; i ++) {
|
||
if(m_hexCamps[i].Camp != m_hexes[i].Camp) {
|
||
if(m_hexCamps[i].go != null) {
|
||
GameObject.Destroy(m_hexCamps[i].go);
|
||
m_hexCamps[i].go = null;
|
||
}
|
||
|
||
m_hexCamps[i].Camp = m_hexes[i].Camp;
|
||
switch(m_hexCamps[i].Camp)
|
||
{
|
||
case (int)BattlefieldActorCamp.CampA:
|
||
{
|
||
m_hexCamps[i].go = GameObject.Instantiate(Blue);
|
||
}
|
||
break;
|
||
|
||
case (int)BattlefieldActorCamp.CampAsource:
|
||
{
|
||
m_hexCamps[i].go = GameObject.Instantiate(BlueSource);
|
||
}
|
||
break;
|
||
|
||
case (int)BattlefieldActorCamp.CampAbuilding:
|
||
{
|
||
m_hexCamps[i].go = GameObject.Instantiate(BlueBuilding);
|
||
}
|
||
break;
|
||
|
||
case (int)BattlefieldActorCamp.CampB:
|
||
{
|
||
m_hexCamps[i].go = GameObject.Instantiate(Red);
|
||
}
|
||
break;
|
||
|
||
case (int)BattlefieldActorCamp.CampBsource:
|
||
{
|
||
m_hexCamps[i].go = GameObject.Instantiate(RedSource);
|
||
}
|
||
break;
|
||
|
||
case (int)BattlefieldActorCamp.CampBbuilding:
|
||
{
|
||
m_hexCamps[i].go = GameObject.Instantiate(RedBuilding);
|
||
}
|
||
break;
|
||
}
|
||
|
||
if(m_hexCamps[i].go != null) {
|
||
m_hexCamps[i].go.transform.SetParent(CampRoot);
|
||
m_hexCamps[i].go.transform.position = new Vector3(m_hexes[i].Wx, 0, m_hexes[i].Wz);
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
|
||
}
|
||
|
||
HexDir GetTargetPositionDir(MapHex start, MapHex end)
|
||
{
|
||
var dir = GetLineDir(start, end);
|
||
if(dir != HexDir.None)
|
||
return dir;
|
||
|
||
if(end.Az > start.Az)
|
||
{
|
||
if(end.Ax < start.Ax && end.Ay < start.Ay)
|
||
{
|
||
return HexDir.UpRL;
|
||
}
|
||
else if(end.Ay > start.Ay)
|
||
{
|
||
return HexDir.LeftUp;
|
||
}
|
||
else
|
||
{
|
||
return HexDir.RightUp;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if(end.Ax > start.Ax && end.Ay > start.Ay)
|
||
{
|
||
return HexDir.DownRL;
|
||
}
|
||
else if(end.Ax < start.Ax)
|
||
{
|
||
return HexDir.LeftDown;
|
||
}
|
||
else
|
||
{
|
||
return HexDir.RightDown;
|
||
}
|
||
}
|
||
}
|
||
|
||
HexDir GetLineDir(MapHex start, MapHex end)
|
||
{
|
||
if(start.Ax == end.Ax)
|
||
{
|
||
if(end.Az > start.Az)
|
||
{
|
||
return HexDir.RightUp;
|
||
}
|
||
else
|
||
{
|
||
return HexDir.LeftDown;
|
||
}
|
||
}
|
||
else if( start.Az == end.Az)
|
||
{
|
||
if(end.Ax > start.Ax)
|
||
{
|
||
return HexDir.Right;
|
||
}
|
||
else
|
||
{
|
||
return HexDir.Left;
|
||
}
|
||
}
|
||
else if( start.Ay == end.Ay)
|
||
{
|
||
if(end.Az > start.Az)
|
||
{
|
||
return HexDir.LeftUp;
|
||
}
|
||
else
|
||
{
|
||
return HexDir.RightDown;
|
||
}
|
||
}
|
||
|
||
return HexDir.None;
|
||
}
|
||
|
||
void AddPathNode(PathNode node, PathNode parent)
|
||
{
|
||
node.parent = parent;
|
||
}
|
||
|
||
bool GetNextNode(PathNode currentNode, List<PathNode> openList, int startCityID, int endCityID)
|
||
{
|
||
var dir = currentNode.dir;
|
||
|
||
var nextHex = GetNextHex(currentNode.hex, dir);
|
||
if(nextHex != null)
|
||
{
|
||
// 下一格是建筑,那当前这一格,肯定就在路点上
|
||
if(!nextHex.CanPass(startCityID, endCityID))
|
||
{
|
||
// 如果当前格没有路点信息,则找左右两个路点作为下一格
|
||
if(currentNode.cr == null)
|
||
{
|
||
m_Roads.TryGetValue(nextHex.CityID, out CityRoad cr);
|
||
if(cr == null)
|
||
{
|
||
UnityEngine.Debug.LogError("City road not found, city id=" + nextHex.CityID);
|
||
return false;
|
||
}
|
||
|
||
cr.GetNextHex(currentNode.hex, out MapHex left, out int leftIndex, out MapHex right, out int rightIndex);
|
||
if(!m_CloseList.TryGetValue(left.index, out int hexIndex))
|
||
{
|
||
PathNode nodeLeft = new PathNode();
|
||
nodeLeft.hex = left;
|
||
nodeLeft.dir = GetTargetPositionDir(left, m_End);
|
||
nodeLeft.cr = cr;
|
||
nodeLeft.crIndex = leftIndex;
|
||
nodeLeft.goRight = false;
|
||
//nodeLeft.parent = currentNode;
|
||
AddPathNode(nodeLeft, currentNode);
|
||
if(m_End.IsMe(left))
|
||
{
|
||
m_EndNode = nodeLeft;
|
||
return true;
|
||
}
|
||
openList.Add(nodeLeft);
|
||
}
|
||
|
||
if(!m_CloseList.TryGetValue(right.index, out int hexIndex2))
|
||
{
|
||
PathNode nodeRight = new PathNode();
|
||
nodeRight.hex = right;
|
||
nodeRight.dir = GetTargetPositionDir(right, m_End);
|
||
nodeRight.cr = cr;
|
||
nodeRight.crIndex = rightIndex;
|
||
nodeRight.goRight = true;
|
||
//nodeRight.parent = currentNode;
|
||
AddPathNode(nodeRight, currentNode);
|
||
if(m_End.IsMe(right))
|
||
{
|
||
m_EndNode = nodeRight;
|
||
return true;
|
||
}
|
||
openList.Add(nodeRight);
|
||
}
|
||
|
||
}
|
||
else // 当前格本身就在路点上,则找下一格路点
|
||
{
|
||
currentNode.cr.GetNextHex(currentNode.crIndex, currentNode.goRight, out MapHex next, out int nextIndex);
|
||
if(!m_CloseList.TryGetValue(next.index, out int hexIndex2))
|
||
{
|
||
PathNode nodeNext = new PathNode();
|
||
nodeNext.hex = next;
|
||
nodeNext.cr = currentNode.cr;
|
||
nodeNext.dir = GetTargetPositionDir(next, m_End);
|
||
nodeNext.goRight = currentNode.goRight;
|
||
nodeNext.crIndex = nextIndex;
|
||
//nodeNext.parent = currentNode;
|
||
AddPathNode(nodeNext, currentNode);
|
||
if(m_End.IsMe(next))
|
||
{
|
||
m_EndNode = nodeNext;
|
||
return true;
|
||
}
|
||
openList.Add(nodeNext);
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
PathNode nextNode = new PathNode();
|
||
nextNode.hex = nextHex;
|
||
nextNode.dir = dir;
|
||
var dirNext = GetLineDir(nextHex, m_End);
|
||
if(dirNext != HexDir.None)
|
||
{
|
||
nextNode.dir = dirNext;
|
||
}
|
||
|
||
nextNode.parent = currentNode;
|
||
AddPathNode(nextNode, currentNode);
|
||
if(m_End.IsMe(nextHex))
|
||
{
|
||
m_EndNode = nextNode;
|
||
return true;
|
||
}
|
||
|
||
openList.Add(nextNode);
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
public List<MapHex> DoFind2(MapActor actor)
|
||
{
|
||
m_EndNode = null;
|
||
m_CloseList = new HashSet<int>();
|
||
m_Start = actor.m_Current;
|
||
var StartCityID = m_Start.CityID;
|
||
|
||
if(actor.m_Camp == (int)BattlefieldActorCamp.CampA) {
|
||
if(m_Start.Nx == ANx && m_Start.Nz == ANz) {
|
||
return null;
|
||
}
|
||
|
||
m_End = m_hexes[GetIndex(ANx, ANz)];
|
||
} else {
|
||
if(m_Start.Nx == BNx && m_Start.Nz == BNz) {
|
||
return null;
|
||
}
|
||
|
||
m_End = m_hexes[GetIndex(BNx, BNz)];
|
||
}
|
||
|
||
//
|
||
var EndCityID = m_End.CityID;
|
||
|
||
StartGo.transform.position = new Vector3(m_Start.Wx, 0, m_Start.Wz);
|
||
EndGo.transform.position = new Vector3(m_End.Wx, 0, m_End.Wz);
|
||
|
||
List<PathNode> openList = new List<PathNode>();
|
||
|
||
PathNode path = new PathNode();
|
||
path.hex = m_Start;
|
||
path.dir = GetTargetPositionDir(m_Start, m_End);
|
||
openList.Add(path);
|
||
|
||
List<MapHex> hexList = new List<MapHex>();
|
||
while(openList.Count > 0)
|
||
{
|
||
//UnityEngine.Debug.Log("START ============================================== openList.Count=" + openList.Count);
|
||
List<PathNode> newOpenList = new List<PathNode>();
|
||
for(int i = 0; i < openList.Count; i ++)
|
||
{
|
||
hexList.Clear();
|
||
var node = openList[i];
|
||
m_CloseList.Add(node.hex.index);
|
||
|
||
//UnityEngine.Debug.Log($"node.dir={node.dir}, node={node.hex}");
|
||
switch(node.dir)
|
||
{
|
||
case HexDir.DownRL:
|
||
{
|
||
node.dir = HexDir.LeftDown;
|
||
if(GetNextNode(node, newOpenList, StartCityID, EndCityID))
|
||
{
|
||
UnityEngine.Debug.Log("LeftDown go end");
|
||
goto End;
|
||
}
|
||
|
||
node.dir = HexDir.RightDown;
|
||
|
||
if(GetNextNode(node, newOpenList, StartCityID, EndCityID))
|
||
{
|
||
UnityEngine.Debug.Log("RightDown go end");
|
||
goto End;
|
||
}
|
||
}
|
||
break;
|
||
|
||
case HexDir.UpRL:
|
||
{
|
||
node.dir = HexDir.LeftUp;
|
||
if(GetNextNode(node, newOpenList, StartCityID, EndCityID))
|
||
{
|
||
UnityEngine.Debug.Log("LeftUp go end");
|
||
goto End;
|
||
}
|
||
|
||
node.dir = HexDir.RightUp;
|
||
if(GetNextNode(node, newOpenList, StartCityID, EndCityID))
|
||
{
|
||
UnityEngine.Debug.Log("RightUp go end");
|
||
goto End;
|
||
}
|
||
}
|
||
break;
|
||
|
||
//*
|
||
case HexDir.LeftUp:
|
||
{
|
||
node.dir = HexDir.Left;
|
||
if(GetNextNode(node, newOpenList, StartCityID, EndCityID))
|
||
{
|
||
UnityEngine.Debug.Log("LeftUp HexDir.Left go end");
|
||
goto End;
|
||
}
|
||
|
||
node.dir = HexDir.LeftUp;
|
||
if(GetNextNode(node, newOpenList, StartCityID, EndCityID))
|
||
{
|
||
UnityEngine.Debug.Log("HexDir.LeftUp go end");
|
||
goto End;
|
||
}
|
||
}
|
||
break;
|
||
|
||
case HexDir.LeftDown:
|
||
{
|
||
node.dir = HexDir.Left;
|
||
if(GetNextNode(node, newOpenList, StartCityID, EndCityID))
|
||
{
|
||
UnityEngine.Debug.Log("LeftDown HexDir.Left go end");
|
||
goto End;
|
||
}
|
||
|
||
node.dir = HexDir.LeftDown;
|
||
if(GetNextNode(node, newOpenList, StartCityID, EndCityID))
|
||
{
|
||
UnityEngine.Debug.Log("HexDir.LeftDown go end");
|
||
goto End;
|
||
}
|
||
}
|
||
break;
|
||
|
||
case HexDir.RightUp:
|
||
{
|
||
node.dir = HexDir.Right;
|
||
if(GetNextNode(node, newOpenList, StartCityID, EndCityID))
|
||
{
|
||
UnityEngine.Debug.Log("RightUp HexDir.Right go end");
|
||
goto End;
|
||
}
|
||
|
||
node.dir = HexDir.RightUp;
|
||
if(GetNextNode(node, newOpenList, StartCityID, EndCityID))
|
||
{
|
||
UnityEngine.Debug.Log("HexDir.RightUp go end");
|
||
goto End;
|
||
}
|
||
}
|
||
break;
|
||
|
||
case HexDir.RightDown:
|
||
{
|
||
node.dir = HexDir.Right;
|
||
if(GetNextNode(node, newOpenList, StartCityID, EndCityID))
|
||
{
|
||
UnityEngine.Debug.Log("RightDown HexDir.Right go end");
|
||
goto End;
|
||
}
|
||
|
||
node.dir = HexDir.RightDown;
|
||
if(GetNextNode(node, newOpenList, StartCityID, EndCityID))
|
||
{
|
||
UnityEngine.Debug.Log("HexDir.RightDown go end");
|
||
goto End;
|
||
}
|
||
}
|
||
break;
|
||
//*/
|
||
|
||
default:
|
||
{
|
||
if(GetNextNode(node, newOpenList, StartCityID, EndCityID))
|
||
{
|
||
UnityEngine.Debug.Log("default go end");
|
||
goto End;
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
|
||
}
|
||
|
||
openList.Clear();
|
||
openList.AddRange(newOpenList);
|
||
|
||
//UnityEngine.Debug.Log("NEXT ============================================== openList.Count=" + openList.Count);
|
||
}
|
||
|
||
End:
|
||
/*
|
||
PathNode pathNode2 = m_EndNode;
|
||
while(pathNode2.parent != null)
|
||
{
|
||
PathNode check = pathNode2.parent;
|
||
while(check.parent != null)
|
||
{
|
||
var dir = GetLineDir(pathNode2.hex, check.hex);
|
||
if(dir != HexDir.None)
|
||
{
|
||
List<MapHex> list = new List<MapHex>();
|
||
MapHex hex = pathNode2.hex;
|
||
while(true)
|
||
{
|
||
MapHex nextHex = GetNextHex(hex, dir);
|
||
if(nextHex.CityID <= 0)
|
||
{
|
||
if(nextHex.IsMe(check.hex))
|
||
{
|
||
break;
|
||
}
|
||
|
||
list.Add(nextHex);
|
||
hex = nextHex;
|
||
}
|
||
else
|
||
{
|
||
list.Clear();
|
||
break;
|
||
}
|
||
}
|
||
|
||
if(list.Count > 0)
|
||
{
|
||
PathNode newAdd = pathNode2;
|
||
while(list.Count > 0)
|
||
{
|
||
PathNode newNext = new PathNode();
|
||
newNext.hex = list[0];
|
||
newAdd.parent = newNext;
|
||
newAdd = newNext;
|
||
|
||
list.RemoveAt(0);
|
||
}
|
||
newAdd.parent = check;
|
||
}
|
||
}
|
||
|
||
check = check.parent;
|
||
}
|
||
|
||
pathNode2 = pathNode2.parent;
|
||
}
|
||
//*/
|
||
|
||
List<MapHex> result = new List<MapHex>();
|
||
int nCount = 0;
|
||
PathNode pathNode = m_EndNode;
|
||
while(pathNode.parent != null)
|
||
{
|
||
UnityEngine.Debug.Log(pathNode.hex);
|
||
|
||
result.Add(pathNode.hex);
|
||
//var go = Instantiate(PathGo);
|
||
//go.transform.SetParent(transform);
|
||
//go.transform.position = new Vector3(pathNode.hex.Wx, 0, pathNode.hex.Wz);
|
||
nCount ++;
|
||
|
||
pathNode = pathNode.parent;
|
||
}
|
||
|
||
UnityEngine.Debug.Log("all path node count="+ nCount);
|
||
|
||
return result;
|
||
}
|
||
|
||
MapHex GetMapHex(int Ax, int Ay, int Az)
|
||
{
|
||
int Nz = Az;
|
||
int Nx = Ax+Az/2;
|
||
//UnityEngine.Debug.Log($"GetMapHex Ax={Ax}, Ay={Ay}, Az={Az}, Nx={Nx}, Nz={Nz}");
|
||
|
||
if(Nx < 0 || Nx > m_mapData.Width-1)
|
||
{
|
||
//UnityEngine.Debug.LogError($"GetMapHex 1 not found Nx={Nx}, Nz={Nz}");
|
||
return null;
|
||
}
|
||
|
||
|
||
if(Nz < 0 || Nz > m_mapData.Height-1)
|
||
{
|
||
//UnityEngine.Debug.LogError($"GetMapHex 2 not found Nx={Nx}, Nz={Nz}");
|
||
return null;
|
||
}
|
||
|
||
|
||
int index = GetIndex(Nx, Nz);
|
||
return m_hexes[index];
|
||
}
|
||
|
||
public CityRoad GetCityRoad(int cityID) {
|
||
if(m_Roads.TryGetValue(cityID, out CityRoad v)) {
|
||
return v;
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/*
|
||
|
||
|
||
|
||
|
||
|
||
|
||
*/
|
||
MapHex GetNextHex(MapHex hex, HexDir dir)
|
||
{
|
||
switch(dir)
|
||
{
|
||
case HexDir.RightUp:
|
||
{
|
||
//往右上走,Ax不变,Az+1
|
||
int NextAx = hex.Ax;
|
||
int NextAz = hex.Az+1;
|
||
int NextAy = -NextAx-NextAz;
|
||
return GetMapHex(NextAx, NextAy, NextAz);
|
||
}
|
||
|
||
case HexDir.RightDown:
|
||
{
|
||
// 往右下走,Ay不变,Az-1
|
||
int NextAz = hex.Az-1;
|
||
int NextAy = hex.Ay;
|
||
int NextAx = -NextAz-NextAy;
|
||
return GetMapHex(NextAx, NextAy, NextAz);
|
||
}
|
||
|
||
case HexDir.LeftUp:
|
||
{
|
||
//往左上走,Ay不变,Az+1
|
||
int NextAy = hex.Ay;
|
||
int NextAz = hex.Az+1;
|
||
int NextAx = -NextAz-NextAy;
|
||
return GetMapHex(NextAx, NextAy, NextAz);
|
||
}
|
||
|
||
case HexDir.LeftDown:
|
||
{
|
||
//往左下走,Ax不变,Az-1
|
||
int NextAx = hex.Ax;
|
||
int NextAz = hex.Az-1;
|
||
int NextAy = -NextAx-NextAz;
|
||
return GetMapHex(NextAx, NextAy, NextAz);
|
||
}
|
||
|
||
case HexDir.Right:
|
||
{
|
||
//往右走,Az不变,Ay-1
|
||
int NextAz = hex.Az;
|
||
int NextAy = hex.Ay-1;
|
||
int NextAx = -NextAz-NextAy;
|
||
return GetMapHex(NextAx, NextAy, NextAz);
|
||
}
|
||
|
||
case HexDir.Left:
|
||
{
|
||
//往左走,Az不变,Ay+1
|
||
int NextAz = hex.Az;
|
||
int NextAy = hex.Ay+1;
|
||
int NextAx = -NextAz-NextAy;
|
||
return GetMapHex(NextAx, NextAy, NextAz);
|
||
}
|
||
|
||
default:
|
||
UnityEngine.Debug.LogError("GetNextHex invalid dir, dir=" + dir);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
// 获取指定阵营的空格子
|
||
List<MapHex> GetRoundEmptyHex(MapHex center) {
|
||
List<MapHex> result = new List<MapHex>();
|
||
|
||
int MaxX = center.Ax + 1;
|
||
int MinX = center.Ax - 1;
|
||
int MaxY = center.Ay + 1;
|
||
int MinY = center.Ay - 1;
|
||
int MaxZ = center.Az + 1;
|
||
int MinZ = center.Az - 1;
|
||
;
|
||
for (int x = MinX; x <= MaxX; x++) {
|
||
for (int y = MinY; y <= MaxY; y++) {
|
||
int z = -x - y;
|
||
if (z >= MinZ && z <= MaxZ) {
|
||
var hex = GetMapHex(x, y, z);
|
||
if (hex != null && hex != center && hex.CityID == 0 && !hex.isChecked) {
|
||
hex.isChecked = true;
|
||
result.Add(hex);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
public void SetRoundCamp(bool isBuilding, MapHex center, int camp , int length ) {
|
||
length--;
|
||
|
||
int MaxX = center.Ax + length;
|
||
int MinX = center.Ax - length;
|
||
int MaxY = center.Ay + length;
|
||
int MinY = center.Ay - length;
|
||
int MaxZ = center.Az + length;
|
||
int MinZ = center.Az - length;
|
||
|
||
for (int x = MinX; x <= MaxX; x++ ) {
|
||
for (int y = MinY; y <= MaxY; y++) {
|
||
int z = -x - y;
|
||
if (z >= MinZ && z <= MaxZ) {
|
||
var hex = GetMapHex(x, y, z);
|
||
if (hex != null) {
|
||
if (isBuilding) {
|
||
if(camp == (int)BattlefieldActorCamp.CampA) {
|
||
hex.Camp = (int)BattlefieldActorCamp.CampAbuilding;
|
||
} else if(camp == (int)BattlefieldActorCamp.CampB) {
|
||
hex.Camp = (int)BattlefieldActorCamp.CampBbuilding;
|
||
} else if (camp == (int)BattlefieldActorCamp.BacNone) {
|
||
if(hex.CityID <= 0) {
|
||
hex.Camp = hex.StaticCamp;
|
||
} else {
|
||
hex.Camp = camp;
|
||
}
|
||
} else {
|
||
hex.Camp = camp;
|
||
}
|
||
} else {
|
||
if (hex.CityID == 0 && hex.Camp < (int)BattlefieldActorCamp.CampMax) { // 如果不是设置建筑范围,则周围有建筑范围,不能更新
|
||
hex.Camp = camp;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
// 获取周围相同阵营,并且没有检查过的格子
|
||
List<MapHex> GetRoundUnchecked(MapHex center) {
|
||
List<MapHex> result = new List<MapHex>();
|
||
|
||
int MaxX = center.Ax + 1;
|
||
int MinX = center.Ax - 1;
|
||
int MaxY = center.Ay + 1;
|
||
int MinY = center.Ay - 1;
|
||
int MaxZ = center.Az + 1;
|
||
int MinZ = center.Az - 1;
|
||
|
||
for (int x = MinX; x <= MaxX; x++) {
|
||
for (int y = MinY; y <= MaxY; y++) {
|
||
int z = -x - y;
|
||
if (z >= MinZ && z <= MaxZ) {
|
||
var hex = GetMapHex(x, y, z);
|
||
if( hex != null && !hex.isChecked && hex.CityID == 0 && hex.GetRealCamp() == center.GetRealCamp() ){
|
||
hex.isChecked = true;
|
||
result.Add(hex);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
// 获取任意一个未检查过的格子
|
||
MapHex GetNextUnchecked() {
|
||
for(int i = 0; i < m_hexes.Length; i ++) {
|
||
var h = m_hexes[i];
|
||
if (!h.isChecked && h.CityID == 0 && h.IsRealCamp() ) {
|
||
h.isChecked = true;
|
||
return h;
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
|
||
void CheckRange(MapHex start) {
|
||
clearChecked();
|
||
|
||
List<MapArea> areas = new List<MapArea>();
|
||
|
||
var nextHex = start;
|
||
while(nextHex != null) {
|
||
var area = new MapArea();
|
||
areas.Add(area);
|
||
|
||
int checkIndex = 0;
|
||
List<MapHex> checkList = new List<MapHex>();
|
||
checkList.Add(nextHex);
|
||
while(checkIndex < checkList.Count) {
|
||
var current = checkList[checkIndex];
|
||
current.isChecked = true;
|
||
checkIndex++;
|
||
area.hexes.Add(current);
|
||
|
||
if (current.IsCampSource()) {
|
||
area.hasSource = true;
|
||
}
|
||
|
||
var result = GetRoundUnchecked(current);
|
||
if (result.Count > 0 ){
|
||
checkList.AddRange(result);
|
||
}
|
||
}
|
||
|
||
nextHex = GetNextUnchecked();
|
||
}
|
||
|
||
for (int i = 0; i < areas.Count; i ++) {
|
||
if (!areas[i].hasSource ){
|
||
areas[i].Reverse();
|
||
}
|
||
}
|
||
}
|
||
|
||
// 采用膨胀算法初始化势力范围
|
||
void InitRange( List<MapHex> BornPosA, List<MapHex> BornPosB) {
|
||
clearChecked();
|
||
|
||
int checkIndexA = 0;
|
||
List<MapHex> checkListA = new List<MapHex>();
|
||
checkListA.AddRange(BornPosA);
|
||
for (int i = 0; i < checkListA.Count; i ++) {
|
||
checkListA[i].isChecked = true;
|
||
checkListA[i].Camp = (int)BattlefieldActorCamp.CampAsource;
|
||
}
|
||
|
||
int checkIndexB = 0;
|
||
List<MapHex> checkListB = new List<MapHex>();
|
||
checkListB.AddRange(BornPosB);
|
||
for (int i = 0; i < checkListB.Count; i ++) {
|
||
checkListB[i].isChecked = true;
|
||
checkListB[i].Camp = (int)BattlefieldActorCamp.CampBsource;
|
||
}
|
||
|
||
bool working = true;
|
||
while(working) {
|
||
working = false;
|
||
if (checkIndexA < checkListA.Count) {
|
||
var current = checkListA[checkIndexA];
|
||
current.isChecked = true;
|
||
|
||
if (current.Camp == (int)BattlefieldActorCamp.BacNone) {
|
||
current.Camp = (int)BattlefieldActorCamp.CampA; // 设置格子所属势力
|
||
current.StaticCamp = (int)BattlefieldActorCamp.CampA;
|
||
}
|
||
|
||
var result = GetRoundEmptyHex(current); // 只获取周围没有阵营的空格子
|
||
if (result.Count > 0) {
|
||
checkListA.AddRange(result);
|
||
}
|
||
|
||
checkIndexA++;
|
||
if (checkIndexA < checkListA.Count) {
|
||
working = true;
|
||
}
|
||
}
|
||
|
||
if (checkIndexB < checkListB.Count) {
|
||
var current = checkListB[checkIndexB];
|
||
current.isChecked = true;
|
||
|
||
if (current.Camp == (int)BattlefieldActorCamp.BacNone) {
|
||
current.Camp = (int)BattlefieldActorCamp.CampB; // 设置格子所属势力
|
||
current.StaticCamp = (int)BattlefieldActorCamp.CampB;
|
||
}
|
||
|
||
var result = GetRoundEmptyHex(current); // 只获取周围没有阵营的空格子
|
||
if (result.Count > 0) {
|
||
checkListB.AddRange(result);
|
||
}
|
||
|
||
checkIndexB++;
|
||
if (checkIndexB < checkListB.Count) {
|
||
working = true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} |