feat: 보스레이드 연동 — 입장 요청, 토큰 검증, 결과 보고 API 추가

- RestApi에 보스레이드 입장/검증/시작/완료/실패 엔드포인트 추가
- GameServer에 보스레이드 흐름 처리 로직
- Player 모델에 보스레이드 상태 필드 추가
- 보스레이드 관련 패킷 정의

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-16 17:51:33 +09:00
parent f6067047d9
commit 46dd92b27d
6 changed files with 364 additions and 2 deletions

View File

@@ -98,6 +98,7 @@ public class GameServer : ServerBase
{
AccTokenPacket accTokenPacket = Serializer.Deserialize<AccTokenPacket>(new ReadOnlyMemory<byte>(payload));
string token = accTokenPacket.Token;
string? verifiedUsername = null;
tokenHash.TryGetValue(token, out int hashKey);
if (hashKey <= 1000)
{
@@ -125,10 +126,15 @@ public class GameServer : ServerBase
}
Log.Information("[Server] 토큰 검증 성공 Username={Username} PeerId={Id}", username, peer.Id);
verifiedUsername = username;
}
peer.Tag = new Session(hashKey, peer);
((Session)peer.Tag).Token = token;
if (verifiedUsername != null)
{
((Session)peer.Tag).Username = verifiedUsername;
}
sessions[hashKey] = peer;
tokenHash[token] = hashKey;
pendingPeers.Remove(peer.Id);
@@ -370,6 +376,11 @@ public class GameServer : ServerBase
MaxMp = player.MaxMp,
Position = new Position { X = player.PosX, Y = player.PosY, Z = player.PosZ },
RotY = player.RotY,
Experience = player.Experience,
NextExp = player.NextExp,
AttackPower = player.AttackPower,
AttackRange = player.AttackRange,
SprintMultiplier = player.SprintMultiplier,
};
}
@@ -377,7 +388,7 @@ public class GameServer : ServerBase
// 패킷 핸들러
// ============================================================
private void OnIntoChannel(NetPeer peer, int hashKey, byte[] payload)
private async void OnIntoChannel(NetPeer peer, int hashKey, byte[] payload)
{
IntoChannelPacket packet = Serializer.Deserialize<IntoChannelPacket>(new ReadOnlyMemory<byte>(payload));
@@ -416,7 +427,7 @@ public class GameServer : ServerBase
return;
}
// TODO: 실제 서비스에서는 DB/세션에서 플레이어 정보 로드 필요
// API 서버에서 플레이어 프로필 로드
Player newPlayer = new Player
{
HashKey = hashKey,
@@ -424,6 +435,42 @@ public class GameServer : ServerBase
Nickname = hashKey.ToString()
};
Session? session = peer.Tag as Session;
string? username = session?.Username;
if (!string.IsNullOrEmpty(username))
{
try
{
RestApi.PlayerProfileResponse? profile = await RestApi.Instance.GetPlayerProfileAsync(username);
if (profile != null)
{
newPlayer.Nickname = string.IsNullOrEmpty(profile.Nickname) ? username : profile.Nickname;
newPlayer.Level = profile.Level;
newPlayer.MaxHp = (int)profile.MaxHp;
newPlayer.Hp = (int)profile.MaxHp;
newPlayer.MaxMp = (int)profile.MaxMp;
newPlayer.Mp = (int)profile.MaxMp;
newPlayer.Experience = profile.Experience;
newPlayer.NextExp = profile.NextExp;
newPlayer.AttackPower = (float)profile.AttackPower;
newPlayer.AttackRange = (float)profile.AttackRange;
newPlayer.SprintMultiplier = (float)profile.SprintMultiplier;
Log.Information("[GameServer] 프로필 로드 완료 Username={Username} Level={Level} MaxHp={MaxHp}",
username, profile.Level, profile.MaxHp);
}
else
{
newPlayer.Nickname = username;
Log.Warning("[GameServer] 프로필 로드 실패 — 기본값 사용 Username={Username}", username);
}
}
catch (Exception ex)
{
newPlayer.Nickname = username;
Log.Error(ex, "[GameServer] 프로필 로드 예외 Username={Username}", username);
}
}
cm.AddUser(packet.ChannelId, hashKey, newPlayer, peer);
Log.Debug("[GameServer] INTO_CHANNEL HashKey={Key} ChannelId={ChannelId}", hashKey, packet.ChannelId);

View File

@@ -50,6 +50,36 @@ public class Player
set;
}
public int Experience
{
get;
set;
}
public int NextExp
{
get;
set;
}
public float AttackPower
{
get;
set;
}
public float AttackRange
{
get;
set;
}
public float SprintMultiplier
{
get;
set;
}
// 위치/방향 (클라이언트 패킷과 동일하게 float)
public float PosX
{