fix: 크로스 프로젝트 통신 버그 수정

- Tokens 타입 string? → Dictionary<string,string>? (Go API JSON object 역직렬화 실패 수정)
- 409 응답 핸들러 return false → return null (컴파일 에러 수정)
- INTO_BOSS_RAID 파티원 각자에게 본인 토큰과 함께 전달 (기존: 파티장에게 N번 중복)
- GetPlayer null 체크 추가 (NullReferenceException 방지)
- BossId 하드코딩 1 → packet.RaidId 사용
- Player 클래스에 Experience/AttackPower 등 전투 스탯 필드 추가
- ToPlayerInfo에서 새 필드 매핑 추가
- OnIntoChannelParty Nickname을 Session.UserName에서 가져오도록 수정

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-17 00:20:20 +09:00
parent 39ef81d48a
commit a5eedb2fb2
4 changed files with 68 additions and 11 deletions

View File

@@ -9,5 +9,5 @@ public sealed class BossRaidResult
public int BossId { get; init; } public int BossId { get; init; }
public List<string> Players { get; init; } = new(); public List<string> Players { get; init; } = new();
public string Status { get; init; } = string.Empty; public string Status { get; init; } = string.Empty;
public string? Tokens { get; init; } public Dictionary<string, string> Tokens { get; init; } = new();
} }

View File

@@ -88,13 +88,20 @@ public class RestApi : Singleton<RestApi>
return null; return null;
} }
// 400: 입장 조건 미충족 (레벨 부족, 이미 진행중 등) // 400: 입장 조건 미충족 (레벨 부족 등)
if (response.StatusCode == HttpStatusCode.BadRequest) if (response.StatusCode == HttpStatusCode.BadRequest)
{ {
Log.Warning("[RestApi] 보스 레이드 입장 거절 (400) BossId={BossId}", bossId); Log.Warning("[RestApi] 보스 레이드 입장 거절 (400) BossId={BossId}", bossId);
return null; return null;
} }
// 409: 이미 진행 중이거나 슬롯 충돌
if (response.StatusCode == HttpStatusCode.Conflict)
{
Log.Warning("[RestApi] 보스 레이드 충돌 (409) BossId={BossId} - 이미 진행 중이거나 슬롯 없음", bossId);
return null;
}
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
BossRaidAccessResponse? raw = await response.Content.ReadFromJsonAsync<BossRaidAccessResponse>(); BossRaidAccessResponse? raw = await response.Content.ReadFromJsonAsync<BossRaidAccessResponse>();
@@ -111,7 +118,7 @@ public class RestApi : Singleton<RestApi>
BossId = raw.BossId, BossId = raw.BossId,
Players = raw.Players, Players = raw.Players,
Status = raw.Status ?? string.Empty, Status = raw.Status ?? string.Empty,
Tokens = raw.Tokens Tokens = raw.Tokens ?? new()
}; };
} }
catch (Exception ex) when (attempt < MAX_RETRY) catch (Exception ex) when (attempt < MAX_RETRY)
@@ -166,7 +173,7 @@ public class RestApi : Singleton<RestApi>
} }
[JsonPropertyName("tokens")] [JsonPropertyName("tokens")]
public string? Tokens public Dictionary<string, string>? Tokens
{ {
get; get;
set; set;

View File

@@ -406,7 +406,12 @@ public class GameServer : ServerBase
Mp = player.Mp, Mp = player.Mp,
MaxMp = player.MaxMp, MaxMp = player.MaxMp,
Position = new Position { X = player.PosX, Y = player.PosY, Z = player.PosZ }, Position = new Position { X = player.PosX, Y = player.PosY, Z = player.PosZ },
RotY = player.RotY RotY = player.RotY,
Experience = player.Experience,
NextExp = player.NextExp,
AttackPower = player.AttackPower,
AttackRange = player.AttackRange,
SprintMultiplier = player.SprintMultiplier
}; };
} }
@@ -574,12 +579,17 @@ public class GameServer : ServerBase
if (memberPeer != null) if (memberPeer != null)
{ {
// 세션에서 username 조회
string nickname = memberPeer.Tag is Session s && !string.IsNullOrEmpty(s.UserName)
? s.UserName
: memberId.ToString();
// 새 채널에 유저 추가 // 새 채널에 유저 추가
Player newPlayer = new() Player newPlayer = new()
{ {
HashKey = memberId, HashKey = memberId,
PlayerId = memberId, PlayerId = memberId,
Nickname = memberId.ToString() Nickname = nickname
}; };
cm.AddUser(packet.ChannelId, memberId, newPlayer, memberPeer); cm.AddUser(packet.ChannelId, memberId, newPlayer, memberPeer);
@@ -1073,10 +1083,16 @@ public class GameServer : ServerBase
List<string> userNames = new List<string>(); List<string> userNames = new List<string>();
foreach (int memberId in party.PartyMemberIds) foreach (int memberId in party.PartyMemberIds)
{ {
userNames.Add(channel.GetPlayer(memberId).Nickname); Player? member = channel.GetPlayer(memberId);
if (member == null)
{
continue;
} }
BossRaidResult? result = await RestApi.Instance.BossRaidAccesssAsync(userNames, 1); userNames.Add(member.Nickname);
}
BossRaidResult? result = await RestApi.Instance.BossRaidAccesssAsync(userNames, packet.RaidId);
// 입장 실패 // 입장 실패
if (result == null || result.BossId <= 0) if (result == null || result.BossId <= 0)
@@ -1143,11 +1159,13 @@ public class GameServer : ServerBase
SendTo(memberPeer, PacketSerializer.Serialize((ushort)PacketCode.CHANGE_MAP, response)); SendTo(memberPeer, PacketSerializer.Serialize((ushort)PacketCode.CHANGE_MAP, response));
// 모두에게 레이드로 이동 (할당된 실제 레이드 맵 ID 전달) // 각 파티원에게 레이드 입장 정보 전달 (본인의 토큰 포함)
SendTo(peer, string? memberToken = null;
result.Tokens?.TryGetValue(memberPlayer.Nickname, out memberToken);
SendTo(memberPeer,
PacketSerializer.Serialize((ushort)PacketCode.INTO_BOSS_RAID, PacketSerializer.Serialize((ushort)PacketCode.INTO_BOSS_RAID,
new IntoBossRaidPacket new IntoBossRaidPacket
{ RaidId = assignedRaidMapId, IsSuccess = true, Session = result.SessionName, Token = result.Tokens })); { RaidId = assignedRaidMapId, IsSuccess = true, Session = result.SessionName, Token = memberToken }));
} }
Log.Debug("[GameServer] INTO_BOSS_RAID HashKey={Key} PartyId={PartyId} AssignedRaidMapId={RaidId}", hashKey, party.PartyId, Log.Debug("[GameServer] INTO_BOSS_RAID HashKey={Key} PartyId={PartyId} AssignedRaidMapId={RaidId}", hashKey, party.PartyId,

View File

@@ -75,6 +75,38 @@ public class Player
set; 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;
}
// 현재 위치한 맵 ID // 현재 위치한 맵 ID
public int CurrentMapId public int CurrentMapId
{ {