diff --git a/ClientTester/EchoClientTester/Packet/PacketBody.cs b/ClientTester/EchoClientTester/Packet/PacketBody.cs index 4c133df..ecae837 100644 --- a/ClientTester/EchoClientTester/Packet/PacketBody.cs +++ b/ClientTester/EchoClientTester/Packet/PacketBody.cs @@ -540,7 +540,7 @@ public class RequestPartyPacket } [ProtoMember(2)] - public long PartyId + public int PartyId { get; set; @@ -559,7 +559,7 @@ public class RequestPartyPacket public class UpdatePartyPacket { [ProtoMember(1)] - public long PartyId + public int PartyId { get; set; diff --git a/MMOTestServer/MMOserver/Game/GameServer.cs b/MMOTestServer/MMOserver/Game/GameServer.cs index bd30085..95b769b 100644 --- a/MMOTestServer/MMOserver/Game/GameServer.cs +++ b/MMOTestServer/MMOserver/Game/GameServer.cs @@ -2,6 +2,7 @@ using LiteNetLib; using LiteNetLib.Utils; using MMOserver.Api; using MMOserver.Game.Channel; +using MMOserver.Game.Party; using MMOserver.Packet; using MMOserver.Utils; using ProtoBuf; @@ -25,6 +26,7 @@ public class GameServer : ServerBase [(ushort)PacketCode.TRANSFORM_PLAYER] = OnTransformPlayer, [(ushort)PacketCode.ACTION_PLAYER] = OnActionPlayer, [(ushort)PacketCode.STATE_PLAYER] = OnStatePlayer, + [(ushort)PacketCode.REQUEST_PARTY] = OnRequestParty, }; userUuidGenerator = new UuidGenerator(); } @@ -462,4 +464,99 @@ public class GameServer : ServerBase byte[] data = PacketSerializer.Serialize((ushort)PacketCode.STATE_PLAYER, packet); BroadcastToChannel(channelId, data, peer); } + + private void OnRequestParty(NetPeer peer, int hashKey, byte[] payload) + { + RequestPartyPacket req = Serializer.Deserialize(new ReadOnlyMemory(payload)); + PartyManager pm = PartyManager.Instance; + + switch (req.Type) + { + case PartyUpdateType.CREATE: + { + if (!pm.CreateParty(hashKey, req.PartyName, out PartyInfo? party)) + { + return; + } + + UpdatePartyPacket notify = new UpdatePartyPacket + { + PartyId = party!.PartyId, + Type = PartyUpdateType.CREATE, + LeaderId = party.LeaderId, + PlayerId = hashKey, + PartyName = party.PartyName, + }; + byte[] data = PacketSerializer.Serialize((ushort)PacketCode.UPDATE_PARTY, notify); + SendTo(peer, data); + break; + } + case PartyUpdateType.JOIN: + { + if (!pm.JoinParty(hashKey, req.PartyId, out PartyInfo? party)) + { + return; + } + + UpdatePartyPacket notify = new UpdatePartyPacket + { + PartyId = party!.PartyId, + Type = PartyUpdateType.JOIN, + LeaderId = party.LeaderId, + PlayerId = hashKey, + }; + byte[] data = PacketSerializer.Serialize((ushort)PacketCode.UPDATE_PARTY, notify); + BroadcastToUsers(party.PartyMemberIds, data); // 새 멤버 포함 전원 + break; + } + case PartyUpdateType.LEAVE: + { + if (!pm.LeaveParty(hashKey, out PartyInfo? party)) + { + return; + } + + UpdatePartyPacket notify = new UpdatePartyPacket + { + PartyId = req.PartyId, + Type = PartyUpdateType.LEAVE, + LeaderId = party?.LeaderId ?? 0, + PlayerId = hashKey, + }; + byte[] data = PacketSerializer.Serialize((ushort)PacketCode.UPDATE_PARTY, notify); + if (party != null) + BroadcastToUsers(party.PartyMemberIds, data); // 남은 멤버들에게 + SendTo(peer, data); // 탈퇴자 본인에게 + break; + } + case PartyUpdateType.DELETE: + { + if (!pm.DeleteParty(hashKey, req.PartyId, out PartyInfo? party)) + { + return; + } + + UpdatePartyPacket notify = new UpdatePartyPacket + { + PartyId = party!.PartyId, + Type = PartyUpdateType.DELETE, + LeaderId = party.LeaderId, + }; + byte[] data = PacketSerializer.Serialize((ushort)PacketCode.UPDATE_PARTY, notify); + BroadcastToUsers(party.PartyMemberIds, data); // 전원 (리더 포함) + break; + } + } + } + + private void BroadcastToUsers(IEnumerable userIds, byte[] data, DeliveryMethod method = DeliveryMethod.ReliableOrdered) + { + foreach (int userId in userIds) + { + if (sessions.TryGetValue(userId, out NetPeer? targetPeer)) + { + SendTo(targetPeer, data, method); + } + } + } } diff --git a/MMOTestServer/MMOserver/Game/Party/Party.cs b/MMOTestServer/MMOserver/Game/Party/Party.cs new file mode 100644 index 0000000..8c96a15 --- /dev/null +++ b/MMOTestServer/MMOserver/Game/Party/Party.cs @@ -0,0 +1,35 @@ +namespace MMOserver.Game.Party; + +public class PartyInfo +{ + public static readonly int partyMemberMax = 3; + + public int PartyId + { + get; + set; + } + + public int LeaderId + { + get; + set; + } + + public List PartyMemberIds + { + get; + set; + } = new List(); + + public string PartyName + { + get; + set; + } = ""; + + public int GetPartyMemberCount() + { + return PartyMemberIds.Count; + } +} diff --git a/MMOTestServer/MMOserver/Game/Party/PartyManager.cs b/MMOTestServer/MMOserver/Game/Party/PartyManager.cs new file mode 100644 index 0000000..41f5b7e --- /dev/null +++ b/MMOTestServer/MMOserver/Game/Party/PartyManager.cs @@ -0,0 +1,152 @@ +using MMOserver.Utils; + +namespace MMOserver.Game.Party; + +public class PartyManager : Singleton +{ + private readonly UuidGenerator partyUuidGenerator = new UuidGenerator(); + + // partyId → PartyInfo + private readonly Dictionary parties = new(); + + // playerId → partyId (한 플레이어는 하나의 파티만) + private readonly Dictionary playerPartyMap = new(); + + // 파티 생성 + public bool CreateParty(int leaderId, string partyName, out PartyInfo? party) + { + party = null; + + if (playerPartyMap.ContainsKey(leaderId)) + { + return false; // 이미 파티에 속해있음 + } + + int partyId = partyUuidGenerator.Create(); + party = new PartyInfo + { + PartyId = partyId, + LeaderId = leaderId, + PartyName = partyName, + }; + party.PartyMemberIds.Add(leaderId); + + parties[partyId] = party; + playerPartyMap[leaderId] = partyId; + + return true; + } + + // 파티 참가 + public bool JoinParty(int playerId, int partyId, out PartyInfo? party) + { + party = null; + + if (playerPartyMap.ContainsKey(playerId)) + { + return false; // 이미 파티에 속해있음 + } + + if (!parties.TryGetValue(partyId, out party)) + { + return false; // 파티 없음 + } + + if (party.GetPartyMemberCount() >= PartyInfo.partyMemberMax) + { + party = null; + return false; // 파티 인원 초과 + } + + party.PartyMemberIds.Add(playerId); + playerPartyMap[playerId] = partyId; + + return true; + } + + // 파티 탈퇴 + public bool LeaveParty(int playerId, out PartyInfo? party) + { + party = null; + + if (!playerPartyMap.TryGetValue(playerId, out int partyId)) + { + return false; // 파티에 속해있지 않음 + } + + if (!parties.TryGetValue(partyId, out party)) + { + return false; + } + + party.PartyMemberIds.Remove(playerId); + playerPartyMap.Remove(playerId); + + // 마지막 멤버가 나가면 파티 해산 + if (party.PartyMemberIds.Count == 0) + { + DeletePartyInternal(partyId, party); + party = null; + return true; + } + + // 리더가 나갔으면 남은 멤버 중 첫 번째를 리더로 승계 + if (party.LeaderId == playerId) + { + party.LeaderId = party.PartyMemberIds[0]; + } + + return true; + } + + // 파티 해산 (리더만) + public bool DeleteParty(int leaderId, int partyId, out PartyInfo? party) + { + party = null; + + if (!parties.TryGetValue(partyId, out party)) + { + return false; + } + + if (party.LeaderId != leaderId) + { + party = null; + return false; // 리더만 해산 가능 + } + + DeletePartyInternal(partyId, party); + return true; + } + + // 조회 + public PartyInfo? GetParty(int partyId) + { + parties.TryGetValue(partyId, out PartyInfo? party); + return party; + } + + public PartyInfo? GetPartyByPlayer(int playerId) + { + if (!playerPartyMap.TryGetValue(playerId, out int partyId)) + { + return null; + } + + parties.TryGetValue(partyId, out PartyInfo? party); + return party; + } + + // 삭제 + private void DeletePartyInternal(int partyId, PartyInfo party) + { + // 남아 있는인원도 제거 + foreach (int memberId in party.PartyMemberIds) + { + playerPartyMap.Remove(memberId); + } + + parties.Remove(partyId); + partyUuidGenerator.Release(partyId); + } +} diff --git a/MMOTestServer/MMOserver/Packet/PacketBody.cs b/MMOTestServer/MMOserver/Packet/PacketBody.cs index 17bf30e..ee3a61d 100644 --- a/MMOTestServer/MMOserver/Packet/PacketBody.cs +++ b/MMOTestServer/MMOserver/Packet/PacketBody.cs @@ -540,7 +540,7 @@ public class RequestPartyPacket } [ProtoMember(2)] - public long PartyId + public int PartyId { get; set; @@ -559,7 +559,7 @@ public class RequestPartyPacket public class UpdatePartyPacket { [ProtoMember(1)] - public long PartyId + public int PartyId { get; set;