Compare commits
3 Commits
ca5a345c8f
...
2ebd0120ab
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ebd0120ab | ||
|
|
849105f8ff | ||
|
|
0617e6a194 |
@@ -29,7 +29,7 @@ public class DummyClients
|
|||||||
private float dirZ = 0f;
|
private float dirZ = 0f;
|
||||||
|
|
||||||
// 맵 경계
|
// 맵 경계
|
||||||
public MapBounds Map { get; set; } = new MapBounds(-50f, 50f, -50f, 50f);
|
public MapBounds Map { get; set; } = new MapBounds(-95f, -25f, -30f, 10f);
|
||||||
|
|
||||||
// 통계
|
// 통계
|
||||||
public int SentCount
|
public int SentCount
|
||||||
@@ -86,6 +86,9 @@ public class DummyClients
|
|||||||
|
|
||||||
manager.Start();
|
manager.Start();
|
||||||
manager.Connect(ip, port, key);
|
manager.Connect(ip, port, key);
|
||||||
|
|
||||||
|
position.X = -60;
|
||||||
|
position.Z = 25;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateDummy()
|
public void UpdateDummy()
|
||||||
@@ -144,7 +147,7 @@ public class DummyClients
|
|||||||
Position = new Packet.Position
|
Position = new Packet.Position
|
||||||
{
|
{
|
||||||
X = position.X,
|
X = position.X,
|
||||||
Y = -11.09f, // 높이는 버린다.
|
Y = -3.7f, // 높이는 버린다.
|
||||||
Z = position.Z
|
Z = position.Z
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,10 +9,9 @@ namespace MMOserver.Api;
|
|||||||
|
|
||||||
public class RestApi : Singleton<RestApi>
|
public class RestApi : Singleton<RestApi>
|
||||||
{
|
{
|
||||||
private readonly HttpClient httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(5) };
|
|
||||||
|
|
||||||
private const int MAX_RETRY = 3;
|
private const int MAX_RETRY = 3;
|
||||||
private static readonly TimeSpan RETRY_DELAY = TimeSpan.FromSeconds(1);
|
private static readonly TimeSpan RETRY_DELAY = TimeSpan.FromSeconds(1);
|
||||||
|
private readonly HttpClient httpClient = new() { Timeout = TimeSpan.FromSeconds(5) };
|
||||||
|
|
||||||
public RestApi()
|
public RestApi()
|
||||||
{
|
{
|
||||||
@@ -91,58 +90,6 @@ public class RestApi : Singleton<RestApi>
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class AuthVerifyResponse
|
|
||||||
{
|
|
||||||
[JsonPropertyName("username")]
|
|
||||||
public string? Username
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class PlayerProfileResponse
|
|
||||||
{
|
|
||||||
[JsonPropertyName("nickname")]
|
|
||||||
public string Nickname { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
[JsonPropertyName("level")]
|
|
||||||
public int Level { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("experience")]
|
|
||||||
public int Experience { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("nextExp")]
|
|
||||||
public int NextExp { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("maxHp")]
|
|
||||||
public double MaxHp { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("maxMp")]
|
|
||||||
public double MaxMp { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("attackPower")]
|
|
||||||
public double AttackPower { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("attackRange")]
|
|
||||||
public double AttackRange { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("sprintMultiplier")]
|
|
||||||
public double SprintMultiplier { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("lastPosX")]
|
|
||||||
public double LastPosX { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("lastPosY")]
|
|
||||||
public double LastPosY { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("lastPosZ")]
|
|
||||||
public double LastPosZ { get; set; }
|
|
||||||
|
|
||||||
[JsonPropertyName("lastRotY")]
|
|
||||||
public double LastRotY { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
// 레이드 채널 접속 여부 체크
|
// 레이드 채널 접속 여부 체크
|
||||||
// 성공 시 BossRaidResult 반환, 실패/거절 시 null 반환
|
// 성공 시 BossRaidResult 반환, 실패/거절 시 null 반환
|
||||||
public async Task<BossRaidResult?> BossRaidAccessAsync(List<string> userNames, int bossId)
|
public async Task<BossRaidResult?> BossRaidAccessAsync(List<string> userNames, int bossId)
|
||||||
@@ -153,7 +100,7 @@ public class RestApi : Singleton<RestApi>
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
HttpResponseMessage response = await httpClient.PostAsJsonAsync(url, new { usernames = userNames, bossId = bossId });
|
HttpResponseMessage response = await httpClient.PostAsJsonAsync(url, new { usernames = userNames, bossId });
|
||||||
|
|
||||||
// 401: API 키 인증 실패
|
// 401: API 키 인증 실패
|
||||||
if (response.StatusCode == HttpStatusCode.Unauthorized)
|
if (response.StatusCode == HttpStatusCode.Unauthorized)
|
||||||
@@ -210,12 +157,31 @@ public class RestApi : Singleton<RestApi>
|
|||||||
{
|
{
|
||||||
string url = AppConfig.RestApi.BaseUrl + "/api/internal/player/save?username=" + Uri.EscapeDataString(username);
|
string url = AppConfig.RestApi.BaseUrl + "/api/internal/player/save?username=" + Uri.EscapeDataString(username);
|
||||||
|
|
||||||
var body = new Dictionary<string, object?>();
|
Dictionary<string, object?> body = new();
|
||||||
if (posX.HasValue) body["lastPosX"] = posX.Value;
|
if (posX.HasValue)
|
||||||
if (posY.HasValue) body["lastPosY"] = posY.Value;
|
{
|
||||||
if (posZ.HasValue) body["lastPosZ"] = posZ.Value;
|
body["lastPosX"] = posX.Value;
|
||||||
if (rotY.HasValue) body["lastRotY"] = rotY.Value;
|
}
|
||||||
if (playTimeDelta.HasValue) body["playTimeDelta"] = playTimeDelta.Value;
|
|
||||||
|
if (posY.HasValue)
|
||||||
|
{
|
||||||
|
body["lastPosY"] = posY.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (posZ.HasValue)
|
||||||
|
{
|
||||||
|
body["lastPosZ"] = posZ.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rotY.HasValue)
|
||||||
|
{
|
||||||
|
body["lastRotY"] = rotY.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (playTimeDelta.HasValue)
|
||||||
|
{
|
||||||
|
body["playTimeDelta"] = playTimeDelta.Value;
|
||||||
|
}
|
||||||
|
|
||||||
for (int attempt = 1; attempt <= MAX_RETRY; attempt++)
|
for (int attempt = 1; attempt <= MAX_RETRY; attempt++)
|
||||||
{
|
{
|
||||||
@@ -235,9 +201,114 @@ public class RestApi : Singleton<RestApi>
|
|||||||
Log.Error("[RestApi] 게임 데이터 저장 최종 실패 ({Max}회 시도): {Message}", MAX_RETRY, ex.Message);
|
Log.Error("[RestApi] 게임 데이터 저장 최종 실패 ({Max}회 시도): {Message}", MAX_RETRY, ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed class AuthVerifyResponse
|
||||||
|
{
|
||||||
|
[JsonPropertyName("username")]
|
||||||
|
public string? Username
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class PlayerProfileResponse
|
||||||
|
{
|
||||||
|
[JsonPropertyName("nickname")]
|
||||||
|
public string Nickname
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
} = string.Empty;
|
||||||
|
|
||||||
|
[JsonPropertyName("level")]
|
||||||
|
public int Level
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonPropertyName("experience")]
|
||||||
|
public int Experience
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonPropertyName("nextExp")]
|
||||||
|
public int NextExp
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonPropertyName("maxHp")]
|
||||||
|
public double MaxHp
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonPropertyName("maxMp")]
|
||||||
|
public double MaxMp
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonPropertyName("attackPower")]
|
||||||
|
public double AttackPower
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonPropertyName("attackRange")]
|
||||||
|
public double AttackRange
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonPropertyName("sprintMultiplier")]
|
||||||
|
public double SprintMultiplier
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonPropertyName("lastPosX")]
|
||||||
|
public double LastPosX
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonPropertyName("lastPosY")]
|
||||||
|
public double LastPosY
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonPropertyName("lastPosZ")]
|
||||||
|
public double LastPosZ
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonPropertyName("lastRotY")]
|
||||||
|
public double LastRotY
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private sealed class BossRaidAccessResponse
|
private sealed class BossRaidAccessResponse
|
||||||
{
|
{
|
||||||
[JsonPropertyName("roomId")]
|
[JsonPropertyName("roomId")]
|
||||||
|
|||||||
@@ -176,11 +176,15 @@ public abstract class ServerBase : INetEventListener
|
|||||||
_ = HandleAuth(peer, payload).ContinueWith(t =>
|
_ = HandleAuth(peer, payload).ContinueWith(t =>
|
||||||
{
|
{
|
||||||
if (t.IsFaulted)
|
if (t.IsFaulted)
|
||||||
|
{
|
||||||
Log.Error(t.Exception, "[Server] HandleAuth 예외 PeerId={Id}", peer.Id);
|
Log.Error(t.Exception, "[Server] HandleAuth 예외 PeerId={Id}", peer.Id);
|
||||||
|
}
|
||||||
}, TaskContinuationOptions.OnlyOnFaulted);
|
}, TaskContinuationOptions.OnlyOnFaulted);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (type == (ushort)PacketType.DUMMY_ACC_TOKEN)
|
|
||||||
|
// Auth 이외 패킷 처리
|
||||||
|
if (type == (ushort)PacketType.DUMMY_ACC_TOKEN)
|
||||||
{
|
{
|
||||||
HandleAuthDummy(peer, payload);
|
HandleAuthDummy(peer, payload);
|
||||||
return;
|
return;
|
||||||
@@ -205,9 +209,9 @@ public abstract class ServerBase : INetEventListener
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.Warning("[Server] 레이트 리밋 초과 ({Count}회) HashKey={Key} PeerId={Id}", session.RateLimitViolations, session.HashKey,
|
// 패킷 드롭
|
||||||
peer.Id);
|
Log.Warning("[Server] 레이트 리밋 초과 ({Count}회) HashKey={Key} PeerId={Id}", session.RateLimitViolations, session.HashKey, peer.Id);
|
||||||
return; // 패킷 드롭
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
HandlePacket(peer, session.HashKey, type, payload);
|
HandlePacket(peer, session.HashKey, type, payload);
|
||||||
@@ -237,23 +241,19 @@ public abstract class ServerBase : INetEventListener
|
|||||||
if (PingLogRtt)
|
if (PingLogRtt)
|
||||||
{
|
{
|
||||||
// rtt 시간 출력
|
// rtt 시간 출력
|
||||||
|
// Log.Debug("[Server] latency : {Latency} ", latency);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Echo 서버 테스트
|
// Echo 서버 테스트
|
||||||
|
|
||||||
protected abstract void HandleEcho(NetPeer peer, byte[] payload);
|
protected abstract void HandleEcho(NetPeer peer, byte[] payload);
|
||||||
|
|
||||||
// ─── Auth 처리 (더미) ────────────────────────────────────────────────
|
// Auth 처리 (더미)
|
||||||
|
|
||||||
protected abstract void HandleAuthDummy(NetPeer peer, byte[] payload);
|
protected abstract void HandleAuthDummy(NetPeer peer, byte[] payload);
|
||||||
|
|
||||||
// ─── Auth 처리 ────────────────────────────────────────────────
|
// Auth 처리
|
||||||
|
|
||||||
protected abstract Task HandleAuth(NetPeer peer, byte[] payload);
|
protected abstract Task HandleAuth(NetPeer peer, byte[] payload);
|
||||||
|
|
||||||
// ─── 전송 헬퍼 ───────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
// peer에게 전송
|
// peer에게 전송
|
||||||
protected void SendTo(NetPeer peer, byte[] data, DeliveryMethod method = DeliveryMethod.ReliableOrdered)
|
protected void SendTo(NetPeer peer, byte[] data, DeliveryMethod method = DeliveryMethod.ReliableOrdered)
|
||||||
{
|
{
|
||||||
@@ -278,8 +278,6 @@ public abstract class ServerBase : INetEventListener
|
|||||||
netManager.SendToAll(cachedWriter, 0, method, exclude);
|
netManager.SendToAll(cachedWriter, 0, method, exclude);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── 서브클래스 구현 ─────────────────────────────────────────────────
|
|
||||||
|
|
||||||
// 인증(Auth) 완료 후 호출
|
// 인증(Auth) 완료 후 호출
|
||||||
protected abstract void OnSessionConnected(NetPeer peer, int hashKey);
|
protected abstract void OnSessionConnected(NetPeer peer, int hashKey);
|
||||||
|
|
||||||
|
|||||||
@@ -36,8 +36,6 @@ public static class CrashDumpHandler
|
|||||||
Log.Information("[CrashDump] 핸들러 등록 완료 (CrashDir={Dir})", Path.GetFullPath(CRASH_DIR));
|
Log.Information("[CrashDump] 핸들러 등록 완료 (CrashDir={Dir})", Path.GetFullPath(CRASH_DIR));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── 핸들러 ──────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
|
private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
|
||||||
{
|
{
|
||||||
Exception? ex = e.ExceptionObject as Exception;
|
Exception? ex = e.ExceptionObject as Exception;
|
||||||
@@ -52,8 +50,6 @@ public static class CrashDumpHandler
|
|||||||
WriteCrash(tag, e.Exception);
|
WriteCrash(tag, e.Exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── 핵심 처리 ───────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
private static void WriteCrash(string tag, Exception? ex)
|
private static void WriteCrash(string tag, Exception? ex)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
Reference in New Issue
Block a user