diff --git a/ClientTester/EchoClientTester/Packet/PacketBody.cs b/ClientTester/EchoClientTester/Packet/PacketBody.cs index 65b5bd6..3b0a056 100644 --- a/ClientTester/EchoClientTester/Packet/PacketBody.cs +++ b/ClientTester/EchoClientTester/Packet/PacketBody.cs @@ -234,6 +234,13 @@ public class PartyInfoData get; set; } = new List(); + + [ProtoMember(4)] + public string PartyName + { + get; + set; + } } // INTO_CHANNEL 클라->서버: 입장할 채널 ID / 서버->클라: 채널 내 나 이외 플레이어 목록 diff --git a/MMOTestServer/MMOserver/Game/GameServer.cs b/MMOTestServer/MMOserver/Game/GameServer.cs index 902a546..728201b 100644 --- a/MMOTestServer/MMOserver/Game/GameServer.cs +++ b/MMOTestServer/MMOserver/Game/GameServer.cs @@ -169,9 +169,12 @@ public class GameServer : ServerBase { Log.Information("[GameServer] 세션 해제 HashKey={Key} Reason={Reason}", hashKey, info.Reason); - // 같은 채널 유저들에게 나갔다고 알림 if (channelId >= 0 && player != null) { + // 파티 자동 탈퇴 + HandlePartyLeaveOnExit(channelId, hashKey); + + // 같은 채널 유저들에게 나갔다고 알림 SendExitChannelPacket(peer, hashKey, channelId, player); } } @@ -274,6 +277,7 @@ public class GameServer : ServerBase PartyId = party.PartyId, LeaderId = party.LeaderId, MemberPlayerIds = new List(party.PartyMemberIds), + PartyName = party.PartyName }); } @@ -304,7 +308,8 @@ public class GameServer : ServerBase if (player == null) { Log.Warning("[GameServer] LOAD_GAME 플레이어 없음 HashKey={Key}", hashKey); - byte[] denied = PacketSerializer.Serialize((ushort)PacketCode.LOAD_GAME, new LoadGamePacket { IsAccepted = false }); + byte[] denied = + PacketSerializer.Serialize((ushort)PacketCode.LOAD_GAME, new LoadGamePacket { IsAccepted = false }); SendTo(peer, denied); return; } @@ -327,7 +332,8 @@ public class GameServer : ServerBase // 특정 채널의 모든 유저에게 전송 (exclude 지정 시 해당 피어 제외) // Channel이 NetPeer를 직접 보유하므로 sessions 교차 조회 없음 - private void BroadcastToChannel(int channelId, byte[] data, NetPeer? exclude = null, DeliveryMethod method = DeliveryMethod.ReliableOrdered) + private void BroadcastToChannel(int channelId, byte[] data, NetPeer? exclude = null, + DeliveryMethod method = DeliveryMethod.ReliableOrdered) { Channel.Channel channel = ChannelManager.Instance.GetChannel(channelId); foreach (NetPeer targetPeer in channel.GetConnectPeers()) @@ -377,7 +383,8 @@ public class GameServer : ServerBase { Log.Warning("[GameServer] INTO_CHANNEL 채널 인원 초과 HashKey={Key} ChannelId={ChannelId} UserCount={Count}/{Max}", hashKey, packet.ChannelId, channel.UserCount, channel.UserCountMax); - byte[] full = PacketSerializer.Serialize((ushort)PacketCode.INTO_CHANNEL, new IntoChannelPacket { ChannelId = -1 }); + byte[] full = PacketSerializer.Serialize((ushort)PacketCode.INTO_CHANNEL, + new IntoChannelPacket { ChannelId = -1 }); SendTo(peer, full); return; } @@ -410,6 +417,12 @@ public class GameServer : ServerBase int channelId = cm.HasUser(hashKey); Player? player = channelId >= 0 ? cm.GetChannel(channelId).GetPlayer(hashKey) : null; + // 파티 자동 탈퇴 + if (channelId >= 0) + { + HandlePartyLeaveOnExit(channelId, hashKey); + } + cm.RemoveUser(hashKey); Log.Debug("[GameServer] EXIT_CHANNEL HashKey={Key} PlayerId={PlayerId}", hashKey, packet.PlayerId); @@ -520,7 +533,7 @@ public class GameServer : ServerBase PartyName = party.PartyName, }; byte[] data = PacketSerializer.Serialize((ushort)PacketCode.UPDATE_PARTY, notify); - SendTo(peer, data); + BroadcastToChannel(channelId, data); // 채널 전체 파티 목록 갱신 break; } case PartyUpdateType.JOIN: @@ -539,7 +552,7 @@ public class GameServer : ServerBase PlayerId = hashKey, }; byte[] data = PacketSerializer.Serialize((ushort)PacketCode.UPDATE_PARTY, notify); - BroadcastToUsers(party.PartyMemberIds, data); // 새 멤버 포함 전원 + BroadcastToChannel(channelId, data); // 채널 전체 파티 목록 갱신 break; } case PartyUpdateType.LEAVE: @@ -552,18 +565,17 @@ public class GameServer : ServerBase UpdatePartyPacket notify = new UpdatePartyPacket { - PartyId = req.PartyId, - Type = PartyUpdateType.LEAVE, - LeaderId = party?.LeaderId ?? 0, - PlayerId = hashKey, + PartyId = party.PartyId, Type = PartyUpdateType.DELETE, LeaderId = party?.LeaderId ?? 0, PlayerId = hashKey, }; - byte[] data = PacketSerializer.Serialize((ushort)PacketCode.UPDATE_PARTY, notify); - if (party != null) + + // 파티가 남아있으면 살린다. + if (party.PartyMemberIds.Count > 0) { - BroadcastToUsers(party.PartyMemberIds, data); // 남은 멤버들에게 + notify.Type = PartyUpdateType.LEAVE; } - SendTo(peer, data); // 탈퇴자 본인에게 + byte[] data = PacketSerializer.Serialize((ushort)PacketCode.UPDATE_PARTY, notify); + BroadcastToChannel(channelId, data); // 채널 전체 파티 목록 갱신 (탈퇴자 포함) break; } case PartyUpdateType.DELETE: @@ -581,12 +593,12 @@ public class GameServer : ServerBase LeaderId = party.LeaderId, }; byte[] data = PacketSerializer.Serialize((ushort)PacketCode.UPDATE_PARTY, notify); - BroadcastToUsers(party.PartyMemberIds, data); // 전원 (리더 포함) + BroadcastToChannel(channelId, data); // 채널 전체 파티 목록 갱신 break; } case PartyUpdateType.UPDATE: { - if (!pm.UpdateParty(hashKey, req.PartyId, out PartyInfo? party)) + if (!pm.UpdateParty(hashKey, req.PartyId, req.PartyName, out PartyInfo? party)) { SendError(peer, ErrorCode.PARTY_UPDATE_FAILED); return; @@ -595,15 +607,13 @@ public class GameServer : ServerBase UpdatePartyPacket notify = new UpdatePartyPacket { PartyId = req.PartyId, - Type = PartyUpdateType.LEAVE, + Type = PartyUpdateType.UPDATE, LeaderId = party?.LeaderId ?? 0, PlayerId = hashKey, + PartyName = req.PartyName, }; byte[] data = PacketSerializer.Serialize((ushort)PacketCode.UPDATE_PARTY, notify); - if (party != null) - { - BroadcastToUsers(party.PartyMemberIds, data); // 남은 멤버들에게 - } + BroadcastToChannel(channelId, data); // 채널 전체 파티 목록 갱신 break; } } @@ -678,6 +688,40 @@ public class GameServer : ServerBase } } + // 채널 퇴장/연결 해제 시 파티 자동 탈퇴 처리 + private void HandlePartyLeaveOnExit(int channelId, int hashKey) + { + PartyManager pm = ChannelManager.Instance.GetChannel(channelId).GetPartyManager(); + + int? partyId = pm.GetPartyByPlayer(hashKey)?.PartyId; + if (partyId == null) + { + return; // 파티에 없으면 무시 + } + + pm.LeaveParty(hashKey, out PartyInfo? remaining); + + // 0명 → DELETE, 남은 멤버 있음 → LEAVE + UpdatePartyPacket notify = remaining != null + ? new UpdatePartyPacket + { + PartyId = partyId.Value, + Type = PartyUpdateType.LEAVE, + LeaderId = remaining.LeaderId, + PlayerId = hashKey, + } + : new UpdatePartyPacket + { + PartyId = partyId.Value, + Type = PartyUpdateType.DELETE, + LeaderId = -1, + PlayerId = hashKey, + }; + + byte[] data = PacketSerializer.Serialize((ushort)PacketCode.UPDATE_PARTY, notify); + BroadcastToChannel(channelId, data); + } + private void SendError(NetPeer peer, ErrorCode code) { ErrorPacket err = new ErrorPacket { Code = code }; diff --git a/MMOTestServer/MMOserver/Game/Party/PartyManager.cs b/MMOTestServer/MMOserver/Game/Party/PartyManager.cs index 430a59b..d094eb1 100644 --- a/MMOTestServer/MMOserver/Game/Party/PartyManager.cs +++ b/MMOTestServer/MMOserver/Game/Party/PartyManager.cs @@ -86,7 +86,8 @@ public class PartyManager if (party.PartyMemberIds.Count == 0) { DeletePartyInternal(partyId, party); - party = null; + // id가 필요하다 그래서 참조해 둔다. + // party = null; return true; } @@ -120,7 +121,7 @@ public class PartyManager } // 파티 업데이트 - public bool UpdateParty(int leaderId, int partyId, out PartyInfo? party) + public bool UpdateParty(int leaderId, int partyId, string newPartyName, out PartyInfo? party) { party = null; @@ -136,7 +137,7 @@ public class PartyManager } // 파티 이름 변경 - party.PartyName = party.PartyName; + party.PartyName = newPartyName; return true; } diff --git a/MMOTestServer/MMOserver/Packet/PacketBody.cs b/MMOTestServer/MMOserver/Packet/PacketBody.cs index 76f28a2..76b52f9 100644 --- a/MMOTestServer/MMOserver/Packet/PacketBody.cs +++ b/MMOTestServer/MMOserver/Packet/PacketBody.cs @@ -234,6 +234,13 @@ public class PartyInfoData get; set; } = new List(); + + [ProtoMember(4)] + public string PartyName + { + get; + set; + } } // INTO_CHANNEL 클라->서버: 입장할 채널 ID / 서버->클라: 채널 내 나 이외 플레이어 목록