From da51e6ba3ef3a96a6b798ea750defa8a7561a2a4 Mon Sep 17 00:00:00 2001 From: qmengz <554318064@qq.com> Date: Tue, 16 Dec 2025 10:33:49 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20Implement=20soloraid=20-=20=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E5=8D=95=E4=BA=BA=E7=AA=81=E8=A2=AD=20(#71)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added new handlers for closing, entering, and opening solo raids and trials, including: - Close - ClosePractice - CloseTrial - Enter - EnterTrial - Open - OpenPractice - OpenTrial - Introduced FastBattle handler for quick battle functionality. - Implemented methods for getting level and badge data, including: - GetLevel - GetLevelPractice - GetLevelTrial - Implemented methods for setting damage, including: - SetDamage - SetDamagePractice - SetDamageTrial - Added logging and error handling for various operations. - Created SoloRaidHelper class to manage solo raid logic, including opening, closing, and setting damage. - Updated database models to include solo raid data structures. - Enhanced user model to store solo raid information. --- EpinelPS/Data/GameData.cs | 17 + EpinelPS/LobbyServer/Soloraid/Close.cs | 28 + .../LobbyServer/Soloraid/ClosePractice.cs | 28 + EpinelPS/LobbyServer/Soloraid/CloseTrial.cs | 28 + EpinelPS/LobbyServer/Soloraid/Enter.cs | 21 + EpinelPS/LobbyServer/Soloraid/EnterTrial.cs | 21 + EpinelPS/LobbyServer/Soloraid/FastBattle.cs | 25 + EpinelPS/LobbyServer/Soloraid/GetBadgeData.cs | 2 +- EpinelPS/LobbyServer/Soloraid/GetInfo.cs | 35 ++ EpinelPS/LobbyServer/Soloraid/GetLevel.cs | 25 + .../LobbyServer/Soloraid/GetLevelPractice.cs | 25 + .../LobbyServer/Soloraid/GetLevelTrial.cs | 26 + EpinelPS/LobbyServer/Soloraid/GetLogs.cs | 27 + .../{GetSoloraidPeriod.cs => GetPeriod.cs} | 11 +- EpinelPS/LobbyServer/Soloraid/GetRanking.cs | 18 + EpinelPS/LobbyServer/Soloraid/Open.cs | 32 ++ EpinelPS/LobbyServer/Soloraid/OpenPractice.cs | 33 ++ EpinelPS/LobbyServer/Soloraid/OpenTrial.cs | 32 ++ EpinelPS/LobbyServer/Soloraid/SetDamage.cs | 29 + .../LobbyServer/Soloraid/SetDamagePractice.cs | 27 + .../LobbyServer/Soloraid/SetDamageTrial.cs | 29 + .../LobbyServer/Soloraid/SoloRaidHelper.cs | 527 ++++++++++++++++++ EpinelPS/Models/DbModels.cs | 59 ++ EpinelPS/Models/UserModel.cs | 2 + 24 files changed, 1099 insertions(+), 8 deletions(-) create mode 100644 EpinelPS/LobbyServer/Soloraid/Close.cs create mode 100644 EpinelPS/LobbyServer/Soloraid/ClosePractice.cs create mode 100644 EpinelPS/LobbyServer/Soloraid/CloseTrial.cs create mode 100644 EpinelPS/LobbyServer/Soloraid/Enter.cs create mode 100644 EpinelPS/LobbyServer/Soloraid/EnterTrial.cs create mode 100644 EpinelPS/LobbyServer/Soloraid/FastBattle.cs create mode 100644 EpinelPS/LobbyServer/Soloraid/GetInfo.cs create mode 100644 EpinelPS/LobbyServer/Soloraid/GetLevel.cs create mode 100644 EpinelPS/LobbyServer/Soloraid/GetLevelPractice.cs create mode 100644 EpinelPS/LobbyServer/Soloraid/GetLevelTrial.cs create mode 100644 EpinelPS/LobbyServer/Soloraid/GetLogs.cs rename EpinelPS/LobbyServer/Soloraid/{GetSoloraidPeriod.cs => GetPeriod.cs} (53%) create mode 100644 EpinelPS/LobbyServer/Soloraid/GetRanking.cs create mode 100644 EpinelPS/LobbyServer/Soloraid/Open.cs create mode 100644 EpinelPS/LobbyServer/Soloraid/OpenPractice.cs create mode 100644 EpinelPS/LobbyServer/Soloraid/OpenTrial.cs create mode 100644 EpinelPS/LobbyServer/Soloraid/SetDamage.cs create mode 100644 EpinelPS/LobbyServer/Soloraid/SetDamagePractice.cs create mode 100644 EpinelPS/LobbyServer/Soloraid/SetDamageTrial.cs create mode 100644 EpinelPS/LobbyServer/Soloraid/SoloRaidHelper.cs diff --git a/EpinelPS/Data/GameData.cs b/EpinelPS/Data/GameData.cs index a17b7e2..13ef61d 100644 --- a/EpinelPS/Data/GameData.cs +++ b/EpinelPS/Data/GameData.cs @@ -351,6 +351,23 @@ namespace EpinelPS.Data public readonly Dictionary EventAZXAppleGameSkillTable = []; [LoadRecord("EventAZXAppleGameCutSceneTable.json", "Id")] public readonly Dictionary EventAZXAppleGameCutSceneTable = []; + + // solo raid data Table + [LoadRecord("SoloRaidManagerTable.json", "Id")] + public readonly Dictionary SoloRaidManagerTable = []; + [LoadRecord("SoloRaidPresetTable.json", "Id")] + public readonly Dictionary SoloRaidPresetTable = []; + + // Monster data Table + [LoadRecord("MonsterTable.json", "Id")] + public readonly Dictionary MonsterTable = []; + [LoadRecord("MonsterModelTable.json", "Id")] + public readonly Dictionary MonsterModelTable = []; + [LoadRecord("MonsterStatEnhanceTable.json", "Id")] + public readonly Dictionary MonsterStatEnhanceTable = []; + [LoadRecord("WaveDataTable.wave_Intercept_001.json", "StageId")] + public readonly Dictionary WaveIntercept001Table = []; + static async Task BuildAsync() { diff --git a/EpinelPS/LobbyServer/Soloraid/Close.cs b/EpinelPS/LobbyServer/Soloraid/Close.cs new file mode 100644 index 0000000..3faec63 --- /dev/null +++ b/EpinelPS/LobbyServer/Soloraid/Close.cs @@ -0,0 +1,28 @@ +using EpinelPS.Utils; + +namespace EpinelPS.LobbyServer.Soloraid; + +[PacketPath("/soloraid/close")] +public class Close : LobbyMsgHandler +{ + protected override async Task HandleAsync() + { + // int RaidId, int RaidLevel + var req = await ReadData(); + var user = GetUser(); + ResCloseSoloRaid response = new() + { + PeriodResult = SoloRaidPeriodResult.Success + }; + + try + { + SoloRaidHelper.CloseSoloRaid(user, req.RaidId, req.RaidLevel, SoloRaidType.Normal); + }catch(Exception ex) + { + Logging.WriteLine($"CloseSoloRaid Error: {ex.Message}", LogType.Error); + } + + await WriteDataAsync(response); + } +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Soloraid/ClosePractice.cs b/EpinelPS/LobbyServer/Soloraid/ClosePractice.cs new file mode 100644 index 0000000..0057d98 --- /dev/null +++ b/EpinelPS/LobbyServer/Soloraid/ClosePractice.cs @@ -0,0 +1,28 @@ +using EpinelPS.Utils; + +namespace EpinelPS.LobbyServer.Soloraid; + +[PacketPath("/soloraid/practice/close")] +public class ClosePractice : LobbyMsgHandler +{ + protected override async Task HandleAsync() + { + // int RaidId, int RaidLevel + var req = await ReadData(); + var user = GetUser(); + ResCloseSoloRaidPractice response = new() + { + PeriodResult = SoloRaidPeriodResult.Success + }; + + try + { + SoloRaidHelper.CloseSoloRaid(user, req.RaidId, req.RaidLevel, SoloRaidType.Practice); + }catch(Exception ex) + { + Logging.WriteLine($"CloseSoloRaidPractice Error: {ex.Message}", LogType.Error); + } + + await WriteDataAsync(response); + } +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Soloraid/CloseTrial.cs b/EpinelPS/LobbyServer/Soloraid/CloseTrial.cs new file mode 100644 index 0000000..389bae2 --- /dev/null +++ b/EpinelPS/LobbyServer/Soloraid/CloseTrial.cs @@ -0,0 +1,28 @@ +using EpinelPS.Utils; + +namespace EpinelPS.LobbyServer.Soloraid; + +[PacketPath("/soloraid/trial/close")] +public class CloseTrial : LobbyMsgHandler +{ + protected override async Task HandleAsync() + { + // int RaidId, int RaidLevel + var req = await ReadData(); + var user = GetUser(); + ResCloseSoloRaidTrial response = new() + { + PeriodResult = SoloRaidPeriodResult.Success + }; + + try + { + SoloRaidHelper.CloseSoloRaid(user, req.RaidId, req.RaidLevel, SoloRaidType.Trial); + }catch(Exception ex) + { + Logging.WriteLine($"CloseSoloRaidTrial Error: {ex.Message}", LogType.Error); + } + + await WriteDataAsync(response); + } +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Soloraid/Enter.cs b/EpinelPS/LobbyServer/Soloraid/Enter.cs new file mode 100644 index 0000000..e682b75 --- /dev/null +++ b/EpinelPS/LobbyServer/Soloraid/Enter.cs @@ -0,0 +1,21 @@ +using EpinelPS.Utils; + +namespace EpinelPS.LobbyServer.Soloraid; + +[PacketPath("/soloraid/enter")] +public class Enter : LobbyMsgHandler +{ + protected override async Task HandleAsync() + { + var req = await ReadData(); + + Logging.WriteLine($"Entering solo raid {req.RaidId} at level {req.RaidLevel} for user {GetUser().ID} team {req.Team} members"); + + ResEnterSoloRaid response = new() + { + PeriodResult = SoloRaidPeriodResult.Success, + }; + + await WriteDataAsync(response); + } +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Soloraid/EnterTrial.cs b/EpinelPS/LobbyServer/Soloraid/EnterTrial.cs new file mode 100644 index 0000000..8b26ee8 --- /dev/null +++ b/EpinelPS/LobbyServer/Soloraid/EnterTrial.cs @@ -0,0 +1,21 @@ +using EpinelPS.Utils; + +namespace EpinelPS.LobbyServer.Soloraid; + +[PacketPath("/soloraid/trial/enter")] +public class EnterTrial : LobbyMsgHandler +{ + protected override async Task HandleAsync() + { + var req = await ReadData(); + + Logging.WriteLine($"Entering solo raid {req.RaidId} at level {req.RaidLevel} for user {GetUser().ID} team {req.Team} members"); + + ResEnterSoloRaidTrial response = new() + { + PeriodResult = SoloRaidPeriodResult.Success, + }; + + await WriteDataAsync(response); + } +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Soloraid/FastBattle.cs b/EpinelPS/LobbyServer/Soloraid/FastBattle.cs new file mode 100644 index 0000000..a2a3fec --- /dev/null +++ b/EpinelPS/LobbyServer/Soloraid/FastBattle.cs @@ -0,0 +1,25 @@ +using EpinelPS.Utils; + +namespace EpinelPS.LobbyServer.Soloraid; + +[PacketPath("/soloraid/fastbattle")] +public class FastBattle : LobbyMsgHandler +{ + protected override async Task HandleAsync() + { + var req = await ReadData(); + var user = GetUser(); + ResFastBattleSoloRaid response = new(); + + try + { + SoloRaidHelper.FastBattle(user, ref response, req.RaidId, req.RaidLevel, req.ClearCount); + } + catch (Exception e) + { + Logging.WriteLine($"FastBattle Error {e.Message}"); + } + + await WriteDataAsync(response); + } +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Soloraid/GetBadgeData.cs b/EpinelPS/LobbyServer/Soloraid/GetBadgeData.cs index a9724ba..275c67c 100644 --- a/EpinelPS/LobbyServer/Soloraid/GetBadgeData.cs +++ b/EpinelPS/LobbyServer/Soloraid/GetBadgeData.cs @@ -1,6 +1,6 @@ using EpinelPS.Utils; -namespace EpinelPS.LobbyServer.SoloraId; +namespace EpinelPS.LobbyServer.Soloraid; [PacketPath("/soloraidmuseum/get/reddotdata")] public class GetBadgeData : LobbyMsgHandler diff --git a/EpinelPS/LobbyServer/Soloraid/GetInfo.cs b/EpinelPS/LobbyServer/Soloraid/GetInfo.cs new file mode 100644 index 0000000..f65efa1 --- /dev/null +++ b/EpinelPS/LobbyServer/Soloraid/GetInfo.cs @@ -0,0 +1,35 @@ +using EpinelPS.Utils; + +namespace EpinelPS.LobbyServer.Soloraid; + +[PacketPath("/soloraid/get")] +public class GetInfo : LobbyMsgHandler +{ + protected override async Task HandleAsync() + { + await ReadData(); + User user = GetUser(); + + // ResGetSoloRaidInfo Fields + // NetUserSoloRaidInfo Info + // SoloRaidPeriodResult PeriodResult + // SoloRaidBanResult BanResult + ResGetSoloRaidInfo response = new() + { + Info = new NetUserSoloRaidInfo(), + PeriodResult = SoloRaidPeriodResult.Success, + BanResult = SoloRaidBanResult.Success + }; + + try + { + response.Info = SoloRaidHelper.GetUserSoloRaidInfo(user); + } + catch (Exception ex) + { + Logging.WriteLine($"GetSoloRaidInfo error: {ex.Message}", LogType.Error); + } + + await WriteDataAsync(response); + } +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Soloraid/GetLevel.cs b/EpinelPS/LobbyServer/Soloraid/GetLevel.cs new file mode 100644 index 0000000..2e474d2 --- /dev/null +++ b/EpinelPS/LobbyServer/Soloraid/GetLevel.cs @@ -0,0 +1,25 @@ +using EpinelPS.Utils; + +namespace EpinelPS.LobbyServer.Soloraid; + +[PacketPath("/soloraid/getlevel")] +public class GetLevel : LobbyMsgHandler +{ + protected override async Task HandleAsync() + { + var req = await ReadData(); + User user = GetUser(); + ResGetLevelSoloRaid response = new(); + + try + { + SoloRaidHelper.GetLevelInfo(user, ref response, req.RaidLevel); + } + catch (Exception ex) + { + Logging.WriteLine($"GetLevelSoloRaid Error: {ex.Message}", LogType.Error); + } + + await WriteDataAsync(response); + } +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Soloraid/GetLevelPractice.cs b/EpinelPS/LobbyServer/Soloraid/GetLevelPractice.cs new file mode 100644 index 0000000..98c98fc --- /dev/null +++ b/EpinelPS/LobbyServer/Soloraid/GetLevelPractice.cs @@ -0,0 +1,25 @@ +using EpinelPS.Utils; + +namespace EpinelPS.LobbyServer.Soloraid; + +[PacketPath("/soloraid/practice/getlevel")] +public class GetLevelPractice : LobbyMsgHandler +{ + protected override async Task HandleAsync() + { + var req = await ReadData(); + var user = GetUser(); + ResGetLevelPracticeSoloRaid response = new(); + + try + { + SoloRaidHelper.GetLevelPracticeInfo(user, ref response, req.RaidLevel); + } + catch (Exception ex) + { + Logging.WriteLine($"GetLevelPractice Error: {ex.Message}", LogType.Error); + } + + await WriteDataAsync(response); + } +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Soloraid/GetLevelTrial.cs b/EpinelPS/LobbyServer/Soloraid/GetLevelTrial.cs new file mode 100644 index 0000000..5da5fbd --- /dev/null +++ b/EpinelPS/LobbyServer/Soloraid/GetLevelTrial.cs @@ -0,0 +1,26 @@ +using EpinelPS.Utils; + +namespace EpinelPS.LobbyServer.Soloraid; + +[PacketPath("/soloraid/trial/getlevel")] +public class GetLevelTrial : LobbyMsgHandler +{ + protected override async Task HandleAsync() + { + // int RaidLevel + var req = await ReadData(); + User user = GetUser(); + ResGetLevelTrialSoloRaid response = new(); + + try + { + SoloRaidHelper.GetLevelTrialInfo(user, ref response, req.RaidLevel); + } + catch (Exception ex) + { + Logging.WriteLine($"GetLevelTrial Error: {ex.Message}", LogType.Error); + } + + await WriteDataAsync(response); + } +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Soloraid/GetLogs.cs b/EpinelPS/LobbyServer/Soloraid/GetLogs.cs new file mode 100644 index 0000000..a23b34c --- /dev/null +++ b/EpinelPS/LobbyServer/Soloraid/GetLogs.cs @@ -0,0 +1,27 @@ +using EpinelPS.Utils; + +namespace EpinelPS.LobbyServer.Soloraid +{ + [PacketPath("/soloraid/getlogs")] + public class GetLogs : LobbyMsgHandler + { + protected override async Task HandleAsync() + { + // int RaidId, int RaidLevel + var req = await ReadData(); + var user = GetUser(); + ResGetSoloRaidLogs response = new(); + + try + { + SoloRaidHelper.GetSoloRaidLog(user, ref response, req.RaidId, req.RaidLevel); + } + catch (Exception ex) + { + Logging.WriteLine($"GetLogs Error: {ex.Message}", LogType.Error); + } + + await WriteDataAsync(response); + } + } +} diff --git a/EpinelPS/LobbyServer/Soloraid/GetSoloraidPeriod.cs b/EpinelPS/LobbyServer/Soloraid/GetPeriod.cs similarity index 53% rename from EpinelPS/LobbyServer/Soloraid/GetSoloraidPeriod.cs rename to EpinelPS/LobbyServer/Soloraid/GetPeriod.cs index 5267e8d..b2212f8 100644 --- a/EpinelPS/LobbyServer/Soloraid/GetSoloraidPeriod.cs +++ b/EpinelPS/LobbyServer/Soloraid/GetPeriod.cs @@ -1,20 +1,17 @@ using EpinelPS.Utils; -namespace EpinelPS.LobbyServer.SoloraId +namespace EpinelPS.LobbyServer.Soloraid { [PacketPath("/soloraid/getperiod")] - public class GetSoloraidPeriod : LobbyMsgHandler + public class GetPeriod : LobbyMsgHandler { protected override async Task HandleAsync() { - ReqGetSoloRaidPeriod req = await ReadData(); + await ReadData(); ResGetSoloRaidPeriod response = new() { - Period = new NetSoloRaidPeriodData - { - - } + Period = SoloRaidHelper.GetSoloRaidPeriod() }; // TODO await WriteDataAsync(response); diff --git a/EpinelPS/LobbyServer/Soloraid/GetRanking.cs b/EpinelPS/LobbyServer/Soloraid/GetRanking.cs new file mode 100644 index 0000000..914e9a0 --- /dev/null +++ b/EpinelPS/LobbyServer/Soloraid/GetRanking.cs @@ -0,0 +1,18 @@ +using EpinelPS.Utils; + +namespace EpinelPS.LobbyServer.Soloraid; + +[PacketPath("/soloraid/getranking")] +public class GetRanking : LobbyMsgHandler +{ + protected override async Task HandleAsync() + { + await ReadData(); + + ResGetSoloRaidRanking response = new(); + + // TODO + + await WriteDataAsync(response); + } +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Soloraid/Open.cs b/EpinelPS/LobbyServer/Soloraid/Open.cs new file mode 100644 index 0000000..745eb6b --- /dev/null +++ b/EpinelPS/LobbyServer/Soloraid/Open.cs @@ -0,0 +1,32 @@ +using EpinelPS.Database; +using EpinelPS.Utils; + +namespace EpinelPS.LobbyServer.Soloraid; + +[PacketPath("/soloraid/open")] +public class Open : LobbyMsgHandler +{ + protected override async Task HandleAsync() + { + // { "raidId": 1000030, "raidLevel": 1 } + var req = await ReadData(); + User user = GetUser(); + ResOpenSoloRaid response = new() + { + PeriodResult = SoloRaidPeriodResult.Success, + }; + + try + { + int openCount = SoloRaidHelper.OpenSoloRaid(user, req.RaidId, req.RaidLevel); + response.RaidOpenCount = openCount; + } + catch (Exception ex) + { + Logging.WriteLine($"OpenSoloRaid error: {ex.Message}", LogType.Error); + } + + JsonDb.Save(); + await WriteDataAsync(response); + } +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Soloraid/OpenPractice.cs b/EpinelPS/LobbyServer/Soloraid/OpenPractice.cs new file mode 100644 index 0000000..8d410fc --- /dev/null +++ b/EpinelPS/LobbyServer/Soloraid/OpenPractice.cs @@ -0,0 +1,33 @@ +using EpinelPS.Database; +using EpinelPS.Utils; + +namespace EpinelPS.LobbyServer.Soloraid; + +[PacketPath("/soloraid/practice/open")] +public class OpenPractice : LobbyMsgHandler +{ + protected override async Task HandleAsync() + { + // ReqOpenSoloRaidPractice Fields: + // int RaidLevel + // SoloRaidDifficultyType DifficultyType + var req = await ReadData(); + User user = GetUser(); + ResOpenSoloRaidPractice response = new() + { + PeriodResult = SoloRaidPeriodResult.Success, + }; + + try + { + SoloRaidHelper.OpenSoloRaid(user, 0, req.RaidLevel, type: SoloRaidType.Practice); + } + catch (Exception ex) + { + Logging.WriteLine($"OpenSoloRaid error: {ex.Message}", LogType.Error); + } + + JsonDb.Save(); + await WriteDataAsync(response); + } +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Soloraid/OpenTrial.cs b/EpinelPS/LobbyServer/Soloraid/OpenTrial.cs new file mode 100644 index 0000000..a16d27d --- /dev/null +++ b/EpinelPS/LobbyServer/Soloraid/OpenTrial.cs @@ -0,0 +1,32 @@ +using EpinelPS.Database; +using EpinelPS.Utils; + +namespace EpinelPS.LobbyServer.Soloraid; + +[PacketPath("/soloraid/trial/open")] +public class OpenTrial : LobbyMsgHandler +{ + protected override async Task HandleAsync() + { + // { "raidLevel": 8 } + var req = await ReadData(); + User user = GetUser(); + ResOpenSoloRaidTrial response = new() + { + PeriodResult = SoloRaidPeriodResult.Success, + RaidOpenCount = 1, + }; + + try + { + response.RaidOpenCount = SoloRaidHelper.OpenSoloRaid(user, 0, req.RaidLevel, type: SoloRaidType.Trial); + } + catch (Exception ex) + { + Logging.WriteLine($"OpenSoloRaid error: {ex.Message}", LogType.Error); + } + + JsonDb.Save(); + await WriteDataAsync(response); + } +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Soloraid/SetDamage.cs b/EpinelPS/LobbyServer/Soloraid/SetDamage.cs new file mode 100644 index 0000000..0ec7845 --- /dev/null +++ b/EpinelPS/LobbyServer/Soloraid/SetDamage.cs @@ -0,0 +1,29 @@ +using EpinelPS.Database; +using EpinelPS.Utils; + +namespace EpinelPS.LobbyServer.Soloraid; + +[PacketPath("/soloraid/setdamage")] +public class SetDamage : LobbyMsgHandler +{ + protected override async Task HandleAsync() + { + + var req = await ReadData(); + User user = GetUser(); + + ResSetSoloRaidDamage response = new(); + + try + { + SoloRaidHelper.SetDamage(user, ref response, req); + } + catch (Exception ex) + { + Logging.WriteLine($"SetDamage Error: {ex.Message}", LogType.Error); + } + + JsonDb.Save(); + await WriteDataAsync(response); + } +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Soloraid/SetDamagePractice.cs b/EpinelPS/LobbyServer/Soloraid/SetDamagePractice.cs new file mode 100644 index 0000000..3fee236 --- /dev/null +++ b/EpinelPS/LobbyServer/Soloraid/SetDamagePractice.cs @@ -0,0 +1,27 @@ +using EpinelPS.Database; +using EpinelPS.Utils; + +namespace EpinelPS.LobbyServer.Soloraid; + +[PacketPath("/soloraid/practice/setdamage")] +public class SetDamagePractice : LobbyMsgHandler +{ + protected override async Task HandleAsync() + { + var req = await ReadData(); + var user = GetUser(); + ResSetSoloRaidPracticeDamage response = new(); + + try + { + SoloRaidHelper.SetDamagePractice(user, ref response, req); + } + catch (Exception ex) + { + Logging.WriteLine($"SetDamagePractice Error: {ex.Message}", LogType.Error); + } + + JsonDb.Save(); + await WriteDataAsync(response); + } +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Soloraid/SetDamageTrial.cs b/EpinelPS/LobbyServer/Soloraid/SetDamageTrial.cs new file mode 100644 index 0000000..7f8a66a --- /dev/null +++ b/EpinelPS/LobbyServer/Soloraid/SetDamageTrial.cs @@ -0,0 +1,29 @@ +using EpinelPS.Database; +using EpinelPS.Utils; + +namespace EpinelPS.LobbyServer.Soloraid; + +[PacketPath("/soloraid/trial/setdamage")] +public class SetDamageTrial : LobbyMsgHandler +{ + protected override async Task HandleAsync() + { + + var req = await ReadData(); + User user = GetUser(); + + ResSetSoloRaidTrialDamage response = new(); + + try + { + SoloRaidHelper.SetDamageTrial(user, ref response, req); + } + catch (Exception ex) + { + Logging.WriteLine($"SetDamageTrial Error: {ex.Message}", LogType.Error); + } + + JsonDb.Save(); + await WriteDataAsync(response); + } +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Soloraid/SoloRaidHelper.cs b/EpinelPS/LobbyServer/Soloraid/SoloRaidHelper.cs new file mode 100644 index 0000000..375fd3f --- /dev/null +++ b/EpinelPS/LobbyServer/Soloraid/SoloRaidHelper.cs @@ -0,0 +1,527 @@ +using EpinelPS.Data; +using EpinelPS.Database; +using EpinelPS.Utils; +using Google.Protobuf.Collections; +using log4net; +using Newtonsoft.Json; + +namespace EpinelPS.LobbyServer.Soloraid +{ + public class SoloRaidHelper + { + private static readonly ILog log = LogManager.GetLogger(typeof(SoloRaidHelper)); + + /// + /// Open a solo raid + /// + public static int OpenSoloRaid(User user, int raidId, int raidLevel, SoloRaidType type = SoloRaidType.Normal) + { + if (raidId == 0) raidId = GetRaidId(); + + // Check if the raid manager exists, if not, exit + if (!GameData.Instance.SoloRaidManagerTable.TryGetValue(raidId, out var manager)) return 0; + log.Debug($"Fond SoloRaidManager: RaidId: {raidId}, SoloRaidManager: {JsonConvert.SerializeObject(manager)}"); + + // Get the preset for the raid level + var preset = GameData.Instance.SoloRaidPresetTable.Values.FirstOrDefault(r => + r.PresetGroupId == manager.MonsterPreset && r.WaveOrder == raidLevel); + if (preset is null) return 0; // If the preset is null, exit + log.Debug($"Fond SoloRaidPreset: PresetGroupId: {manager.MonsterPreset}, WaveOrder: {raidLevel}, SoloRaidPreset: {JsonConvert.SerializeObject(preset)}"); + + var statEnhanceId = GetStatEnhanceIdByWave(preset.Wave); // Get the stat enhance id for the wave + if (statEnhanceId == 0) return 0; // If the stat enhance id is 0, exit + + // Get the stat enhance data for the raid level + var statEnhance = GameData.Instance.MonsterStatEnhanceTable.Values.Where(m => m.Lv == preset.MonsterStageLv && m.GroupId == statEnhanceId); + log.Debug($"Fond MonsterStatEnhance: Lv: {preset.MonsterStageLv}, GroupId: {statEnhanceId}, MonsterStatEnhance: {JsonConvert.SerializeObject(statEnhance)}"); + + var levelHp = statEnhance.Sum(m => m.LevelHp); // Monster level hp = statEnhance.Sum(m => m.LevelHp) + + var raid = GetSoloRaidData(user, raidId, isAdd: true); + switch (type) + { + case SoloRaidType.Normal: + raid.RaidOpenCount++; // If the raid is a normal raid, increment the raid open count + break; + case SoloRaidType.Trial: + raid.TrialCount++; // If the raid is a trial raid, increment the trial count + break; + case SoloRaidType.Practice: + break; + default: + Logging.Warn($"Unknown SoloRaidType: {type}"); + break; + } + + var level = GetSoloRaidLevelData(raid, raidLevel, type, isOpen: true); + // Reset the level data + if (level != null) + { + raid.SoloRaidLevels.Remove(level); + } + level = new SoloRaidLevelData + { + RaidLevel = raidLevel, + Hp = levelHp, + Type = type, + Status = SoloRaidStatus.Alive, + IsClear = false, + IsOpen = true, + }; + raid.SoloRaidLevels.Add(level); + + user.SoloRaidData[raidId] = raid; + return type == SoloRaidType.Trial ? raid.TrialCount : raid.RaidOpenCount; + } + + public static void CloseSoloRaid(User user, int raidId, int raidLevel, SoloRaidType type = SoloRaidType.Normal) + { + Logging.WriteLine($"CloseSoloRaid: raidId: {raidId}, raidLevel: {raidLevel}, type: {type}"); + if (raidId == 0) raidId = GetRaidId(); + + var raid = GetSoloRaidData(user, raidId, isAdd: false); + // Reset the raid data + if (type == SoloRaidType.Normal) raid.RaidOpenCount--; // If the raid is a normal raid, decrement the raid open count + if (raid.RaidOpenCount < 0) raid.RaidOpenCount = 0; // If the raid open count is less than 0, set it to 0 + if (type == SoloRaidType.Trial) raid.TrialCount--; + if (raid.TrialCount < 0) raid.TrialCount = 0; + + var level = GetSoloRaidLevelData(raid, raidLevel, type, isOpen: true); + if (level is not null) raid.SoloRaidLevels.Remove(level); // If the level is not null, remove it from the raid data + if (type == SoloRaidType.Trial && level is not null) + { + // If the raid is a trial raid and the level is not null, check if the level is the highest damage level + // If the current level's damage is higher than the previous level, update the status and remove the old level + level.Status = SoloRaidStatus.Kill; + level.IsClear = true; + level.IsOpen = false; + var oldLevel = GetSoloRaidLevelData(raid, raidLevel, SoloRaidType.Trial, isOpen: false); + if (oldLevel is null) + { + raid.SoloRaidLevels.Add(level); + } + else if (level.TotalDamage > oldLevel.TotalDamage) + { + raid.SoloRaidLevels.Remove(oldLevel); + raid.SoloRaidLevels.Add(level); + } + } + user.SoloRaidData[raidId] = raid; + JsonDb.Save(); + } + + /// + /// Gets the user solo raid info + /// + public static NetUserSoloRaidInfo GetUserSoloRaidInfo(User user) + { + int SoloRaidManagerTid = GetRaidId(); + NetUserSoloRaidInfo info = new() + { + SoloRaidManagerTid = SoloRaidManagerTid, + LastClearLevel = 0, // set the last clear level + RaidOpenCount = 0, // set the raid open count + Period = GetSoloRaidPeriod(), + }; + + var raidData = GetSoloRaidData(user, SoloRaidManagerTid); + if (raidData is null) return info; + info.RaidOpenCount = raidData.RaidOpenCount; + + int lastClearLevel = GetLastClearLevelId(user, SoloRaidManagerTid); + info.LastClearLevel = lastClearLevel; + + var openLevelData = raidData.SoloRaidLevels.Where(r => r.IsOpen).OrderBy(x => x.RaidLevel).FirstOrDefault(); + if (openLevelData is not null) info.LastOpenRaid = new NetSoloRaid { Level = openLevelData.RaidLevel, Type = openLevelData.Type }; + + var trialLevelData = GetSoloRaidLevelData(raidData, 8, SoloRaidType.Trial); + if (trialLevelData is null) return info; + + info.TrialCount = raidData.TrialCount; + info.TrialDamage = trialLevelData.TotalDamage; + + return info; + } + + public static void SetDamage(User user, ref ResSetSoloRaidDamage response, ReqSetSoloRaidDamage req) + { + (int rewardId, int FirstRewardId, var levelData) = + SetDamage(user, req.RaidLevel, req.Damage, req.AntiCheatBattleData.WaveId, req.AntiCheatBattleData.Characters); + + if (rewardId > 0) response.Reward = RewardUtils.RegisterRewardsForUser(user, rewardId); + if (FirstRewardId > 0) response.FirstClearReward = RewardUtils.RegisterRewardsForUser(user, FirstRewardId); + + response.Info = new NetNormalSoloRaid + { + Level = req.RaidLevel, + Hp = levelData.Hp - levelData.TotalDamage, + }; + + response.RaidJoinCount = levelData.RaidJoinCount; + response.Status = levelData.Status; + response.PeriodResult = SoloRaidPeriodResult.Success; + response.JoinData = GetJoinData(levelData); + } + + public static void SetDamagePractice(User user, ref ResSetSoloRaidPracticeDamage response, ReqSetSoloRaidPracticeDamage req) + { + (_, _, var levelData) = + SetDamage(user, req.RaidLevel, req.Damage, req.AntiCheatBattleData.WaveId, req.AntiCheatBattleData.Characters, SoloRaidType.Practice); + + response.Info = new NetPracticeSoloRaid + { + Level = req.RaidLevel, + Hp = levelData.Hp - levelData.TotalDamage, + }; + + response.RaidJoinCount = levelData.RaidJoinCount; + response.Status = levelData.Status; + response.PeriodResult = SoloRaidPeriodResult.Success; + response.JoinData = GetJoinData(levelData); + } + + public static void SetDamageTrial(User user, ref ResSetSoloRaidTrialDamage response, ReqSetSoloRaidTrialDamage req) + { + (_, _, var levelData) = + SetDamage(user, req.RaidLevel, req.Damage, req.AntiCheatBattleData.WaveId, req.AntiCheatBattleData.Characters, SoloRaidType.Trial); + + response.Info = new NetTrialSoloRaid + { + Level = req.RaidLevel, + Damage = levelData.TotalDamage, + }; + + response.RaidJoinCount = levelData.RaidJoinCount; + response.Status = levelData.Status; + response.PeriodResult = SoloRaidPeriodResult.Success; + response.JoinData = GetJoinData(levelData); + } + + public static void GetLevelInfo(User user, ref ResGetLevelSoloRaid response, int raidLevel) + { + int raidId = GetRaidId(); + + response.PeriodResult = SoloRaidPeriodResult.Success; + var raidData = GetSoloRaidData(user, raidId, isAdd: false); + var levelData = GetSoloRaidLevelData(raidData, raidLevel, SoloRaidType.Normal, true); + if (levelData is null) return; + + response.Raid = new NetNormalSoloRaid + { + Level = raidLevel, + Hp = levelData.Hp - levelData.TotalDamage, + }; + response.RaidJoinCount = levelData.RaidJoinCount; + response.JoinData = GetJoinData(levelData); + } + + public static void GetLevelPracticeInfo(User user, ref ResGetLevelPracticeSoloRaid response, int raidLevel) + { + int raidId = GetRaidId(); + + response.PeriodResult = SoloRaidPeriodResult.Success; + var raidData = GetSoloRaidData(user, raidId, isAdd: false); + var levelData = GetSoloRaidLevelData(raidData, raidLevel, SoloRaidType.Practice, true); + if (levelData is null) return; + + response.Raid = new NetPracticeSoloRaid + { + Level = raidLevel, + Hp = levelData.Hp - levelData.TotalDamage, + }; + response.RaidJoinCount = levelData.RaidJoinCount; + response.PeriodResult = SoloRaidPeriodResult.Success; + response.JoinData = GetJoinData(levelData); + } + + public static void GetLevelTrialInfo(User user, ref ResGetLevelTrialSoloRaid response, int raidLevel) + { + int raidId = GetRaidId(); + + response.PeriodResult = SoloRaidPeriodResult.Success; + var raidData = GetSoloRaidData(user, raidId, isAdd: false); + var levelData = GetSoloRaidLevelData(raidData, raidLevel, SoloRaidType.Trial, true); + if (levelData is null) return; + + response.Raid = new NetTrialSoloRaid + { + Level = raidLevel, + Damage = levelData.TotalDamage, + }; + response.RaidJoinCount = levelData.RaidJoinCount; + response.PeriodResult = SoloRaidPeriodResult.Success; + response.JoinData = GetJoinData(levelData); + } + + public static void GetSoloRaidLog(User user, ref ResGetSoloRaidLogs response, int raidId, int raidLevel) + { + // ResGetSoloRaidLogs Fields: + // RepeatedField Logs + // SoloRaidBanResult BanResult + // RepeatedField PracticeLogs + // NetSoloRaidLog Fields: + // long Damage + // RepeatedField Team + // bool Kill + // NetSoloRaidTeamCharacter Fields: + // int Slot + // int Tid + // int Lv + // int Combat + // int CostumeId + + if (raidId == 0) raidId = GetRaidId(); + var raidData = GetSoloRaidData(user, raidId); + var levelData = GetSoloRaidLevelData(raidData, raidLevel, SoloRaidType.Normal); + if (levelData is not null && levelData.Logs.Count > 0) + { + response.Logs.AddRange(levelData.Logs.Select(x => x.ToNet())); + } + levelData = GetSoloRaidLevelData(raidData, raidLevel, SoloRaidType.Normal, true); + if (levelData is not null && levelData.Logs.Count > 0) + { + response.Logs.AddRange(levelData.Logs.Select(x => x.ToNet())); + } + + var practiceLevelData = GetSoloRaidLevelData(raidData, raidLevel, type: SoloRaidType.Practice); + if (practiceLevelData is not null && practiceLevelData.Logs.Count > 0) + { + response.PracticeLogs.AddRange(practiceLevelData.Logs.Select(x => x.ToNet())); + } + practiceLevelData = GetSoloRaidLevelData(raidData, raidLevel, type: SoloRaidType.Practice, true); + if (practiceLevelData is not null && practiceLevelData.Logs.Count > 0) + { + response.PracticeLogs.AddRange(practiceLevelData.Logs.Select(x => x.ToNet())); + } + } + + public static void GetSoloRaidRanking(User user, ref ResGetSoloRaidRanking response) + { + // ResGetSoloRaidRanking Fields: + // RepeatedField Rankings + // NetSoloRaidRankingData User + // long TotalUserCount + // SoloRaidBanResult BanResult + // NetSoloRaidRankingData Fields: + // long Ranking + // long Damage + // NetWholeUserData User + // long ReportBattleId + // Google.Protobuf.WellKnownTypes.Timestamp ReportBattleDate + // + } + public static void FastBattle(User user, ref ResFastBattleSoloRaid response, int raidId, int raidLevel, int clearCount) + { + var raidData = GetSoloRaidData(user, raidId, isAdd: true); + raidData.RaidOpenCount += clearCount; + user.SoloRaidData[raidId] = raidData; + response.PeriodResult = SoloRaidPeriodResult.Success; + if (GameData.Instance.SoloRaidManagerTable.TryGetValue(raidId, out var manager)) + { + var presetData = GameData.Instance.SoloRaidPresetTable.Values.FirstOrDefault(r => + r.PresetGroupId == manager.MonsterPreset && r.WaveOrder == raidLevel); + if (presetData is not null) + { + NetRewardData reward = new(); + for (int i = 0; i < clearCount; i++) + { + var newReward = RewardUtils.RegisterRewardsForUser(user, presetData.RewardId); + reward.MergeFrom(newReward); + } + response.Reward = reward; + } + } + response.RaidOpenCount = raidData.RaidOpenCount; + + JsonDb.Save(); + } + + + public static (int rewardId, int FirstRewardId, SoloRaidLevelData levelData) + SetDamage(User user, int raidLevel, long damage, int waveId, + RepeatedField characters, SoloRaidType type = SoloRaidType.Normal) + { + int rewardId = 0; + int FirstRewardId = 0; + int raidId = GetRaidId(); + + var raidData = GetSoloRaidData(user, raidId, isAdd: true); + var levelData = GetSoloRaidLevelData(raidData, raidLevel, type, isOpen: true); + var oldLevel = GetSoloRaidLevelData(raidData, raidLevel, type, isOpen: false); + + // Remove existing level data + if (levelData is not null) + raidData.SoloRaidLevels.Remove(levelData); + else + levelData = new SoloRaidLevelData() { RaidLevel = raidLevel, Type = type, IsOpen = true }; + + levelData.TotalDamage += damage; + levelData.RaidJoinCount++; + + if (type == SoloRaidType.Trial || raidLevel == 8) + { + if (levelData.RaidJoinCount == 5) + { + levelData.Status = SoloRaidStatus.Kill; + levelData.IsClear = true; + levelData.IsOpen = false; + } + else + { + levelData.Status = SoloRaidStatus.Alive; + } + } + else if (levelData.TotalDamage >= levelData.Hp) + { + levelData.TotalDamage = levelData.Hp; + levelData.Status = SoloRaidStatus.Kill; + if (type == SoloRaidType.Normal) + { + var presetData = GameData.Instance.SoloRaidPresetTable.Values.FirstOrDefault(r => + r.Wave == waveId && r.WaveOrder == raidLevel); + if (presetData is not null) + { + bool isFirstClear = oldLevel is null; + rewardId = presetData.RewardId; + FirstRewardId = !isFirstClear ? 0 : presetData.FirstClearRewardId; + } + } + + levelData.IsClear = true; + levelData.IsOpen = false; + } + else + { + levelData.Status = SoloRaidStatus.Alive; + } + + SoloRaidLogData logData = new() + { + Damage = damage, + Kill = levelData.Status == SoloRaidStatus.Kill, + }; + + foreach (var item in characters) + { + int costumeId = user.Characters.FirstOrDefault(c => c.Tid == item.Tid)?.CostumeId ?? 0; + logData.Team.Add(new TeamCharacterData + { + Slot = item.Slot, + Tid = item.Tid, + Csn = item.Csn, + Lv = item.CharacterSpec.Level, + Combat = (int)item.CharacterSpec.Combat, + CostumeId = costumeId, + }); + } + + levelData.Logs.Add(logData); + + // If the level is not open, remove the old level data + if (!levelData.IsOpen && oldLevel is not null) + { + raidData.SoloRaidLevels.Remove(oldLevel); + } + // If the level is not open and join count is 5, do not add to levels + if (!(levelData.RaidJoinCount == 5 && levelData.IsOpen)) + { + raidData.SoloRaidLevels.Add(levelData); + } + user.SoloRaidData[raidId] = raidData; + + return (rewardId, FirstRewardId, levelData); + } + + public static NetSoloRaidJoinData GetJoinData(SoloRaidLevelData? levelData) + { + var joinData = new NetSoloRaidJoinData(); + if (levelData is null || levelData.Logs.Count <= 0) return joinData; + foreach (var item in levelData.Logs) + { + joinData.CsnList.AddRange(item.Team.Select(l => l.Csn)); + } + + return joinData; + } + + /// + /// Gets the solo raid data for the user + /// + public static SoloRaidInfo? GetSoloRaidData(User user, int raidId, bool isAdd = false) + { + // Get the solo raid data for the raidId, if not found, isAdd is true, create a new one + if (!user.SoloRaidData.TryGetValue(raidId, out var raidData)) + { + raidData = new() { RaidId = raidId, LastDateDay = user.GetDateDay() }; + if (isAdd) user.SoloRaidData.TryAdd(raidId, raidData); + return isAdd ? raidData : null; + } + + ResetOpenCount(user, ref raidData); + return raidData; + } + + /// + /// Gets the last clear level id + /// + public static int GetLastClearLevelId(User user, int raidId) + { + var raidData = GetSoloRaidData(user, raidId, isAdd: false); + if (raidData is null || !raidData.SoloRaidLevels.Where(r => r.IsClear && r.Type == SoloRaidType.Normal).Any()) return 0; + return raidData.SoloRaidLevels.Where(r => r.IsClear && r.Type == SoloRaidType.Normal).Max(r => r.RaidLevel); + } + + public static SoloRaidLevelData? GetSoloRaidLevelData(SoloRaidInfo raidData, int raidLevel, SoloRaidType type, bool isOpen = false) + { + return raidData.SoloRaidLevels.FirstOrDefault(r => r.RaidLevel == raidLevel && r.Type == type && r.IsOpen == isOpen); + } + + public static void ResetOpenCount(User user, ref SoloRaidInfo? raidData) + { + if (raidData is null) return; + int newDateDay = user.GetDateDay(); + if (newDateDay <= raidData.LastDateDay) return; + Logging.WriteLine($"Reset OpenCount: LastDateDay: {raidData.LastDateDay}, NewDateDay: {newDateDay}, Old: {raidData.RaidOpenCount}, New: 0 ", LogType.Warning); + raidData.LastDateDay = newDateDay; + raidData.RaidOpenCount = 0; + raidData.TrialCount = 0; + } + + /// + /// Gets the solo raid period data + /// + /// + public static NetSoloRaidPeriodData GetSoloRaidPeriod() + { + DateTime utcNow = DateTime.UtcNow.Date; + return new NetSoloRaidPeriodData + { + VisibleDate = utcNow.AddDays(-10).Ticks, + StartDate = utcNow.AddDays(-5).Ticks, + EndDate = utcNow.AddDays(5).Ticks, + DisableDate = utcNow.AddDays(10).Ticks, + SettleDate = utcNow.AddDays(15).Ticks, + }; + } + + private static int GetStatEnhanceIdByWave(int wave) + { + // Get the intercept data for the wave, if not found, return 0 + if (!GameData.Instance.WaveIntercept001Table.TryGetValue(wave, out var intercept)) return 0; + log.Debug($"Fond WaveIntercept001: Wave: {wave}, WaveIntercept: {JsonConvert.SerializeObject(intercept)}"); + if (intercept.TargetList!.Count == 0) return 0; + + var monsterId = intercept.TargetList[0]; // monsterId is the first element of the TargetList + // Get the monster data for the monsterId, if not found, return 0 + if (!GameData.Instance.MonsterTable.TryGetValue(monsterId, out var monster)) return 0; + monster.SkillData = []; + log.Debug($"Fond Monster: MonsterId: {monsterId}, Monster: {JsonConvert.SerializeObject(monster)}"); + return monster.StatenhanceId; + } + + public static int GetRaidId() + { + return GameData.Instance.SoloRaidManagerTable.Keys.Max(); + } + } +} \ No newline at end of file diff --git a/EpinelPS/Models/DbModels.cs b/EpinelPS/Models/DbModels.cs index 7dfdc01..11a39ce 100644 --- a/EpinelPS/Models/DbModels.cs +++ b/EpinelPS/Models/DbModels.cs @@ -440,4 +440,63 @@ namespace EpinelPS.Models public int CutSceneId { get; set; } public bool IsNew { get; set; } } + + // Solo Raid Data + public class SoloRaidInfo + { + public int RaidId { get; set; } + public int RaidOpenCount { get; set; } + public int TrialCount { get; set; } + public int LastDateDay { get; set; } + public List SoloRaidLevels { get; set; } = []; // key: raidLevel + } + public class SoloRaidLevelData + { + public int RaidLevel { get; set; } + public int RaidJoinCount { get; set; } + public long Hp { get; set; } + public long TotalDamage { get; set; } + public bool IsClear { get; set; } + public SoloRaidStatus Status { get; set; } + public SoloRaidType Type { get; set; } + public bool IsOpen { get; set; } + public List Logs { get; set; } = []; + } + public class SoloRaidLogData + { + public long Damage { get; set; } + public bool Kill { get; set; } + public List Team { get; set; } = []; + + public NetSoloRaidLog ToNet() + { + return new NetSoloRaidLog() + { + Damage = Damage, + Kill = Kill, + Team = { Team.Select(x => x.ToNet()).ToList() }, + }; + } + } + public class TeamCharacterData + { + public int Slot { get; set; } + public long Csn { get; set; } + public int Tid { get; set; } + public int Lv { get; set; } + public int Combat { get; set; } + public int CostumeId { get; set; } + + public NetSoloRaidTeamCharacter ToNet() + { + return new NetSoloRaidTeamCharacter() + { + Slot = Slot, + Tid = Tid, + Lv = Lv, + Combat = Combat, + CostumeId = CostumeId, + }; + } + } } \ No newline at end of file diff --git a/EpinelPS/Models/UserModel.cs b/EpinelPS/Models/UserModel.cs index a799c32..4066b1f 100644 --- a/EpinelPS/Models/UserModel.cs +++ b/EpinelPS/Models/UserModel.cs @@ -126,6 +126,8 @@ public class User public List LobbyPrivateBannerIds = []; public Dictionary MiniGameAzxInfo = []; + // solo raid data + public Dictionary SoloRaidData = []; // key: raidId public TriggerModel AddTrigger(Trigger type, int value, int conditionId = 0) {