using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using NeoSmart.AsyncLock;
using Nettention.Proud;
using Google.Protobuf.WellKnownTypes;
using ServerCore;
using ServerBase;
using ServerCommon;
using ServerCommon.BusinessLogDomain;
using MetaAssets;
using SESSION_ID = System.Int32;
using META_ID = System.UInt32;
using ENTITY_GUID = System.String;
using ANCHOR_GUID = System.String;
using USER_GUID = System.String;
namespace GameServer;
///
/// Map Class 에서 사용
///
public class AnchorInfo
{
public string AnchorGuid;
public Pos AnchorPos;
public AnchorProp AnchorProp;
public string OccupyingUserGuid = string.Empty;
public PropState PropState = PropState.None;
public Timestamp respawnTime = new();
private AsyncLock m_lock = new();
public AnchorInfo(string anchorGuid, Pos pos, AnchorProp prop)
{
AnchorGuid = anchorGuid;
AnchorPos = pos;
AnchorProp = prop;
}
public AsyncLock getAsyncLock() => m_lock;
}
internal class Cell
{
///
/// Grid에서 Cell의 X 좌표
///
int _cellinGridX;
///
/// Grid에서 Cell의 Y 좌표
///
int _cellinGridY;
ConcurrentDictionary _players = new ConcurrentDictionary();
ConcurrentDictionary _anchors = new();
ConcurrentDictionary _ugc_npcs = new();
List _startPoints = new();
StartPointType m_stat_point_type = StartPointType.None;
public HostID _P2PGroup = HostID.HostID_None;
public Cell(int cellinGridX, int cellinGridY, StartPointType startPointType)
{
ByteArray byArray = new ByteArray();
P2PGroupType p2PGroupType = new P2PGroupType();
p2PGroupType.Type = 1;
ServerBase.ProudNetHelper.convertP2PGroupToByteArray(byArray, p2PGroupType);
HostID[] arrHostID = new HostID[0];
_P2PGroup = GameServerApp.getServerLogic().getProudNetListener().getNetServer().CreateP2PGroup(arrHostID, byArray);
if (_P2PGroup == HostID.HostID_None)
{
Log.getLogger().error($"fail CreateP2PGroup");
}
_cellinGridX = cellinGridX;
_cellinGridY = cellinGridY;
m_stat_point_type = startPointType;
}
public void addPlayer(Player player)
{
_players.AddOrUpdate(player.getUserGuid(), player, (key, oldValue) => player);
SendP2PGroup(player);
}
public bool removePlayer(Player player)
{
return _players.TryRemove(player.getUserGuid(), out _);
}
public void AddAnchor(AnchorInfo anchor)
{
_anchors.AddOrUpdate(anchor.AnchorGuid, anchor, (key, oldValue) => anchor);
if (MapHelper.isStartPoint(anchor.AnchorGuid, m_stat_point_type))
{
_startPoints.Add(anchor);
}
}
public void RemoveAnchor(AnchorInfo anchor)
{
_anchors.TryRemove(anchor.AnchorGuid, out _);
}
public void addUgcNpc(UgcNpc ugcNpc)
{
_ugc_npcs.AddOrUpdate(ugcNpc.getEntityGuid(), ugcNpc, (key, oldValue) => ugcNpc);
}
public bool removeUgcNpc(UgcNpc ugcNpc)
{
return _ugc_npcs.TryRemove(ugcNpc.getEntityGuid(), out _);
}
public bool GetNearestStartPoint(Pos checkPos, out double nearestPoinstDistance, [MaybeNullWhen(false)] out Pos nearestPoint)
{
nearestPoinstDistance = -1;
Pos? minPos = null;
foreach (var startPoint in _startPoints)
{
var distance = Math.Sqrt(Math.Pow(checkPos.X - startPoint.AnchorPos.X, 2) + Math.Pow(checkPos.Y - startPoint.AnchorPos.Y, 2));
if (minPos == null || distance < nearestPoinstDistance)
{
nearestPoinstDistance = distance;
minPos = startPoint.AnchorPos.Clone();
}
}
if (minPos == null)
{
nearestPoint = null;
return false;
}
nearestPoint = minPos;
return true;
}
public void Broadcast(ClientToGame message)
{
HostID[] allClients = _players.Values.Select(x => x.getHostId()).ToArray();
GameServerApp.getServerLogic().onSendPacketToHosts(allClients, ServerCore.ProudNetHelper.compressRmi(), message);
}
void SendP2PGroup(Player player)
{
ClientToGame clientToGame = new ClientToGame();
clientToGame.Message = new ClientToGameMessage();
clientToGame.Message.P2PGroupHostIdNoti = new ClientToGameMessage.Types.P2PGroupHostIdNoti();
clientToGame.Message.P2PGroupHostIdNoti.P2PGroupHostId = (int)_P2PGroup;
GameServerApp.getServerLogic().onSendPacket(player, clientToGame);
}
public void CallFunc(Action action)
{
foreach (var player in _players.Values)
{
action(player);
}
}
public void CallFunc(Action action)
{
foreach (var ugc_ppc in _ugc_npcs.Values)
{
action(ugc_ppc);
}
}
public void CallFunc(Action action)
{
foreach (var anchor in _anchors.Values)
{
action(anchor);
}
}
public string toBasicString()
{
return $"Cell[CellInGridX:{_cellinGridX},CellInGridY:{_cellinGridY}]";
}
}
internal class Grid
{
Cell[,] _cells;
///
/// Map에서 Grid의 X 좌표
///
public int GridX { get; private set; }
///
/// Map에서 Grid의 Y 좌표
///
public int GridY { get; private set; }
readonly int _maxCellInGrid;
public Grid(int gridX, int gridY, bool isOneGrid, StartPointType startPointType)
{
if (isOneGrid)
{
_cells = new Cell[1, 1];
_maxCellInGrid = 1;
}
else
{
_cells = new Cell[Map.MAX_CELLS_IN_GRID, Map.MAX_CELLS_IN_GRID];
_maxCellInGrid = Map.MAX_CELLS_IN_GRID;
}
GridX = gridX;
GridY = gridY;
for (int cellinGridX = 0; cellinGridX < _cells.GetLength(0); cellinGridX++)
{
for (int cellinGridY = 0; cellinGridY < _cells.GetLength(1); cellinGridY++)
{
_cells[cellinGridX, cellinGridY] = new Cell(cellinGridX, cellinGridY, startPointType);
}
}
}
public Result tryAddPlayer(CellPos cellPos, Player player)
{
var result = new Result();
var err_msg = string.Empty;
if (cellPos.CellinGridX < 0 || cellPos.CellinGridX >= _maxCellInGrid)
{
err_msg = $"Position Out of Range X Grid Bounds !!! : 0 < {cellPos.CellinGridX} or {cellPos.CellinGridX} < {_maxCellInGrid} - {player.toBasicString()}";
result.setFail(ServerErrorCode.MapGridBoundOutOfRange, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
if (cellPos.CellinGridY < 0 || cellPos.CellinGridY >= _maxCellInGrid)
{
err_msg = $"Position Out of Range Y Grid Bounds !!! : 0 < {cellPos.CellinGridY} or {cellPos.CellinGridY} < {_maxCellInGrid} - {player.toBasicString()}";
result.setFail(ServerErrorCode.MapGridBoundOutOfRange, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
var found_cell = GetCell(cellPos.CellinGridX, cellPos.CellinGridY);
if (null == found_cell)
{
err_msg = $"Not found Cell !!! : {cellPos.toBasicString()} - {player.toBasicString()}";
result.setFail(ServerErrorCode.MapGridNotFoundCell, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
found_cell.addPlayer(player);
Log.getLogger().debug($"Player Enter in Cell Grid : {found_cell.toBasicString()}, {cellPos.toBasicString()} - {player.toBasicString()}");
return result;
}
public Result tryRemovePlayer(CellPos cellPos, Player player)
{
var result = new Result();
var err_msg = string.Empty;
if (cellPos.CellinGridX < 0 || cellPos.CellinGridX >= _maxCellInGrid)
{
err_msg = $"Position Out of Range X Grid Bounds !!! : 0 < {cellPos.CellinGridX} or {cellPos.CellinGridX} < {_maxCellInGrid} - {player.toBasicString()}";
result.setFail(ServerErrorCode.MapGridBoundOutOfRange, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
if (cellPos.CellinGridY < 0 || cellPos.CellinGridY >= _maxCellInGrid)
{
err_msg = $"Position Out of Range Y Grid Bounds !!! : 0 < {cellPos.CellinGridY} or {cellPos.CellinGridY} < {_maxCellInGrid} - {player.toBasicString()}";
result.setFail(ServerErrorCode.MapGridBoundOutOfRange, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
var found_cell = GetCell(cellPos.CellinGridX, cellPos.CellinGridY);
if(null == found_cell)
{
err_msg = $"Not found Cell !!! : {cellPos.toBasicString()} - {player.toBasicString()}";
result.setFail(ServerErrorCode.MapGridNotFoundCell, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
if(false == found_cell.removePlayer(player))
{
err_msg = $"Failed to RemovePlayer in Cell Grid !!! : {cellPos.toBasicString()} - {player.toBasicString()}";
Log.getLogger().warn(result.toBasicString());
}
Log.getLogger().debug($"Player Leave in Cell Grid : {found_cell.toBasicString()}, {cellPos.toBasicString()} - {player.toBasicString()}");
return result;
}
public void AddAnchor(CellPos cellPos, AnchorInfo anchor)
{
if (cellPos.CellinGridX < 0 || cellPos.CellinGridX >= _maxCellInGrid)
return;
if (cellPos.CellinGridY < 0 || cellPos.CellinGridY >= _maxCellInGrid)
return;
GetCell(cellPos.CellinGridX, cellPos.CellinGridY)?.AddAnchor(anchor);
}
public void RemoveAnchor(CellPos cellPos, AnchorInfo anchor)
{
if (cellPos.CellinGridX < 0 || cellPos.CellinGridX >= _maxCellInGrid)
return;
if (cellPos.CellinGridY < 0 || cellPos.CellinGridY >= _maxCellInGrid)
return;
GetCell(cellPos.CellinGridX, cellPos.CellinGridY)?.RemoveAnchor(anchor);
}
public Result addUgcNpc(CellPos cellPos, UgcNpc ugcNpc)
{
var result = new Result();
var err_msg = string.Empty;
if (cellPos.CellinGridX < 0 || cellPos.CellinGridX >= _maxCellInGrid)
{
err_msg = $"Position Out of Range X Grid Bounds !!! : 0 < {cellPos.CellinGridX} or {cellPos.CellinGridX} < {_maxCellInGrid} - {ugcNpc.toBasicString()}";
result.setFail(ServerErrorCode.MapGridBoundOutOfRange, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
if (cellPos.CellinGridY < 0 || cellPos.CellinGridY >= _maxCellInGrid)
{
err_msg = $"Position Out of Range Y Grid Bounds !!! : 0 < {cellPos.CellinGridY} or {cellPos.CellinGridY} < {_maxCellInGrid} - {ugcNpc.toBasicString()}";
result.setFail(ServerErrorCode.MapGridBoundOutOfRange, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
var found_cell = GetCell(cellPos.CellinGridX, cellPos.CellinGridY);
if (null == found_cell)
{
err_msg = $"Not found Cell !!! : {cellPos.toBasicString()} - {ugcNpc.toBasicString()}";
result.setFail(ServerErrorCode.MapGridNotFoundCell, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
found_cell.addUgcNpc(ugcNpc);
Log.getLogger().debug($"UgcNpc Enter in Cell Grid : {found_cell.toBasicString()}, {cellPos.toBasicString()} - {ugcNpc.toBasicString()}");
return result;
}
public Result removeUgcNpc(CellPos cellPos, UgcNpc ugcNpc)
{
var result = new Result();
var err_msg = string.Empty;
if (cellPos.CellinGridX < 0 || cellPos.CellinGridX >= _maxCellInGrid)
{
err_msg = $"Position Out of Range X Grid Bounds !!! : 0 < {cellPos.CellinGridX} or {cellPos.CellinGridX} < {_maxCellInGrid} - {ugcNpc.toBasicString()}";
result.setFail(ServerErrorCode.MapGridBoundOutOfRange, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
if (cellPos.CellinGridY < 0 || cellPos.CellinGridY >= _maxCellInGrid)
{
err_msg = $"Position Out of Range Y Grid Bounds !!! : 0 < {cellPos.CellinGridY} or {cellPos.CellinGridY} < {_maxCellInGrid} - {ugcNpc.toBasicString()}";
result.setFail(ServerErrorCode.MapGridBoundOutOfRange, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
var found_cell = GetCell(cellPos.CellinGridX, cellPos.CellinGridY);
if (null == found_cell)
{
err_msg = $"Not found Cell !!! : {cellPos.toBasicString()} - {ugcNpc.toBasicString()}";
result.setFail(ServerErrorCode.MapGridNotFoundCell, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
if (false == found_cell.removeUgcNpc(ugcNpc))
{
err_msg = $"Failed to RemovePlayer in Cell Grid !!! : {cellPos.toBasicString()} - {ugcNpc.toBasicString()}";
result.setFail(ServerErrorCode.MapGridCellNotFoundPlayer, err_msg);
Log.getLogger().error(result.toBasicString());
return result;
}
Log.getLogger().debug($"UgcNpc Leave in Cell Grid : {found_cell.toBasicString()}, {cellPos.toBasicString()} - {ugcNpc.toBasicString()}");
return result;
}
public Cell? GetCell(int cellinGridX, int cellinGridY)
{
if (cellinGridX < 0 || cellinGridX >= _maxCellInGrid)
return null;
if (cellinGridY < 0 || cellinGridY >= _maxCellInGrid)
return null;
return _cells[cellinGridX, cellinGridY];
}
public void DestroyP2PGroup()
{
foreach(var cell in _cells)
{
GameServerApp.getServerLogic().getProudNetListener().getNetServer()?.DestroyP2PGroup(cell._P2PGroup);
}
}
//instance 내 p2p host 설정을 위한 groupId 가져오는 함수
public HostID getP2PGroupId()
{
foreach(var cell in _cells)
{
return cell._P2PGroup;
}
Log.getLogger().error("cell._P2PGroup is not exist!!!!");
return 0;
}
}