From e4429177dbcde3d8bfe871aca5300f2e5f3c6f55 Mon Sep 17 00:00:00 2001 From: qornwh1 Date: Mon, 16 Mar 2026 14:50:29 +0900 Subject: [PATCH] =?UTF-8?q?feat=20:=20=EB=A7=B5=EA=B4=80=EB=A6=AC=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80=20=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MMOserver/Game/Channel/Channel.cs | 83 ++++++++++++++---- .../MMOserver/Game/Channel/Maps/AMap.cs | 24 +++++ .../MMOserver/Game/Channel/Maps/EunmMap.cs | 8 ++ .../Maps/InstanceDungeun/BossInstance.cs | 34 ++++++++ .../MMOserver/Game/Channel/Maps/Robby.cs | 19 +++- MMOTestServer/MMOserver/Game/GameServer.cs | 87 ++++++++++++++++++- MMOTestServer/MMOserver/Game/Player.cs | 7 ++ MMOTestServer/MMOserver/Packet/PacketBody.cs | 35 ++++++++ .../MMOserver/Packet/PacketHeader.cs | 6 ++ 9 files changed, 282 insertions(+), 21 deletions(-) create mode 100644 MMOTestServer/MMOserver/Game/Channel/Maps/AMap.cs create mode 100644 MMOTestServer/MMOserver/Game/Channel/Maps/EunmMap.cs create mode 100644 MMOTestServer/MMOserver/Game/Channel/Maps/InstanceDungeun/BossInstance.cs diff --git a/MMOTestServer/MMOserver/Game/Channel/Channel.cs b/MMOTestServer/MMOserver/Game/Channel/Channel.cs index cc6996f..48b2799 100644 --- a/MMOTestServer/MMOserver/Game/Channel/Channel.cs +++ b/MMOTestServer/MMOserver/Game/Channel/Channel.cs @@ -1,27 +1,45 @@ using LiteNetLib; using MMOserver.Game.Channel.Maps; +using MMOserver.Game.Channel.Maps.InstanceDungeun; using MMOserver.Game.Party; namespace MMOserver.Game.Channel; public class Channel { - // 로비 - private Robby robby = new Robby(); - - // 파티 - private PartyManager partyManager = new PartyManager(); + // 채널 내 유저 NetPeer (hashKey → NetPeer) — BroadcastToChannel 교차 조회 제거용 + private readonly Dictionary connectPeers = new(); // 채널 내 유저 상태 (hashKey → Player) - private Dictionary connectUsers = new Dictionary(); + private readonly Dictionary connectUsers = new(); - // 채널 내 유저 NetPeer (hashKey → NetPeer) — BroadcastToChannel 교차 조회 제거용 - private Dictionary connectPeers = new Dictionary(); + // 채널 맵 관리 + private readonly Dictionary maps = new(); + + // 파티 + private readonly PartyManager partyManager = new(); + + public Channel(int channelId) + { + ChannelId = channelId; + + // 일단 하드코딩으로 맵 생성 + { + // 로비 + maps.Add(1, new Robby(1)); + + // 인던 + int defaultValue = 10; + for (int i = 1; i <= 10; i++) + { + maps.Add(i + defaultValue, new BossInstance(i + defaultValue)); + } + } + } public int ChannelId { get; - private set; } public int UserCount @@ -36,25 +54,48 @@ public class Channel private set; } = 100; - public Channel(int channelId) - { - ChannelId = channelId; - } - public void AddUser(int userId, Player player, NetPeer peer) { connectUsers[userId] = player; connectPeers[userId] = peer; UserCount++; + + // 처음 접속 시 1번 맵(로비)으로 입장 + ChangeMap(userId, player, 1); } public void RemoveUser(int userId) { + // 현재 맵에서도 제거 + if (connectUsers.TryGetValue(userId, out Player? player)) + { + maps[player.CurrentMapId].RemoveUser(userId); + } + connectUsers.Remove(userId); connectPeers.Remove(userId); UserCount--; } + // 맵 이동 (현재 맵 제거 → 새 맵 추가 → CurrentMapId 갱신) + public bool ChangeMap(int userId, Player player, int mapId) + { + if (!maps.TryGetValue(mapId, out AMap? newMap)) + { + return false; + } + + // 기존 맵에서 제거 + if (maps.TryGetValue(player.CurrentMapId, out AMap? oldMap)) + { + oldMap.RemoveUser(userId); + } + + newMap.AddUser(userId, player); + player.CurrentMapId = mapId; + return true; + } + // 재연결(WiFi→LTE 등) 시 동일 유저의 peer 교체 public void UpdatePeer(int userId, NetPeer peer) { @@ -96,10 +137,18 @@ public class Channel return -1; } - // 로비 가져옴 - public Robby GetRobby() + // 맵들 가져옴 + public Dictionary GetMaps() { - return robby; + return maps; + } + + // 맵 가져옴 + public AMap? GetMap(int mapId) + { + AMap? map = null; + maps.TryGetValue(mapId, out map); + return map; } // 파티매니저 가져옴 diff --git a/MMOTestServer/MMOserver/Game/Channel/Maps/AMap.cs b/MMOTestServer/MMOserver/Game/Channel/Maps/AMap.cs new file mode 100644 index 0000000..cda1f4d --- /dev/null +++ b/MMOTestServer/MMOserver/Game/Channel/Maps/AMap.cs @@ -0,0 +1,24 @@ +namespace MMOserver.Game.Channel.Maps; + +public abstract class AMap +{ + private Dictionary users = new Dictionary(); + + public abstract EnumMap GetMapType(); + public abstract int GetMapId(); + + public void AddUser(int userId, Player player) + { + users[userId] = player; + } + + public void RemoveUser(int userId) + { + users.Remove(userId); + } + + public Dictionary GetUsers() + { + return users; + } +} diff --git a/MMOTestServer/MMOserver/Game/Channel/Maps/EunmMap.cs b/MMOTestServer/MMOserver/Game/Channel/Maps/EunmMap.cs new file mode 100644 index 0000000..2380ed9 --- /dev/null +++ b/MMOTestServer/MMOserver/Game/Channel/Maps/EunmMap.cs @@ -0,0 +1,8 @@ +namespace MMOserver.Game.Channel.Maps; + +public enum EnumMap : int +{ + NONE = 0, + ROBBY = 1, + INSTANCE = 10, +} diff --git a/MMOTestServer/MMOserver/Game/Channel/Maps/InstanceDungeun/BossInstance.cs b/MMOTestServer/MMOserver/Game/Channel/Maps/InstanceDungeun/BossInstance.cs new file mode 100644 index 0000000..e9b6f9e --- /dev/null +++ b/MMOTestServer/MMOserver/Game/Channel/Maps/InstanceDungeun/BossInstance.cs @@ -0,0 +1,34 @@ +using MMOserver.Game.Channel.Maps; +using MMOserver.Game.Engine; + +namespace MMOserver.Game.Channel.Maps.InstanceDungeun; + +// 인스턴스 보스 맵에 들어갈때 쓰는 것 +public class BossInstance : AMap +{ + private EnumMap enumMap; + private int mapId; + + // 마을 시작 지점 넣어 둔다. + public static Vector3 StartPosition + { + get; + set; + } = new Vector3(0, 0, 0); + + public BossInstance(int mapId, EnumMap enumMap = EnumMap.INSTANCE) + { + this.enumMap = enumMap; + this.mapId = mapId; + } + + public override EnumMap GetMapType() + { + return enumMap; + } + + public override int GetMapId() + { + return mapId; + } +} diff --git a/MMOTestServer/MMOserver/Game/Channel/Maps/Robby.cs b/MMOTestServer/MMOserver/Game/Channel/Maps/Robby.cs index 14985d8..d8890c5 100644 --- a/MMOTestServer/MMOserver/Game/Channel/Maps/Robby.cs +++ b/MMOTestServer/MMOserver/Game/Channel/Maps/Robby.cs @@ -2,8 +2,11 @@ namespace MMOserver.Game.Channel.Maps; -public class Robby +public class Robby : AMap { + private EnumMap enumMap; + private int mapId; + // 마을 시작 지점 넣어 둔다. public static Vector3 StartPosition { @@ -11,7 +14,19 @@ public class Robby set; } = new Vector3(0, 0, 0); - public Robby() + public Robby(int mapId, EnumMap enumMap = EnumMap.ROBBY) { + this.enumMap = enumMap; + this.mapId = mapId; + } + + public override EnumMap GetMapType() + { + return enumMap; + } + + public override int GetMapId() + { + return mapId; } } diff --git a/MMOTestServer/MMOserver/Game/GameServer.cs b/MMOTestServer/MMOserver/Game/GameServer.cs index a2dff94..400c943 100644 --- a/MMOTestServer/MMOserver/Game/GameServer.cs +++ b/MMOTestServer/MMOserver/Game/GameServer.cs @@ -27,6 +27,8 @@ public class GameServer : ServerBase [(ushort)PacketCode.TRANSFORM_PLAYER] = OnTransformPlayer, [(ushort)PacketCode.ACTION_PLAYER] = OnActionPlayer, [(ushort)PacketCode.STATE_PLAYER] = OnStatePlayer, + [(ushort)PacketCode.CHANGE_MAP] = OnChangeMap, + [(ushort)PacketCode.PARTY_CHANGE_MAP] = OnPartyChangeMap, [(ushort)PacketCode.REQUEST_PARTY] = OnRequestParty, [(ushort)PacketCode.CHAT] = OnChat, }; @@ -78,7 +80,8 @@ public class GameServer : ServerBase { HashKey = hashKey, PlayerId = hashKey, - Nickname = hashKey.ToString() + Nickname = hashKey.ToString(), + CurrentMapId = 1 }; cm.AddUser(1, hashKey, newPlayer, peer); @@ -421,9 +424,11 @@ public class GameServer : ServerBase { HashKey = hashKey, PlayerId = hashKey, - Nickname = hashKey.ToString() + Nickname = hashKey.ToString(), + CurrentMapId = 1 }; + // 채널에 추가 cm.AddUser(packet.ChannelId, hashKey, newPlayer, peer); Log.Debug("[GameServer] INTO_CHANNEL HashKey={Key} ChannelId={ChannelId}", hashKey, packet.ChannelId); @@ -828,6 +833,84 @@ public class GameServer : ServerBase } } + private void OnChangeMap(NetPeer peer, int hashKey, byte[] payload) + { + ChangeMapPacket packet = Serializer.Deserialize(new ReadOnlyMemory(payload)); + + ChannelManager cm = ChannelManager.Instance; + int channelId = cm.HasUser(hashKey); + if (channelId < 0) + { + return; + } + + Channel.Channel channel = cm.GetChannel(channelId); + Player? player = channel.GetPlayer(hashKey); + if (player != null) + { + if (!channel.ChangeMap(hashKey, player, packet.MapId)) + { + // 맵이 없으면 무효 + Log.Warning("[GameServer] CHANGE_MAP 유효하지 않은 맵 HashKey={Key} MapId={MapId}", hashKey, packet.MapId); + return; + } + + // 이동 완료 알림 + byte[] data = PacketSerializer.Serialize((ushort)PacketCode.CHANGE_MAP, packet); + SendTo(peer, data); + Log.Debug("[GameServer] CHANGE_MAP HashKey={Key} MapId={MapId}", hashKey, packet.MapId); + } + } + + private void OnPartyChangeMap(NetPeer peer, int hashKey, byte[] payload) + { + PartyChangeMapPacket packet = Serializer.Deserialize(new ReadOnlyMemory(payload)); + + ChannelManager cm = ChannelManager.Instance; + int channelId = cm.HasUser(hashKey); + if (channelId < 0) + { + return; + } + + Channel.Channel channel = cm.GetChannel(channelId); + + // 맵 유효성 체크 + if (channel.GetMap(packet.MapId) == null) + { + Log.Warning("[GameServer] PARTY_CHANGE_MAP 유효하지 않은 맵 HashKey={Key} MapId={MapId}", hashKey, packet.MapId); + return; + } + + // 파티 확인 + PartyInfo? party = channel.GetPartyManager().GetParty(packet.PartyId); + if (party == null) + { + Log.Warning("[GameServer] PARTY_CHANGE_MAP 파티 없음 HashKey={Key} PartyId={PartyId}", hashKey, packet.PartyId); + return; + } + + // 파티원 전체 맵 이동 + 각자에게 알림 + byte[] data = PacketSerializer.Serialize((ushort)PacketCode.PARTY_CHANGE_MAP, packet); + foreach (int memberId in party.PartyMemberIds) + { + Player? memberPlayer = channel.GetPlayer(memberId); + if (memberPlayer == null) + { + continue; + } + + channel.ChangeMap(memberId, memberPlayer, packet.MapId); + + if (sessions.TryGetValue(memberId, out NetPeer? memberPeer)) + { + SendTo(memberPeer, data); + } + } + + Log.Debug("[GameServer] PARTY_CHANGE_MAP HashKey={Key} PartyId={PartyId} MapId={MapId}", hashKey, packet.PartyId, packet.MapId); + } + // 채널 퇴장/연결 해제 시 파티 자동 탈퇴 처리 private void HandlePartyLeaveOnExit(int channelId, int hashKey) { diff --git a/MMOTestServer/MMOserver/Game/Player.cs b/MMOTestServer/MMOserver/Game/Player.cs index 6ad8325..7008477 100644 --- a/MMOTestServer/MMOserver/Game/Player.cs +++ b/MMOTestServer/MMOserver/Game/Player.cs @@ -74,4 +74,11 @@ public class Player get; set; } + + // 현재 위치한 맵 ID + public int CurrentMapId + { + get; + set; + } } diff --git a/MMOTestServer/MMOserver/Packet/PacketBody.cs b/MMOTestServer/MMOserver/Packet/PacketBody.cs index 6903fad..61dd6ab 100644 --- a/MMOTestServer/MMOserver/Packet/PacketBody.cs +++ b/MMOTestServer/MMOserver/Packet/PacketBody.cs @@ -709,6 +709,41 @@ public class ChatPacket set; } } +// ============================================================ +// 맵 이동 +// ============================================================ + +// CHANGE_MAP (클라 -> 서버 & 서버 -> 클라) +[ProtoContract] +public class ChangeMapPacket +{ + [ProtoMember(1)] + public int MapId + { + get; + set; + } +} + +// PARTY_CHANGE_MAP (클라 -> 서버 & 서버 -> 클라) +[ProtoContract] +public class PartyChangeMapPacket +{ + [ProtoMember(1)] + public int MapId + { + get; + set; + } + + [ProtoMember(2)] + public int PartyId + { + get; + set; + } +} + // ============================================================ // 파티 diff --git a/MMOTestServer/MMOserver/Packet/PacketHeader.cs b/MMOTestServer/MMOserver/Packet/PacketHeader.cs index 09b703d..c452f52 100644 --- a/MMOTestServer/MMOserver/Packet/PacketHeader.cs +++ b/MMOTestServer/MMOserver/Packet/PacketHeader.cs @@ -30,6 +30,12 @@ public enum PacketCode : ushort // 채널 나가기 (클라 -> 서버) EXIT_CHANNEL, + // 맵 이동 + CHANGE_MAP, + + // 단체로 맵 이동 + PARTY_CHANGE_MAP, + // 플레이어 위치, 방향 (서버 -> 클라 \ 클라 -> 서버) TRANSFORM_PLAYER,