From 17ba88e841b42e774ac8581f820fdc9f2cbd90b1 Mon Sep 17 00:00:00 2001 From: qornwh1 Date: Fri, 3 Apr 2026 01:38:36 +0900 Subject: [PATCH] =?UTF-8?q?feat=20:=20=EC=A1=B4=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B4=88=EA=B8=B0=20=EC=BB=A4=EB=B0=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MMOserver/Game/Channel/Channel.cs | 87 ++++++++---------- .../MMOserver/Game/Channel/Maps/AMap.cs | 79 ++++++++++++++-- .../MMOserver/Game/Channel/Maps/MapLoader.cs | 60 +++++++++++++ .../MMOserver/Game/Channel/Maps/Robby.cs | 36 +++++++- .../Game/Channel/Maps/ZoneData/Zone.cs | 89 +++++++++++++++++++ .../MMOserver/Game/Channel/UserContainer.cs | 47 ++++++++++ .../MMOserver/Game/Engine/Vector3.cs | 11 ++- MMOTestServer/MMOserver/Game/Player.cs | 28 +++--- .../MMOserver/Game/Service/GameServer.cs | 57 ++++++++++-- .../Game/Service/IntoChannelHandle.cs | 6 +- .../Game/Service/TransformPlayerHandler.cs | 25 +++++- MMOTestServer/MMOserver/MMOserver.csproj | 3 + MMOTestServer/MMOserver/Program.cs | 2 + MMOTestServer/MMOserver/maps.json | 51 +++++++++++ MMOTestServer/ServerLib/Service/ServerBase.cs | 1 + 15 files changed, 486 insertions(+), 96 deletions(-) create mode 100644 MMOTestServer/MMOserver/Game/Channel/Maps/MapLoader.cs create mode 100644 MMOTestServer/MMOserver/Game/Channel/Maps/ZoneData/Zone.cs create mode 100644 MMOTestServer/MMOserver/Game/Channel/UserContainer.cs create mode 100644 MMOTestServer/MMOserver/maps.json diff --git a/MMOTestServer/MMOserver/Game/Channel/Channel.cs b/MMOTestServer/MMOserver/Game/Channel/Channel.cs index 370207d..4b1e9d4 100644 --- a/MMOTestServer/MMOserver/Game/Channel/Channel.cs +++ b/MMOTestServer/MMOserver/Game/Channel/Channel.cs @@ -13,14 +13,11 @@ namespace MMOserver.Game.Channel; * - 인스턴스 던전 관리 * - 동적 맵 id 생성 */ -public class Channel +public class Channel : UserContainer { // 채널 내 유저 NetPeer (hashKey → NetPeer) — BroadcastToChannel 교차 조회 제거용 private readonly Dictionary connectPeers = new(); - // 채널 내 유저 상태 (hashKey → Player) - private readonly Dictionary connectUsers = new(); - // 채널 맵 관리 private readonly Dictionary maps = new(); @@ -37,17 +34,32 @@ public class Channel { ChannelId = channelId; - // 일단 하드코딩으로 맵 생성 + foreach (MapConfigData config in MapLoader.Data.Maps) { - // 로비 - maps.Add(1, new Robby(1)); - - // 인던 - int defaultValue = 1000; - for (int i = 1; i <= 10; i++) + AMap? map = MapLoader.ParseMapType(config.MapType) switch { - maps.Add(i + defaultValue, new BossInstance(i + defaultValue)); + EnumMap.ROBBY => new Robby(config.MapId), + EnumMap.DUNGEON => new BossInstance(config.MapId), + _ => null + }; + + if (map == null) + { + continue; } + + // 맵정보 + map.SetZoneConfig(config.Rows, config.Cols, config.ZoneSize); + // 맵내에 존 생성 + map.CreateZones(config.Zones); + maps.Add(config.MapId, map); + } + + // 인던은 동적 생성 유지 + int defaultValue = 1000; + for (int i = 1; i <= 10; i++) + { + maps.Add(i + defaultValue, new BossInstance(i + defaultValue)); } } @@ -56,12 +68,6 @@ public class Channel get; } - public int UserCount - { - get; - private set; - } - public int UserCountMax { get; @@ -70,28 +76,23 @@ public class Channel public void AddUser(int userId, Player player, NetPeer peer) { - connectUsers[userId] = player; + base.AddUser(userId, player); + users[userId] = player; connectPeers[userId] = peer; - UserCount++; // 처음 접속 시 1번 맵(로비)으로 입장 ChangeMap(userId, player, 1); } - public void RemoveUser(int userId) + public override void RemoveUser(int userId) { + base.RemoveUser(userId); // 현재 맵에서도 제거 - if (connectUsers.TryGetValue(userId, out Player? player) && - maps.TryGetValue(player.CurrentMapId, out AMap? currentMap)) + if (maps.TryGetValue(userId, out AMap? currentMap)) { currentMap.RemoveUser(userId); } - if (connectUsers.Remove(userId)) - { - UserCount--; - } - connectPeers.Remove(userId); } @@ -111,6 +112,10 @@ public class Channel newMap.AddUser(userId, player); player.CurrentMapId = mapId; + player.CurrentZoneId = 0; + + // 현재 맵에 0번 존에 추가 + GetMap(mapId)?.GetZone(0)?.AddUser(player.PlayerId, player); return true; } @@ -120,31 +125,12 @@ public class Channel connectPeers[userId] = peer; } - // 채널 내 모든 유저의 hashKey 반환 (채널 입장 시 기존 플레이어 목록 조회용) - public IEnumerable GetConnectUsers() - { - return connectUsers.Keys; - } - - // 채널 내 모든 Player 반환 - public IEnumerable GetPlayers() - { - return connectUsers.Values; - } - // 채널 내 모든 NetPeer 반환 — BroadcastToChannel 전용 (sessions 교차 조회 불필요) public IEnumerable GetConnectPeers() { return connectPeers.Values; } - // 특정 유저의 Player 반환 - public Player? GetPlayer(int userId) - { - connectUsers.TryGetValue(userId, out Player? player); - return player; - } - // 특정 유저의 NetPeer 반환 public NetPeer? GetPeer(int userId) { @@ -152,9 +138,10 @@ public class Channel return peer; } - public int HasUser(int userId) + // 유저가 존재하면 채널id를 넘긴다 + public override int HasUser(int userId) { - if (connectUsers.ContainsKey(userId)) + if (users.ContainsKey(userId)) { return ChannelId; } @@ -217,6 +204,4 @@ public class Channel { return partyManager; } - - // TODO : 채널 가져오기 } diff --git a/MMOTestServer/MMOserver/Game/Channel/Maps/AMap.cs b/MMOTestServer/MMOserver/Game/Channel/Maps/AMap.cs index 72afcb8..3574bf8 100644 --- a/MMOTestServer/MMOserver/Game/Channel/Maps/AMap.cs +++ b/MMOTestServer/MMOserver/Game/Channel/Maps/AMap.cs @@ -1,3 +1,6 @@ +using MMOserver.Game.Channel.Maps.ZoneData; +using MMOserver.Game.Engine; + namespace MMOserver.Game.Channel.Maps; /* @@ -5,25 +8,83 @@ namespace MMOserver.Game.Channel.Maps; * - 각 존 관리 * - 맵의 유저 관리 */ -public abstract class AMap +public abstract class AMap : UserContainer { - private Dictionary users = new Dictionary(); - public abstract EnumMap GetMapType(); public abstract int GetMapId(); - public void AddUser(int userId, Player player) + // 존 그리드 설정 (MapLoader에서 주입) + public int Rows { get; private set; } + public int Cols { get; private set; } + public int ZoneSize { get; private set; } + + // 존 관리 + private readonly Dictionary zones = new(); + + public void SetZoneConfig(int rows, int cols, int zoneSize) { - users[userId] = player; + Rows = rows; + Cols = cols; + ZoneSize = zoneSize; } - public void RemoveUser(int userId) + // 존 생성 + public void CreateZones(List zoneConfigs) { - users.Remove(userId); + foreach (ZoneConfigData config in zoneConfigs) + { + Vector3 position = new Vector3(config.CenterX, 0, config.CenterZ); + Vector3 size = new Vector3(ZoneSize, 0, ZoneSize); + Zone zone = new Zone(this, config.ZoneId, config.Row, config.Col, position, size); + AddZone(zone); + } } - public Dictionary GetUsers() + public void AddZone(Zone zone) { - return users; + zones[zone.ZoneId] = zone; + } + + public Zone? GetZone(int zoneId) + { + zones.TryGetValue(zoneId, out Zone? zone); + return zone; + } + + // 현재 맵의 근처 존 가져오기 + public virtual (int, int, int, int) GetNearZone(int zoneId, Vector3? position) + { + return (zoneId, -1, -1, -1); + } + + // 좌상단/우상단/우하단/좌하단 판정 + public int ZoneAt(int row, int col) + { + return (row >= 0 && row < Rows && col >= 0 && col < Cols) ? row * Cols + col : -1; + } + + // 이동시 zone업데이트 + public bool UpdatePlayerZone(int userId, Player player) + { + // 플레이어의 위치는 이동한 상태 + int col = (int)(player.Position.X / ZoneSize); + int row = (int)(player.Position.Z / ZoneSize); + int newZoneId = ZoneAt(row, col); + + if (newZoneId == player.CurrentZoneId) + { + return true; + } + + // 범위 밖으로 이동한 상태 일단 존은 그대로 둔다. + if (newZoneId < 0) + { + return false; + } + + GetZone(player.CurrentZoneId)?.RemoveUser(userId); + GetZone(newZoneId)!.AddUser(userId, player); + player.CurrentZoneId = newZoneId; + return true; } } diff --git a/MMOTestServer/MMOserver/Game/Channel/Maps/MapLoader.cs b/MMOTestServer/MMOserver/Game/Channel/Maps/MapLoader.cs new file mode 100644 index 0000000..6b2f62e --- /dev/null +++ b/MMOTestServer/MMOserver/Game/Channel/Maps/MapLoader.cs @@ -0,0 +1,60 @@ +using System.Text.Json; +using MMOserver.Game.Channel.Maps.InstanceDungeun; + +namespace MMOserver.Game.Channel.Maps; + +// Zone 데이터 구조 +public record ZoneConfigData( + int ZoneId, + int Row, + int Col, + int CenterX, + int CenterZ +); + +// Map 데이터 구조 +public record MapConfigData( + int MapId, + string MapType, + int Rows, + int Cols, + int ZoneSize, + List Zones +); + +// 한 채널의 Map정보 List +public record MapFileData( + List Maps +); + +/* + * maps.json 로더 + * - JSON 파싱만 담당 + * - Program.cs에서 Initialize() 1회 호출 + */ +public static class MapLoader +{ + private static readonly JsonSerializerOptions Options = new() + { + PropertyNameCaseInsensitive = true + }; + + public static MapFileData Data { get; private set; } = null!; + + public static void Initialize() + { + string json = File.ReadAllText("maps.json"); + Data = JsonSerializer.Deserialize(json, Options) ?? throw new InvalidOperationException("maps.json 파싱 실패"); + } + + public static EnumMap ParseMapType(string mapType) + { + return mapType.ToUpper() switch + { + "ROBBY" => EnumMap.ROBBY, + "DUNGEON" => EnumMap.DUNGEON, + "INSTANCE" => EnumMap.INSTANCE, + _ => EnumMap.NONE + }; + } +} diff --git a/MMOTestServer/MMOserver/Game/Channel/Maps/Robby.cs b/MMOTestServer/MMOserver/Game/Channel/Maps/Robby.cs index 34199b5..58b6fca 100644 --- a/MMOTestServer/MMOserver/Game/Channel/Maps/Robby.cs +++ b/MMOTestServer/MMOserver/Game/Channel/Maps/Robby.cs @@ -1,4 +1,5 @@ -using MMOserver.Game.Engine; +using MMOserver.Game.Channel.Maps.ZoneData; +using MMOserver.Game.Engine; namespace MMOserver.Game.Channel.Maps; @@ -7,10 +8,10 @@ namespace MMOserver.Game.Channel.Maps; */ public class Robby : AMap { - private EnumMap enumMap; - private int mapId; + private readonly EnumMap enumMap; + private readonly int mapId; - public Robby(int mapId, EnumMap enumMap = EnumMap.ROBBY) + public Robby(int mapId, EnumMap enumMap = EnumMap.ROBBY) { this.enumMap = enumMap; this.mapId = mapId; @@ -25,4 +26,31 @@ public class Robby : AMap { return mapId; } + + public override (int, int, int, int) GetNearZone(int zoneId, Vector3? position) + { + if (position == null || zoneId < 0) + { + return base.GetNearZone(zoneId, position); + } + + Zone? zone = GetZone(zoneId); + if (zone == null) + { + return base.GetNearZone(zoneId, position); + } + + int quadrant = zone.GetFourquadrant(position); + int row = zone.Row; + int col = zone.Col; + + return quadrant switch + { + 1 => (zoneId, ZoneAt(row - 1, col), ZoneAt(row, col - 1), ZoneAt(row - 1, col - 1)), // 위, 좌, 좌상단 + 2 => (zoneId, ZoneAt(row - 1, col), ZoneAt(row, col + 1), ZoneAt(row - 1, col + 1)), // 위, 우, 우상단 + 3 => (zoneId, ZoneAt(row + 1, col), ZoneAt(row, col + 1), ZoneAt(row + 1, col + 1)), // 아래, 우, 우하단 + 4 => (zoneId, ZoneAt(row + 1, col), ZoneAt(row, col - 1), ZoneAt(row + 1, col - 1)), // 아래, 좌, 좌하단 + _ => base.GetNearZone(zoneId, position) // 0 = 중앙, 현재 존만 + }; + } } diff --git a/MMOTestServer/MMOserver/Game/Channel/Maps/ZoneData/Zone.cs b/MMOTestServer/MMOserver/Game/Channel/Maps/ZoneData/Zone.cs new file mode 100644 index 0000000..97b1b67 --- /dev/null +++ b/MMOTestServer/MMOserver/Game/Channel/Maps/ZoneData/Zone.cs @@ -0,0 +1,89 @@ +using MMOserver.Game.Engine; + +namespace MMOserver.Game.Channel.Maps.ZoneData; + +/* + * 존 단위로 변경시킨다. + * 기존 맵 단위로 유저관리를 존 단위로 나눈다. + * 마을 존 / 전투 필드 존 타입을 분기 시킨다. + */ +public class Zone : UserContainer +{ + public Zone(AMap map, int zoneId, int row, int col, Vector3 position, Vector3 size) + { + Map = new WeakReference(map); + ZoneId = zoneId; + Row = row; + Col = col; + Position = position; + ZoneSize = size; + } + + // Map은 약한참조를 들고 있는다 순환참조 방지 + public WeakReference Map + { + get; + private set; + } + + // 그리드 인덱스 (zoneId = Row * Cols + Col) + public int ZoneId + { + get; + private set; + } + public int Row + { + get; + private set; + } + public int Col + { + get; + private set; + } + + // 존 중심 좌표 + public Vector3 Position + { + get; + private set; + } + + // 존 사이즈 + public Vector3 ZoneSize + { + get; + } + + // 플레이어 위치 기준 현재 존 내 분면 반환 + public int GetFourquadrant(Vector3 position) + { + float dx = position.X - Position.X; + float dz = position.Z - Position.Z; + + // 중앙 영역 (존 크기의 1/4 이내) → 현재 존만 브로드캐스트 + float threshold = ZoneSize.X / 4; + if (Math.Abs(dx) <= threshold && Math.Abs(dz) <= threshold) + { + return 0; + } + if (dx <= 0 && dz <= 0) + { + // 좌상단 + return 1; + } + if (dx > 0 && dz <= 0) + { + // 우상단 + return 2; + } + if (dx > 0 && dz > 0) + { + // 우하단 + return 3; + } + // 좌하단 + return 4; + } +} diff --git a/MMOTestServer/MMOserver/Game/Channel/UserContainer.cs b/MMOTestServer/MMOserver/Game/Channel/UserContainer.cs new file mode 100644 index 0000000..9e26bc7 --- /dev/null +++ b/MMOTestServer/MMOserver/Game/Channel/UserContainer.cs @@ -0,0 +1,47 @@ +using MMOserver.Game.Service; +using ServerLib.Service; + +namespace MMOserver.Game.Channel; + +public abstract class UserContainer +{ + protected readonly Dictionary users = new(); + + public virtual void AddUser(int userId, Player player) + { + users.Add(userId, player); + } + + public virtual void RemoveUser(int userId) + { + users.Remove(userId); + } + + public virtual int HasUser(int userId) + { + return users.ContainsKey(userId) ? userId : -1; + } + + public virtual Player? GetPlayer(int userId) + { + users.TryGetValue(userId, out Player? player); + return player; + } + + public virtual IEnumerable GetPlayers() + { + return users.Values; + } + + public IEnumerable GetConnectUsers() + { + return users.Keys; + } + + public Dictionary GetUsers() + { + return users; + } + + public int UserCount => users.Count; +} diff --git a/MMOTestServer/MMOserver/Game/Engine/Vector3.cs b/MMOTestServer/MMOserver/Game/Engine/Vector3.cs index f702748..a9ec2de 100644 --- a/MMOTestServer/MMOserver/Game/Engine/Vector3.cs +++ b/MMOTestServer/MMOserver/Game/Engine/Vector3.cs @@ -2,19 +2,19 @@ public class Vector3 { - public int X + public float X { get; set; } - public int Y + public float Y { get; set; } - public int Z + public float Z { get; set; @@ -33,4 +33,9 @@ public class Vector3 Y = y; // 수직 사실상 안쓰겠다. Z = z; } + + public static Vector3 Zero() + { + return new Vector3(0, 0, 0); + } } diff --git a/MMOTestServer/MMOserver/Game/Player.cs b/MMOTestServer/MMOserver/Game/Player.cs index ca64309..49e8d06 100644 --- a/MMOTestServer/MMOserver/Game/Player.cs +++ b/MMOTestServer/MMOserver/Game/Player.cs @@ -1,3 +1,4 @@ +using MMOserver.Game.Engine; using MMOserver.Packet; namespace MMOserver.Game; @@ -82,24 +83,11 @@ public class Player set; } - // 위치/방향 (클라이언트 패킷과 동일하게 float) - public float PosX + public Vector3 Position { get; set; - } - - public float PosY - { - get; - set; - } - - public float PosZ - { - get; - set; - } + } = new Vector3(0, 0, 0); public float RotY { @@ -121,6 +109,14 @@ public class Player set; } + // 현재 위치한 맵의 존 ID + public int CurrentZoneId + { + get; + set; + } = 0; + + // 패킷용 전환 public PlayerInfo ToPlayerInfo() { return new PlayerInfo @@ -132,7 +128,7 @@ public class Player MaxHp = this.MaxHp, Mp = this.Mp, MaxMp = this.MaxMp, - Position = new Position { X = this.PosX, Y = this.PosY, Z = this.PosZ }, + Position = new Position { X = this.Position.X, Y = this.Position.Y, Z = this.Position.Z }, RotY = this.RotY, Experience = this.Experience, NextExp = this.NextExp, diff --git a/MMOTestServer/MMOserver/Game/Service/GameServer.cs b/MMOTestServer/MMOserver/Game/Service/GameServer.cs index c40327d..49b41ad 100644 --- a/MMOTestServer/MMOserver/Game/Service/GameServer.cs +++ b/MMOTestServer/MMOserver/Game/Service/GameServer.cs @@ -3,6 +3,8 @@ using LiteNetLib.Utils; using MMOserver.Api; using MMOserver.Game.Channel; using MMOserver.Game.Channel.Maps; +using MMOserver.Game.Channel.Maps.ZoneData; +using MMOserver.Game.Engine; using MMOserver.Game.Party; using MMOserver.Packet; using MMOserver.Utils; @@ -93,8 +95,10 @@ public partial class GameServer : ServerBase HashKey = hashKey, PlayerId = hashKey, Nickname = hashKey.ToString(), - CurrentMapId = 1 + CurrentMapId = 1, + CurrentZoneId = 0, }; + ChannelManager.Instance.GetChannel(1).GetMap(0)?.GetZone(0)?.AddUser(hashKey, newPlayer); cm.AddUser(1, hashKey, newPlayer, peer); } @@ -161,7 +165,7 @@ public partial class GameServer : ServerBase Log.Information("[Server] 토큰 검증 성공 Username={Username} PeerId={Id}", username, peer.Id); } - // await 이후 — 공유 자원 접근 보호 + // 공유 자원 접근 보호 lock (sessionLock) { peer.Tag = new Session(hashKey, peer); @@ -224,7 +228,7 @@ public partial class GameServer : ServerBase } _ = RestApi.Instance.SaveGameDataAsync( session.Username, - player.PosX, player.PosY, player.PosZ, player.RotY, + player.Position.X, player.Position.Y, player.Position.Z, player.RotY, playTimeDelta > 0 ? playTimeDelta : null ); } @@ -320,7 +324,7 @@ public partial class GameServer : ServerBase Channel.Channel channel = cm.GetChannel(channelId); Player? myPlayer = channel.GetPlayer(hashKey); - // 1. 새 유저에게: 자신을 제외한 기존 채널 유저 목록 + 파티 목록 전송 + // 새 유저에게 자신을 제외한 기존 채널 유저 목록 + 파티 목록 전송 IntoChannelPacket response = new() { ChannelId = channelId }; foreach (int userId in channel.GetConnectUsers()) { @@ -350,7 +354,7 @@ public partial class GameServer : ServerBase byte[] toNewUser = PacketSerializer.Serialize((ushort)PacketCode.INTO_CHANNEL, response); SendTo(peer, toNewUser); - // 2. 기존 유저들에게: 새 유저 입장 알림 + // 기존 유저들에게 새 유저 입장 알림 if (myPlayer != null) { UpdateChannelUserPacket notify = new() @@ -423,7 +427,8 @@ public partial class GameServer : ServerBase foreach (int userId in map.GetUsers().Keys) { - if (!sessions.TryGetValue(userId, out NetPeer? targetPeer)) + NetPeer? targetPeer = ChannelManager.Instance.GetChannel(channelId).GetPeer(userId); + if (targetPeer == null) { continue; } @@ -437,6 +442,46 @@ public partial class GameServer : ServerBase } } + // 특정 맵의 근처 유저들에게 전송 (exclude 지정 시 해당 피어 제외) + private void BroadcastToNear(int channelId, int mapId, int zoneId, Vector3 position, byte[] data, NetPeer? exclude = null, DeliveryMethod method = DeliveryMethod.ReliableOrdered) + { + Channel.Channel channel = ChannelManager.Instance.GetChannel(channelId); + AMap? map = channel.GetMap(mapId); + if (map == null) + { + return; + } + + // 인접한 존을 가져온다. + (int zoneId1, int zoneId2, int zoneId3, int zoneId4) = map.GetNearZone(mapId, position); + // new / []로 동적할당 비용 감소를 위해 Span 스택에 할당되는 방식사용 + Span zoneIds = [zoneId1, zoneId2, zoneId3, zoneId4]; + foreach (int nearZoneId in zoneIds) + { + Zone? nearZone = map.GetZone(nearZoneId); + if (nearZone == null) + { + continue; + } + + foreach (int userId in nearZone.GetUsers().Keys) + { + NetPeer? targetPeer = channel.GetPeer(userId); + if (targetPeer == null) + { + continue; + } + + if (exclude != null && targetPeer.Id == exclude.Id) + { + continue; + } + + SendTo(targetPeer, data, method); + } + } + } + // 채널 퇴장/연결 해제 시 파티 자동 탈퇴 처리 private void HandlePartyLeaveOnExit(int channelId, int hashKey) { diff --git a/MMOTestServer/MMOserver/Game/Service/IntoChannelHandle.cs b/MMOTestServer/MMOserver/Game/Service/IntoChannelHandle.cs index bf774e9..cac9614 100644 --- a/MMOTestServer/MMOserver/Game/Service/IntoChannelHandle.cs +++ b/MMOTestServer/MMOserver/Game/Service/IntoChannelHandle.cs @@ -92,9 +92,9 @@ public partial class GameServer : ServerBase newPlayer.AttackPower = (float)profile.AttackPower; newPlayer.AttackRange = (float)profile.AttackRange; newPlayer.SprintMultiplier = (float)profile.SprintMultiplier; - newPlayer.PosX = (float)profile.LastPosX; - newPlayer.PosY = (float)profile.LastPosY; - newPlayer.PosZ = (float)profile.LastPosZ; + newPlayer.Position.X = (float)profile.LastPosX; + newPlayer.Position.Y = (float)profile.LastPosY; + newPlayer.Position.Z = (float)profile.LastPosZ; newPlayer.RotY = (float)profile.LastRotY; Log.Information("[GameServer] 프로필 로드 완료 Username={Username} Level={Level} MaxHp={MaxHp}", username, profile.Level, profile.MaxHp); diff --git a/MMOTestServer/MMOserver/Game/Service/TransformPlayerHandler.cs b/MMOTestServer/MMOserver/Game/Service/TransformPlayerHandler.cs index 05bab32..880a90f 100644 --- a/MMOTestServer/MMOserver/Game/Service/TransformPlayerHandler.cs +++ b/MMOTestServer/MMOserver/Game/Service/TransformPlayerHandler.cs @@ -1,7 +1,9 @@ using LiteNetLib; using MMOserver.Game.Channel; +using MMOserver.Game.Channel.Maps; using MMOserver.Packet; using ProtoBuf; +using Serilog; using ServerLib.Packet; using ServerLib.Service; @@ -30,13 +32,28 @@ public partial class GameServer : ServerBase return; } - player.PosX = packet.Position.X; - player.PosY = packet.Position.Y; - player.PosZ = packet.Position.Z; + AMap? map = cm.GetChannel(channelId).GetMap(player.CurrentMapId); + if (map == null) + { + return; + } + + player.Position.X = packet.Position.X; + player.Position.Y = packet.Position.Y; + player.Position.Z = packet.Position.Z; player.RotY = packet.RotY; // 같은 맵 유저들에게 위치/방향 브로드캐스트 (나 제외) byte[] data = PacketSerializer.Serialize((ushort)PacketCode.TRANSFORM_PLAYER, packet); - BroadcastToMap(channelId, player.CurrentMapId, data, peer, DeliveryMethod.Unreliable); + int mapId = player.CurrentMapId; + int zoneId = player.CurrentZoneId; + + // 유저가 이동가는한 공간을 탈출하면 일단 리턴 + if (!map.UpdatePlayerZone(hashKey, player)) + { + Log.Warning("플레이어 이동이 불가는한 공간에 이동했습니다."); + return; + } + BroadcastToNear(channelId, mapId, zoneId, player.Position, data, peer, DeliveryMethod.Unreliable); } } diff --git a/MMOTestServer/MMOserver/MMOserver.csproj b/MMOTestServer/MMOserver/MMOserver.csproj index bc0803a..9e4070e 100644 --- a/MMOTestServer/MMOserver/MMOserver.csproj +++ b/MMOTestServer/MMOserver/MMOserver.csproj @@ -29,6 +29,9 @@ Always + + PreserveNewest + diff --git a/MMOTestServer/MMOserver/Program.cs b/MMOTestServer/MMOserver/Program.cs index 7b95d24..082cf24 100644 --- a/MMOTestServer/MMOserver/Program.cs +++ b/MMOTestServer/MMOserver/Program.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Configuration; using MMOserver.Config; +using MMOserver.Game.Channel.Maps; using MMOserver.Game.Service; using MMOserver.RDB; using Serilog; @@ -23,6 +24,7 @@ class Program .Build(); AppConfig.Initialize(config); + MapLoader.Initialize(); // DB 연결 // DbConnectionFactory dbFactory = new DbConnectionFactory(config); diff --git a/MMOTestServer/MMOserver/maps.json b/MMOTestServer/MMOserver/maps.json new file mode 100644 index 0000000..425ba3e --- /dev/null +++ b/MMOTestServer/MMOserver/maps.json @@ -0,0 +1,51 @@ +{ + "maps": [ + { + "mapId": 1, + "mapType": "ROBBY", + "rows": 2, + "cols": 2, + "zoneSize": 50, + "zones": [ + { "zoneId": 0, "row": 0, "col": 0, "centerX": 25, "centerZ": 25 }, + { "zoneId": 1, "row": 0, "col": 1, "centerX": 75, "centerZ": 25 }, + { "zoneId": 2, "row": 1, "col": 0, "centerX": 25, "centerZ": 75 }, + { "zoneId": 3, "row": 1, "col": 1, "centerX": 75, "centerZ": 75 } + ] + }, + { + "mapId": 2, + "mapType": "DUNGEON", + "rows": 5, + "cols": 5, + "zoneSize": 100, + "zones": [ + { "zoneId": 0, "row": 0, "col": 0, "centerX": 50, "centerZ": 50 }, + { "zoneId": 1, "row": 0, "col": 1, "centerX": 150, "centerZ": 50 }, + { "zoneId": 2, "row": 0, "col": 2, "centerX": 250, "centerZ": 50 }, + { "zoneId": 3, "row": 0, "col": 3, "centerX": 350, "centerZ": 50 }, + { "zoneId": 4, "row": 0, "col": 4, "centerX": 450, "centerZ": 50 }, + { "zoneId": 5, "row": 1, "col": 0, "centerX": 50, "centerZ": 150 }, + { "zoneId": 6, "row": 1, "col": 1, "centerX": 150, "centerZ": 150 }, + { "zoneId": 7, "row": 1, "col": 2, "centerX": 250, "centerZ": 150 }, + { "zoneId": 8, "row": 1, "col": 3, "centerX": 350, "centerZ": 150 }, + { "zoneId": 9, "row": 1, "col": 4, "centerX": 450, "centerZ": 150 }, + { "zoneId": 10, "row": 2, "col": 0, "centerX": 50, "centerZ": 250 }, + { "zoneId": 11, "row": 2, "col": 1, "centerX": 150, "centerZ": 250 }, + { "zoneId": 12, "row": 2, "col": 2, "centerX": 250, "centerZ": 250 }, + { "zoneId": 13, "row": 2, "col": 3, "centerX": 350, "centerZ": 250 }, + { "zoneId": 14, "row": 2, "col": 4, "centerX": 450, "centerZ": 250 }, + { "zoneId": 15, "row": 3, "col": 0, "centerX": 50, "centerZ": 350 }, + { "zoneId": 16, "row": 3, "col": 1, "centerX": 150, "centerZ": 350 }, + { "zoneId": 17, "row": 3, "col": 2, "centerX": 250, "centerZ": 350 }, + { "zoneId": 18, "row": 3, "col": 3, "centerX": 350, "centerZ": 350 }, + { "zoneId": 19, "row": 3, "col": 4, "centerX": 450, "centerZ": 350 }, + { "zoneId": 20, "row": 4, "col": 0, "centerX": 50, "centerZ": 450 }, + { "zoneId": 21, "row": 4, "col": 1, "centerX": 150, "centerZ": 450 }, + { "zoneId": 22, "row": 4, "col": 2, "centerX": 250, "centerZ": 450 }, + { "zoneId": 23, "row": 4, "col": 3, "centerX": 350, "centerZ": 450 }, + { "zoneId": 24, "row": 4, "col": 4, "centerX": 450, "centerZ": 450 } + ] + } + ] +} diff --git a/MMOTestServer/ServerLib/Service/ServerBase.cs b/MMOTestServer/ServerLib/Service/ServerBase.cs index 7708192..cfd4312 100644 --- a/MMOTestServer/ServerLib/Service/ServerBase.cs +++ b/MMOTestServer/ServerLib/Service/ServerBase.cs @@ -62,6 +62,7 @@ public abstract class ServerBase : INetEventListener get; } + // 최적화 되면 안되므로 volatile로 선언 private volatile bool isListening = false; public ServerBase(int port, string connectionString)