Fix: 플레이어 프로필 DB 연동, 파티 초대/추방 프로토콜 구현
- 채널 입장 시 API 서버에서 플레이어 프로필 로드 (레벨/스탯/위치) - 채널 퇴장 시 위치/플레이타임 DB 저장 (SaveGameDataAsync) - Player.cs에 AttackPower/AttackRange/SprintMultiplier/Experience 필드 추가 - ToPlayerInfo에서 전투 스탯 매핑 추가 - Session에 ChannelJoinedAt 추가 (플레이타임 계산용) - PartyUpdateType에 INVITE/KICK 추가 - RequestPartyPacket에 TargetPlayerId 필드 추가 - GameServer에 INVITE/KICK 핸들러 구현 - Channel에 GetPeer() 메서드 추가 - RestApi에 GetPlayerProfileAsync/SaveGameDataAsync 추가 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -207,6 +207,21 @@ public class GameServer : ServerBase
|
||||
int channelId = cm.HasUser(hashKey);
|
||||
Player? player = channelId >= 0 ? cm.GetChannel(channelId).GetPlayer(hashKey) : null;
|
||||
|
||||
// 퇴장 시 위치/플레이타임 DB 저장 (fire-and-forget)
|
||||
if (player != null && peer.Tag is Session session && session.Username != null)
|
||||
{
|
||||
long playTimeDelta = 0;
|
||||
if (session.ChannelJoinedAt != default)
|
||||
{
|
||||
playTimeDelta = (long)(DateTime.UtcNow - session.ChannelJoinedAt).TotalSeconds;
|
||||
}
|
||||
_ = RestApi.Instance.SaveGameDataAsync(
|
||||
session.Username,
|
||||
player.PosX, player.PosY, player.PosZ, player.RotY,
|
||||
playTimeDelta > 0 ? playTimeDelta : null
|
||||
);
|
||||
}
|
||||
|
||||
if (cm.RemoveUser(hashKey))
|
||||
{
|
||||
Log.Information("[GameServer] 세션 해제 HashKey={Key} Reason={Reason}", hashKey, info.Reason);
|
||||
@@ -524,6 +539,10 @@ public 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.RotY = (float)profile.LastRotY;
|
||||
Log.Information("[GameServer] 프로필 로드 완료 Username={Username} Level={Level} MaxHp={MaxHp}",
|
||||
username, profile.Level, profile.MaxHp);
|
||||
}
|
||||
@@ -540,6 +559,10 @@ public class GameServer : ServerBase
|
||||
}
|
||||
}
|
||||
|
||||
// 채널 입장 시각 기록 (플레이타임 계산용)
|
||||
((Session)peer.Tag).ChannelJoinedAt = DateTime.UtcNow;
|
||||
|
||||
// 채널에 추가
|
||||
cm.AddUser(packet.ChannelId, hashKey, newPlayer, peer);
|
||||
Log.Debug("[GameServer] INTO_CHANNEL HashKey={Key} ChannelId={ChannelId}", hashKey, packet.ChannelId);
|
||||
|
||||
@@ -921,6 +944,84 @@ public class GameServer : ServerBase
|
||||
BroadcastToChannel(channelId, data); // 채널 전체 파티 목록 갱신
|
||||
break;
|
||||
}
|
||||
case PartyUpdateType.INVITE:
|
||||
{
|
||||
// 리더만 초대 가능
|
||||
PartyInfo? myParty = pm.GetPartyByPlayer(hashKey);
|
||||
if (myParty == null || myParty.LeaderId != hashKey)
|
||||
{
|
||||
SendError(peer, ErrorCode.PARTY_JOIN_FAILED);
|
||||
return;
|
||||
}
|
||||
|
||||
if (myParty.GetPartyMemberCount() >= PartyInfo.partyMemberMax)
|
||||
{
|
||||
SendError(peer, ErrorCode.PARTY_JOIN_FAILED);
|
||||
return;
|
||||
}
|
||||
|
||||
// 대상 플레이어가 같은 채널에 있는지 확인
|
||||
int targetId = req.TargetPlayerId;
|
||||
Channel.Channel? ch = cm.GetChannel(channelId);
|
||||
if (ch == null || ch.GetPlayer(targetId) == null)
|
||||
{
|
||||
SendError(peer, ErrorCode.PARTY_JOIN_FAILED);
|
||||
return;
|
||||
}
|
||||
|
||||
// 대상에게 초대 알림 전송
|
||||
NetPeer? targetPeer = ch.GetPeer(targetId);
|
||||
if (targetPeer == null)
|
||||
{
|
||||
SendError(peer, ErrorCode.PARTY_JOIN_FAILED);
|
||||
return;
|
||||
}
|
||||
|
||||
UpdatePartyPacket inviteNotify = new()
|
||||
{
|
||||
PartyId = myParty.PartyId,
|
||||
Type = PartyUpdateType.INVITE,
|
||||
LeaderId = hashKey,
|
||||
PlayerId = targetId,
|
||||
PartyName = myParty.PartyName
|
||||
};
|
||||
byte[] inviteData = PacketSerializer.Serialize((ushort)PacketCode.UPDATE_PARTY, inviteNotify);
|
||||
SendTo(targetPeer, inviteData);
|
||||
break;
|
||||
}
|
||||
case PartyUpdateType.KICK:
|
||||
{
|
||||
// 리더만 추방 가능
|
||||
PartyInfo? myParty2 = pm.GetPartyByPlayer(hashKey);
|
||||
if (myParty2 == null || myParty2.LeaderId != hashKey)
|
||||
{
|
||||
SendError(peer, ErrorCode.PARTY_DELETE_FAILED);
|
||||
return;
|
||||
}
|
||||
|
||||
int kickTarget = req.TargetPlayerId;
|
||||
if (kickTarget == hashKey)
|
||||
{
|
||||
return; // 자기 자신은 추방 불가
|
||||
}
|
||||
|
||||
if (!pm.LeaveParty(kickTarget, out PartyInfo? kickedParty) || kickedParty == null)
|
||||
{
|
||||
SendError(peer, ErrorCode.PARTY_NOT_IN_PARTY);
|
||||
return;
|
||||
}
|
||||
|
||||
UpdatePartyPacket kickNotify = new()
|
||||
{
|
||||
PartyId = kickedParty.PartyId,
|
||||
Type = PartyUpdateType.KICK,
|
||||
LeaderId = kickedParty.LeaderId,
|
||||
PlayerId = kickTarget
|
||||
};
|
||||
byte[] kickData = PacketSerializer.Serialize((ushort)PacketCode.UPDATE_PARTY, kickNotify);
|
||||
BroadcastToChannel(channelId, kickData);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user