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; } }