feat : 파티 구조 뼈대 완성

This commit is contained in:
qornwh1
2026-03-08 21:48:34 +09:00
parent a5d3b48707
commit a53d838e24
5 changed files with 288 additions and 4 deletions

View File

@@ -540,7 +540,7 @@ public class RequestPartyPacket
} }
[ProtoMember(2)] [ProtoMember(2)]
public long PartyId public int PartyId
{ {
get; get;
set; set;
@@ -559,7 +559,7 @@ public class RequestPartyPacket
public class UpdatePartyPacket public class UpdatePartyPacket
{ {
[ProtoMember(1)] [ProtoMember(1)]
public long PartyId public int PartyId
{ {
get; get;
set; set;

View File

@@ -2,6 +2,7 @@ using LiteNetLib;
using LiteNetLib.Utils; using LiteNetLib.Utils;
using MMOserver.Api; using MMOserver.Api;
using MMOserver.Game.Channel; using MMOserver.Game.Channel;
using MMOserver.Game.Party;
using MMOserver.Packet; using MMOserver.Packet;
using MMOserver.Utils; using MMOserver.Utils;
using ProtoBuf; using ProtoBuf;
@@ -25,6 +26,7 @@ public class GameServer : ServerBase
[(ushort)PacketCode.TRANSFORM_PLAYER] = OnTransformPlayer, [(ushort)PacketCode.TRANSFORM_PLAYER] = OnTransformPlayer,
[(ushort)PacketCode.ACTION_PLAYER] = OnActionPlayer, [(ushort)PacketCode.ACTION_PLAYER] = OnActionPlayer,
[(ushort)PacketCode.STATE_PLAYER] = OnStatePlayer, [(ushort)PacketCode.STATE_PLAYER] = OnStatePlayer,
[(ushort)PacketCode.REQUEST_PARTY] = OnRequestParty,
}; };
userUuidGenerator = new UuidGenerator(); userUuidGenerator = new UuidGenerator();
} }
@@ -462,4 +464,99 @@ public class GameServer : ServerBase
byte[] data = PacketSerializer.Serialize<StatePlayerPacket>((ushort)PacketCode.STATE_PLAYER, packet); byte[] data = PacketSerializer.Serialize<StatePlayerPacket>((ushort)PacketCode.STATE_PLAYER, packet);
BroadcastToChannel(channelId, data, peer); BroadcastToChannel(channelId, data, peer);
} }
private void OnRequestParty(NetPeer peer, int hashKey, byte[] payload)
{
RequestPartyPacket req = Serializer.Deserialize<RequestPartyPacket>(new ReadOnlyMemory<byte>(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<UpdatePartyPacket>((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<UpdatePartyPacket>((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<UpdatePartyPacket>((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<UpdatePartyPacket>((ushort)PacketCode.UPDATE_PARTY, notify);
BroadcastToUsers(party.PartyMemberIds, data); // 전원 (리더 포함)
break;
}
}
}
private void BroadcastToUsers(IEnumerable<int> userIds, byte[] data, DeliveryMethod method = DeliveryMethod.ReliableOrdered)
{
foreach (int userId in userIds)
{
if (sessions.TryGetValue(userId, out NetPeer? targetPeer))
{
SendTo(targetPeer, data, method);
}
}
}
} }

View File

@@ -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<int> PartyMemberIds
{
get;
set;
} = new List<int>();
public string PartyName
{
get;
set;
} = "";
public int GetPartyMemberCount()
{
return PartyMemberIds.Count;
}
}

View File

@@ -0,0 +1,152 @@
using MMOserver.Utils;
namespace MMOserver.Game.Party;
public class PartyManager : Singleton<PartyManager>
{
private readonly UuidGenerator partyUuidGenerator = new UuidGenerator();
// partyId → PartyInfo
private readonly Dictionary<int, PartyInfo> parties = new();
// playerId → partyId (한 플레이어는 하나의 파티만)
private readonly Dictionary<int, int> 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);
}
}

View File

@@ -540,7 +540,7 @@ public class RequestPartyPacket
} }
[ProtoMember(2)] [ProtoMember(2)]
public long PartyId public int PartyId
{ {
get; get;
set; set;
@@ -559,7 +559,7 @@ public class RequestPartyPacket
public class UpdatePartyPacket public class UpdatePartyPacket
{ {
[ProtoMember(1)] [ProtoMember(1)]
public long PartyId public int PartyId
{ {
get; get;
set; set;