From 91cbedf46ce115c969b320b3338f9d670f4feb79 Mon Sep 17 00:00:00 2001 From: qmengz <554318064@qq.com> Date: Sun, 7 Dec 2025 09:55:31 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20Implement=20AZX=20mini-game=20functiona?= =?UTF-8?q?lity=20and=20related=20event=20handling=20-=20=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=20AZX=20=E5=B0=8F=E6=B8=B8=E6=88=8F=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E5=8F=8A=E7=9B=B8=E5=85=B3=E4=BA=8B=E4=BB=B6=E5=A4=84=E7=90=86?= =?UTF-8?q?=20(#69)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added new classes for handling AZX mini-game events including entering, finishing, and retrieving data. - Introduced helper methods for managing rewards, rankings, and achievements within the AZX mini-game. - Updated existing event handling to include user-specific data for events and mini-games. - Enhanced user model to store AZX mini-game data and private banner IDs. - Modified event response structures to accommodate new data fields related to AZX mini-game. - Implemented logging for better traceability of AZX mini-game actions and errors. --- EpinelPS/Data/GameData.cs | 12 + EpinelPS/LobbyServer/Event/EventHelper.cs | 27 +- EpinelPS/LobbyServer/Event/GetJoinedEvent.cs | 7 +- EpinelPS/LobbyServer/Event/ListEvents.cs | 3 +- .../AZX/AcquireAzxAchievementMissionReward.cs | 29 + .../Event/Minigame/AZX/AzxHelper.cs | 568 ++++++++++++++++++ .../Event/Minigame/AZX/EnterAzx.cs | 32 + .../Event/Minigame/AZX/FinishAzx.cs | 22 + .../Event/Minigame/AZX/GetAzxData.cs | 43 ++ .../Event/Minigame/AZX/GetAzxRanking.cs | 23 + .../Event/Minigame/AZX/GetAzxRedDotData.cs | 28 + .../Event/Minigame/AZX/SetAzxBoardUnlocked.cs | 26 + .../Minigame/AZX/SetAzxCharacterUnlocked.cs | 26 + .../Minigame/AZX/SetAzxCutSceneConfirmed.cs | 26 + .../Event/Minigame/AZX/SetAzxSkillUnlocked.cs | 26 + .../Minigame/AZX/SetAzxTutorialConfirmed.cs | 22 + .../Pass/GetActiveEventPassData.cs | 2 +- EpinelPS/Models/DbModels.cs | 51 ++ EpinelPS/Models/UserModel.cs | 3 + 19 files changed, 964 insertions(+), 12 deletions(-) create mode 100644 EpinelPS/LobbyServer/Event/Minigame/AZX/AcquireAzxAchievementMissionReward.cs create mode 100644 EpinelPS/LobbyServer/Event/Minigame/AZX/AzxHelper.cs create mode 100644 EpinelPS/LobbyServer/Event/Minigame/AZX/EnterAzx.cs create mode 100644 EpinelPS/LobbyServer/Event/Minigame/AZX/FinishAzx.cs create mode 100644 EpinelPS/LobbyServer/Event/Minigame/AZX/GetAzxData.cs create mode 100644 EpinelPS/LobbyServer/Event/Minigame/AZX/GetAzxRanking.cs create mode 100644 EpinelPS/LobbyServer/Event/Minigame/AZX/GetAzxRedDotData.cs create mode 100644 EpinelPS/LobbyServer/Event/Minigame/AZX/SetAzxBoardUnlocked.cs create mode 100644 EpinelPS/LobbyServer/Event/Minigame/AZX/SetAzxCharacterUnlocked.cs create mode 100644 EpinelPS/LobbyServer/Event/Minigame/AZX/SetAzxCutSceneConfirmed.cs create mode 100644 EpinelPS/LobbyServer/Event/Minigame/AZX/SetAzxSkillUnlocked.cs create mode 100644 EpinelPS/LobbyServer/Event/Minigame/AZX/SetAzxTutorialConfirmed.cs diff --git a/EpinelPS/Data/GameData.cs b/EpinelPS/Data/GameData.cs index 29d4d6b..32dd861 100644 --- a/EpinelPS/Data/GameData.cs +++ b/EpinelPS/Data/GameData.cs @@ -322,6 +322,18 @@ namespace EpinelPS.Data [LoadRecord("SimulationRoomOcSeasonTable.json", "Id")] public readonly Dictionary SimulationRoomOcSeasonTable = []; + // MiniGame AZX Tables + [LoadRecord("EventAZXAppleGameMissionTable.json", "Id")] + public readonly Dictionary EventAZXAppleGameMissionTable = []; + [LoadRecord("EventAZXAppleGameBoardTable.json", "Id")] + public readonly Dictionary EventAZXAppleGameBoardTable = []; + [LoadRecord("EventAZXAppleGameCharacterTable.json", "Id")] + public readonly Dictionary EventAZXAppleGameCharacterTable = []; + [LoadRecord("EventAZXAppleGameSkillTable.json", "Id")] + public readonly Dictionary EventAZXAppleGameSkillTable = []; + [LoadRecord("EventAZXAppleGameCutSceneTable.json", "Id")] + public readonly Dictionary EventAZXAppleGameCutSceneTable = []; + static async Task BuildAsync() { await Load(); diff --git a/EpinelPS/LobbyServer/Event/EventHelper.cs b/EpinelPS/LobbyServer/Event/EventHelper.cs index 99cb236..cfd4ef8 100644 --- a/EpinelPS/LobbyServer/Event/EventHelper.cs +++ b/EpinelPS/LobbyServer/Event/EventHelper.cs @@ -9,9 +9,9 @@ namespace EpinelPS.LobbyServer.Event { private static readonly ILog log = LogManager.GetLogger(typeof(EventHelper)); - public static void AddEvents(ref ResGetEventList response) + public static void AddEvents(User user, ref ResGetEventList response) { - List lobbyPrivateBanners = GetLobbyPrivateBannerData(); + List lobbyPrivateBanners = GetLobbyPrivateBannerData(user); if (lobbyPrivateBanners.Count == 0) { // No active lobby private banners @@ -32,6 +32,11 @@ namespace EpinelPS.LobbyServer.Event List gachaEvents = GetEventDataBySystemTypes(banner, eventManagers, systemTypes); log.Debug($"Banner EventId: {banner.EventId} has {gachaEvents.Count} associated gacha events: {JsonConvert.SerializeObject(gachaEvents)}"); AddEvents(ref response, gachaEvents); + + // add challenge events + var challengeEvents = GetChallengeEventData(banner, eventManagers); + log.Debug($"Banner EventId: {banner.EventId} has {challengeEvents.Count} associated challenge events: {JsonConvert.SerializeObject(challengeEvents)}"); + AddEvents(ref response, challengeEvents); } // add daily mission events List dailyMissionEvents = GetDailyMissionEventData(eventManagers); @@ -39,9 +44,9 @@ namespace EpinelPS.LobbyServer.Event AddEvents(ref response, dailyMissionEvents); } - public static void AddJoinedEvents(ref ResGetJoinedEvent response) + public static void AddJoinedEvents(User user, ref ResGetJoinedEvent response) { - List lobbyPrivateBanners = GetLobbyPrivateBannerData(); + List lobbyPrivateBanners = GetLobbyPrivateBannerData(user); if (lobbyPrivateBanners.Count == 0) { // No active lobby private banners @@ -182,11 +187,19 @@ namespace EpinelPS.LobbyServer.Event /// Get active lobby private banner data /// /// List of active lobby private banners - public static List GetLobbyPrivateBannerData() + public static List GetLobbyPrivateBannerData(User user) { + var lobbyPrivateBannerIds = user.LobbyPrivateBannerIds; + var lobbyPrivateBannerRecords = GameData.Instance.LobbyPrivateBannerTable.Values; List lobbyPrivateBanners = []; - // lobbyPrivateBanners = [.. GameData.Instance.LobbyPrivateBannerTable.Values.Where(b => b.StartDate <= DateTime.UtcNow && b.EndDate >= DateTime.UtcNow)]; - lobbyPrivateBanners.Add(new LobbyPrivateBannerRecord() { Id = 10093, PrivateBannerShowDuration = 8, EventId = 82700 }); + if (lobbyPrivateBannerIds is not null && lobbyPrivateBannerIds.Count > 0) + { + lobbyPrivateBanners = [.. lobbyPrivateBannerRecords.Where(b => lobbyPrivateBannerIds.Contains(b.Id))]; + } + else + { + lobbyPrivateBanners.Add(lobbyPrivateBannerRecords.OrderBy(b => b.Id).Last()); + } Logging.WriteLine($"Found {lobbyPrivateBanners.Count} active lobby private banners.", LogType.Debug); log.Debug($"Active lobby private banners: {JsonConvert.SerializeObject(lobbyPrivateBanners)}"); return lobbyPrivateBanners; diff --git a/EpinelPS/LobbyServer/Event/GetJoinedEvent.cs b/EpinelPS/LobbyServer/Event/GetJoinedEvent.cs index 6d6eba6..ed57fb0 100644 --- a/EpinelPS/LobbyServer/Event/GetJoinedEvent.cs +++ b/EpinelPS/LobbyServer/Event/GetJoinedEvent.cs @@ -10,10 +10,11 @@ namespace EpinelPS.LobbyServer.Event await ReadData(); //types are defined in EventTypes.cs ResGetJoinedEvent response = new(); + User user = GetUser(); + + // add gacha events from active lobby banners + EventHelper.AddJoinedEvents(user, ref response); - // add gacha events from active lobby banners - EventHelper.AddJoinedEvents(ref response); - await WriteDataAsync(response); } } diff --git a/EpinelPS/LobbyServer/Event/ListEvents.cs b/EpinelPS/LobbyServer/Event/ListEvents.cs index a766ded..5b24c67 100644 --- a/EpinelPS/LobbyServer/Event/ListEvents.cs +++ b/EpinelPS/LobbyServer/Event/ListEvents.cs @@ -11,9 +11,10 @@ namespace EpinelPS.LobbyServer.Event // types are defined in EventTypes.cs ResGetEventList response = new(); + User user = GetUser(); // add events from active lobby banners - EventHelper.AddEvents(ref response); + EventHelper.AddEvents(user, ref response); await WriteDataAsync(response); } diff --git a/EpinelPS/LobbyServer/Event/Minigame/AZX/AcquireAzxAchievementMissionReward.cs b/EpinelPS/LobbyServer/Event/Minigame/AZX/AcquireAzxAchievementMissionReward.cs new file mode 100644 index 0000000..1bdbedd --- /dev/null +++ b/EpinelPS/LobbyServer/Event/Minigame/AZX/AcquireAzxAchievementMissionReward.cs @@ -0,0 +1,29 @@ +using EpinelPS.Utils; + +namespace EpinelPS.LobbyServer.Event.Minigame.AZX +{ + [PacketPath("/event/minigame/azx/acquire/achievementmission/reward")] + public class AcquireAzxAchievementMissionReward : LobbyMsgHandler + { + protected override async Task HandleAsync() + { + // ReqAcquireMiniGameAzxAchievementMissionReward Fields + // int AzxId + // RepeatedField AchievementMissionIdList + ReqAcquireMiniGameAzxAchievementMissionReward req = await ReadData(); + User user = GetUser(); + + // ResAcquireMiniGameAzxAchievementMissionReward Fields + // NetRewardData Reward + ResAcquireMiniGameAzxAchievementMissionReward response = new(); + + NetRewardData reward = new(); + AzxHelper.AcquireReward(user, ref reward, req.AzxId, req.AchievementMissionIdList); + + response.Reward = reward; + + + await WriteDataAsync(response); + } + } +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Event/Minigame/AZX/AzxHelper.cs b/EpinelPS/LobbyServer/Event/Minigame/AZX/AzxHelper.cs new file mode 100644 index 0000000..d6a9543 --- /dev/null +++ b/EpinelPS/LobbyServer/Event/Minigame/AZX/AzxHelper.cs @@ -0,0 +1,568 @@ +using EpinelPS.Data; +using EpinelPS.Database; +using EpinelPS.Utils; +using Google.Protobuf.Collections; +using Google.Protobuf.WellKnownTypes; +using log4net; +using Newtonsoft.Json; + +namespace EpinelPS.LobbyServer.Event.Minigame.AZX +{ + public static class AzxHelper + { + private static readonly ILog log = LogManager.GetLogger(typeof(AzxHelper)); + + public static void AcquireReward(User user, ref NetRewardData reward, int azxId, RepeatedField missionIds) + { + log.Debug($"Acquiring reward for user {user.ID}, azxId: {azxId}, missionIds: {JsonConvert.SerializeObject(missionIds)}"); + if (missionIds.Count == 0 || azxId == 0) return; + try + { + var missions = GameData.Instance.EventAZXAppleGameMissionTable.Values.Where(x => + x.MissionRewardId > 0 && missionIds.Contains(x.Id)).ToList(); + if (missions.Count == 0) return; + List rewardDatas = []; + foreach (var mission in missions) + { + var rewardRecord = GameData.Instance.GetRewardTableEntry(mission.MissionRewardId); + if (rewardRecord is null || rewardRecord.Rewards.Count == 0) continue; + foreach (var rewardItem in rewardRecord.Rewards) + { + if (rewardItem.RewardValue == 0) continue; + int itemIndex = rewardDatas.FindIndex(x => x.RewardId == rewardItem.RewardId); + if (itemIndex >= 0) + rewardDatas[itemIndex].RewardValue += rewardItem.RewardValue; + else + rewardDatas.Add(rewardItem); + } + } + foreach (var rewardData in rewardDatas) + { + RewardUtils.AddSingleObject(user, ref reward, rewardData.RewardId, rewardData.RewardType, rewardData.RewardValue); + } + + var azxInfo = GetAzxInfo(user, azxId); + azxInfo.AchievementMissionDataList.AddRange(missionIds.Select(x => + new AchievementMissionData() { MissionId = x, IsReceived = true })); + + user.MiniGameAzxInfo[azxId] = azxInfo; + JsonDb.Save(); + } + catch (Exception ex) + { + Logging.WriteLine($"Acquiring reward failed: {ex.Message}", LogType.Error); + } + } + + public static void GetRanking(User user, int azxId, ref ResGetMiniGameAzxRanking response) + { + // ResGetMiniGameAzxRanking Fields + // NetMiniGameAzxRankingData UserGuildRanking + // RepeatedField GuildRankingList + // NetMiniGameAzxRankingData Fields + // int Rank + // NetMiniGameAzxScoreAndTime ScoreAndTime + // NetWholeUserData User + // NetMiniGameAzxScoreAndTime Fields + // int Score + // Google.Protobuf.WellKnownTypes.Duration TimeToScore + + int dateDay = GetDateDay(); + var azxInfo = GetAzxInfo(user, azxId); + var scoreData = azxInfo.ScoreDatas.Find(x => x.DateDay == dateDay && x.AzxId == azxId); + int score = scoreData?.HighScore ?? 0; + Duration timeToScore = scoreData?.HighScoreTime ?? new Duration() { Seconds = 0, Nanos = 0 }; + + response.UserGuildRanking = new NetMiniGameAzxRankingData() + { + Rank = 1, + ScoreAndTime = new NetMiniGameAzxScoreAndTime() + { + Score = score, + TimeToScore = timeToScore + }, + User = new NetWholeUserData() + { + Usn = (long)user.ID, + Server = 10001, + Nickname = user.Nickname, + Lv = user.userPointData?.UserLevel ?? 99, + Icon = user.ProfileIconId, + IconPrism = user.ProfileIconIsPrism, + Frame = user.ProfileFrame, + LastActionAt = user.LastLogin.Ticks, + UserTitleId = user.TitleId, + GuildName = user.Nickname, + } + }; + response.GuildRankingList.Add(new NetMiniGameAzxRankingData() + { + Rank = 2, + ScoreAndTime = new NetMiniGameAzxScoreAndTime() + { + Score = 80000, + TimeToScore = new Duration() { Seconds = 118, Nanos = 432877000 } + }, + User = new NetWholeUserData() + { + Usn = (long)user.ID, + Server = 10001, + Nickname = user.Nickname, + Lv = user.userPointData?.UserLevel ?? 99, + Icon = user.ProfileIconId, + IconPrism = user.ProfileIconIsPrism, + Frame = user.ProfileFrame, + LastActionAt = user.LastLogin.Ticks, + UserTitleId = user.TitleId, + GuildName = user.Nickname, + } + }); + + JsonDb.Save(); + } + + public static void FinishAzx(User user, ReqFinishMiniGameAzx req, ref ResFinishMiniGameAzx response) + { + // ReqEnterMiniGameAzx Fields + // int AzxId + // NetMiniGameAzxScoreAndTime ScoreAndTime + // int PlayBoardId + // int PlayCharacterId + // RepeatedField SkillUseCountList + // int CutSceneId + + // ResFinishMiniGameAzx Fields + // NetRewardData Reward + // NetMiniGameAzxDailyMissionData DailyMissionData + // bool IsNewHighScore + // bool IsNewHighScoreTime + try + { + log.Debug($"Finishing AZX for user {user.ID}, data: {JsonConvert.SerializeObject(req)}"); + int dateDay = GetDateDay(); + + int score = req.ScoreAndTime.Score; + Duration timeToScore = req.ScoreAndTime.TimeToScore; + + NetMiniGameAzxDailyMissionData dailyMissionData = new() { IsDailyRewarded = false, DailyAccumulatedScore = 0 }; + NetRewardData reward = new(); + + var azxInfo = GetAzxInfo(user, req.AzxId); + + if (req.CutSceneId > 0 && azxInfo.CutSceneDataList.Find(x => x.CutSceneId == req.CutSceneId) is null) + { + azxInfo.CutSceneDataList.Add(new CutSceneData() { CutSceneId = req.CutSceneId, IsNew = true }); + } + + var scoreDataIndex = azxInfo.ScoreDatas.FindIndex(x => x.DateDay == dateDay && x.AzxId == req.AzxId); + if (scoreDataIndex >= 0) + { + if (score > 10000 && !azxInfo.ScoreDatas[scoreDataIndex].IsDailyRewarded) + { + azxInfo.ScoreDatas[scoreDataIndex].IsDailyRewarded = true; + dailyMissionData.IsDailyRewarded = true; + RewardUtils.AddSingleCurrencyObject(user, ref reward, CurrencyType.FreeCash, 30); + } + azxInfo.ScoreDatas[scoreDataIndex].AccumulatedScore += score; + dailyMissionData.DailyAccumulatedScore = azxInfo.ScoreDatas[scoreDataIndex].AccumulatedScore; + if (azxInfo.ScoreDatas[scoreDataIndex].HighScore < score) + { + response.IsNewHighScore = true; + azxInfo.ScoreDatas[scoreDataIndex].HighScore = score; + } + if (azxInfo.ScoreDatas[scoreDataIndex].HighScoreTime.ToTimeSpan() > timeToScore.ToTimeSpan()) + { + response.IsNewHighScoreTime = true; + azxInfo.ScoreDatas[scoreDataIndex].HighScoreTime = timeToScore; + } + } + else + { + bool isDailyRewarded = false; + if (score > 10000) + { + isDailyRewarded = true; + dailyMissionData.IsDailyRewarded = true; + RewardUtils.AddSingleCurrencyObject(user, ref reward, CurrencyType.FreeCash, 30); + } + response.IsNewHighScoreTime = true; + response.IsNewHighScore = true; + azxInfo.ScoreDatas.Add(new MiniGameAzxScoreData + { + AzxId = req.AzxId, + DateDay = dateDay, + AccumulatedScore = score, + HighScore = score, + HighScoreTime = timeToScore, + IsDailyRewarded = isDailyRewarded + }); + dailyMissionData.DailyAccumulatedScore = score; + } + + // int PlayBoardId + // int PlayCharacterId + azxInfo.CharacterCount ??= []; + if (azxInfo.CharacterCount.TryGetValue(req.PlayCharacterId, out var characterCount)) + azxInfo.CharacterCount[req.PlayCharacterId] = characterCount + 1; + else + azxInfo.CharacterCount.Add(req.PlayCharacterId, 1); + // RepeatedField SkillUseCountList + if (req.SkillUseCountList != null && req.SkillUseCountList.Count > 0) + { + azxInfo.SkillCount ??= []; + foreach (var item in req.SkillUseCountList) + { + if (azxInfo.SkillCount.TryGetValue(item.SkillId, out var skillCount)) + azxInfo.SkillCount[item.SkillId] = skillCount + item.SkillUseCount; + else + azxInfo.SkillCount.Add(item.SkillId, item.SkillUseCount); + } + } + + response.DailyMissionData = dailyMissionData; + response.Reward = reward; + // Save changes + user.MiniGameAzxInfo[req.AzxId] = azxInfo; + JsonDb.Save(); + } + catch (Exception ex) + { + Logging.WriteLine($"Finish AZX Error: {ex.Message}", LogType.Error); + } + } + + public static void EnterAzx(User user, ref ResEnterMiniGameAzx response, int azxId, int playBoardId, int playCharacterId) + { + log.Debug($"Entering AZX AzxId: {azxId}, PlayBoardId: {playBoardId}, PlayCharacterId: {playCharacterId}"); + try + { + var azxInfo = GetAzxInfo(user, azxId); + azxInfo.SelectedBoardId = playBoardId; + azxInfo.SelectedCharacterId = playCharacterId; + user.MiniGameAzxInfo[azxId] = azxInfo; + response.PreviousSRankCount = 0; + } + catch (Exception ex) + { + Logging.WriteLine($"Enter AZX Error: {ex.Message}", LogType.Error); + } + } + + public static void GetAzxData(User user, int azxId, ref ResGetMiniGameAzxData response) + { + log.Debug($"Getting AZX data for user {user.ID}"); + + var azxInfo = GetAzxInfo(user, azxId); + // Initialize score data if null + azxInfo.ScoreDatas ??= []; + // Get sum score + int sumScore = azxInfo.ScoreDatas.Sum(x => x.AccumulatedScore); + + log.Debug($"AZX data: {JsonConvert.SerializeObject(azxInfo)}"); + try + { + // AchievementMissionDataList + var missions = GameData.Instance.EventAZXAppleGameMissionTable.Values.ToList(); + + foreach (var mission in missions) + { + bool isReceived = false; + azxInfo.AchievementMissionDataList ??= []; + var item = azxInfo.AchievementMissionDataList.Find(x => x.MissionId == mission.Id); + if (item is not null) isReceived = item.IsReceived; + if (mission.MissionType == EventAZXAppleGameMissionMissionType.GetScore) + { + AddAchievementMission(ref response, mission.Id, sumScore, isReceived); + } + else if (mission.MissionType == EventAZXAppleGameMissionMissionType.UseSkillCount) + { + int progress = 0; + if (azxInfo.SkillCount.TryGetValue(mission.MissionConditionId, out var skillCount)) progress = skillCount; + AddAchievementMission(ref response, mission.Id, progress, isReceived); + } + else if (mission.MissionType == EventAZXAppleGameMissionMissionType.PlayCharacterCount) + { + int progress = 0; + if (azxInfo.CharacterCount.TryGetValue(mission.MissionConditionId, out var characterCount)) progress = characterCount; + AddAchievementMission(ref response, mission.Id, progress, isReceived); + } + else + { + AddAchievementMission(ref response, mission.Id, 0, isReceived); + } + } + } + catch (Exception ex) + { + log.Error($"Get AchievementMissionDataList Error: {ex.Message}"); + } + + try + { + // ConditionalBoardDataList + azxInfo.ConditionalBoardDataList ??= []; + var boards = GameData.Instance.EventAZXAppleGameBoardTable.Values.ToList(); + foreach (var board in boards) + { + var item = azxInfo.ConditionalBoardDataList.Find(x => x.BoardId == board.Id); + bool isUnlocked = item is not null && item.IsUnlocked; + if (board.BoardOpenScore == 0) continue; + response.ConditionalBoardDataList.Add(new NetMiniGameAzxConditionalBoardData() + { + BoardId = board.Id, + Progress = sumScore, + IsUnlocked = isUnlocked + }); + } + // SelectedBoardId + int selectedBoardId = azxInfo.SelectedBoardId > 0 ? azxInfo.SelectedBoardId : boards.Min(x => x.Id); + response.SelectedBoardId = selectedBoardId; + } + catch (Exception ex) + { + log.Error($"Get ConditionalBoardDataList Error: {ex.Message}"); + } + + try + { + // ConditionalCharacterDataList + azxInfo.ConditionalCharacterDataList ??= []; + var characters = GameData.Instance.EventAZXAppleGameCharacterTable.Values.ToList(); + foreach (var character in characters) + { + var item = azxInfo.ConditionalCharacterDataList.Find(x => x.CharacterId == character.Id); + bool isUnlocked = item is not null && item.IsUnlocked; + if (character.CharacterOpenScore == 0) continue; + response.ConditionalCharacterDataList.Add(new NetMiniGameAzxConditionalCharacterData() + { + CharacterId = character.Id, + Progress = sumScore, + IsUnlocked = isUnlocked + }); + } + // SelectedCharacterId + int selectedCharacterId = azxInfo.SelectedCharacterId > 0 ? azxInfo.SelectedCharacterId : characters.Min(x => x.Id); + response.SelectedCharacterId = selectedCharacterId; + } + catch (Exception ex) + { + log.Error($"Get ConditionalCharacterDataList Error: {ex.Message}"); + } + + try + { + // ConditionalSkillDataList + azxInfo.ConditionalSkillDataList ??= []; + foreach (var skill in GameData.Instance.EventAZXAppleGameSkillTable.Values) + { + var item = azxInfo.ConditionalSkillDataList.Find(x => x.SkillId == skill.Id); + bool isUnlocked = item is not null && item.IsUnlocked; + if (skill.SkillOpenUseValue == 0) continue; + response.ConditionalSkillDataList.Add(new NetMiniGameAzxConditionalSkillData() + { + SkillId = skill.Id, + IsUnlocked = isUnlocked + }); + } + } + catch (Exception ex) + { + log.Error($"Get ConditionalSkillDataList Error: {ex.Message}"); + } + + try + { + azxInfo.CutSceneDataList ??= []; + foreach (var cutScene in GameData.Instance.EventAZXAppleGameCutSceneTable.Values) + { + var item = azxInfo.CutSceneDataList.Find(x => x.CutSceneId == cutScene.Id); + if (item is not null && item.CutSceneId > 0) + { + response.CutSceneList.Add(new NetMiniGameAzxCutSceneData { CutSceneId = cutScene.Id, IsNew = item.IsNew }); + } + } + } + catch (Exception ex) + { + Logging.WriteLine($"Get CutSceneList Error: {ex.Message}", LogType.Error); + } + + try + { + // NetMiniGameAzxDailyMissionData DailyMissionData + azxInfo.ScoreDatas ??= []; + int dateDay = GetDateDay(); + var scoreData = azxInfo.ScoreDatas.Find(x => x.DateDay == dateDay && x.AzxId == azxId); + if (scoreData is not null) + { + response.DailyMissionData = new NetMiniGameAzxDailyMissionData() + { + DailyAccumulatedScore = scoreData.AccumulatedScore, + IsDailyRewarded = scoreData.IsDailyRewarded, + }; + } + } + catch (Exception ex) + { + Logging.WriteLine($"Get DailyMissionData Error: {ex.Message}", LogType.Error); + } + + response.IsTutorialConfirmed = azxInfo.IsTutorialConfirmed; + } + + public static void SetTutorialConfirmed(User user, int azxId) + { + log.Debug($"Setting tutorial confirmed for AZX {azxId}"); + try + { + var azxInfo = GetAzxInfo(user, azxId); + azxInfo.IsTutorialConfirmed = true; + user.MiniGameAzxInfo[azxId] = azxInfo; + log.Debug($"Tutorial data after: {azxInfo.IsTutorialConfirmed}"); + } + catch (Exception ex) + { + Logging.WriteLine($"Setting AZX tutorial confirmed failed: {ex.Message}", LogType.Error); + } + } + + public static void SetBoardUnlocked(User user, int azxId, int boardId) + { + log.Debug($"Setting board {boardId} unlocked for AZX {azxId}"); + try + { + + var azxInfo = GetAzxInfo(user, azxId); + azxInfo.ConditionalBoardDataList ??= []; + log.Debug($"Board data before: {JsonConvert.SerializeObject(azxInfo.ConditionalBoardDataList)}"); + var itemIndex = azxInfo.ConditionalBoardDataList.FindIndex(x => x.BoardId == boardId); + if (itemIndex >= 0) + { + azxInfo.ConditionalBoardDataList[itemIndex].IsUnlocked = true; + } + else + { + azxInfo.ConditionalBoardDataList.Add(new ConditionalBoardData() { BoardId = boardId, IsUnlocked = true }); + } + user.MiniGameAzxInfo[azxId] = azxInfo; + log.Debug($"Board data after: {JsonConvert.SerializeObject(azxInfo.ConditionalBoardDataList)}"); + } + catch (Exception ex) + { + Logging.WriteLine($"Setting AZX board unlocked failed: {ex.Message}", LogType.Error); + } + } + + public static void SetSkillUnlocked(User user, int azxId, int skillId) + { + log.Debug($"Setting skill {skillId} unlocked for AZX {azxId}"); + try + { + var azxInfo = GetAzxInfo(user, azxId); + azxInfo.ConditionalSkillDataList ??= []; + log.Debug($"Skill data before: {JsonConvert.SerializeObject(azxInfo.ConditionalSkillDataList)}"); + var itemIndex = azxInfo.ConditionalSkillDataList.FindIndex(x => x.SkillId == skillId); + if (itemIndex >= 0) + { + azxInfo.ConditionalSkillDataList[itemIndex].IsUnlocked = true; + } + else + { + azxInfo.ConditionalSkillDataList.Add(new ConditionalSkillData() { SkillId = skillId, IsUnlocked = true }); + } + user.MiniGameAzxInfo[azxId] = azxInfo; + log.Debug($"Skill data after: {JsonConvert.SerializeObject(azxInfo.ConditionalSkillDataList)}"); + } + catch (Exception ex) + { + Logging.WriteLine($"Setting AZX skill unlocked failed: {ex.Message}", LogType.Error); + } + } + + public static void SetCharacterUnlocked(User user, int azxId, int characterId) + { + log.Debug($"Setting character {characterId} unlocked for AZX {azxId}"); + try + { + var azxInfo = GetAzxInfo(user, azxId); + azxInfo.ConditionalCharacterDataList ??= []; + log.Debug($"Character data before: {JsonConvert.SerializeObject(azxInfo.ConditionalCharacterDataList)}"); + var itemIndex = azxInfo.ConditionalCharacterDataList.FindIndex(x => x.CharacterId == characterId); + if (itemIndex >= 0) + { + azxInfo.ConditionalCharacterDataList[itemIndex].IsUnlocked = true; + } + else + { + azxInfo.ConditionalCharacterDataList.Add(new ConditionalCharacterData() { CharacterId = characterId, IsUnlocked = true }); + } + user.MiniGameAzxInfo[azxId] = azxInfo; + log.Debug($"Character data after: {JsonConvert.SerializeObject(azxInfo.ConditionalCharacterDataList)}"); + } + catch (Exception ex) + { + Logging.WriteLine($"Setting AZX character unlocked failed: {ex.Message}", LogType.Error); + } + } + + public static void SetCutSceneConfirmed(User user, int azxId, List cutSceneIdList) + { + log.Debug($"Setting cutscenes confirmed for AZX {azxId}: {string.Join(", ", cutSceneIdList)}"); + try + { + var azxInfo = GetAzxInfo(user, azxId); + azxInfo.CutSceneDataList ??= []; + log.Debug($"Cutscene data before: {JsonConvert.SerializeObject(azxInfo.CutSceneDataList)}"); + foreach (var item in cutSceneIdList) + { + var itemIndex = azxInfo.CutSceneDataList.FindIndex(x => x.CutSceneId == item); + if (itemIndex >= 0) + { + azxInfo.CutSceneDataList[itemIndex].IsNew = false; + } + else + { + azxInfo.CutSceneDataList.Add(new CutSceneData() { CutSceneId = item, IsNew = false }); + } + } + user.MiniGameAzxInfo[azxId] = azxInfo; + log.Debug($"Cutscene data after: {JsonConvert.SerializeObject(azxInfo.CutSceneDataList)}"); + } + catch (Exception ex) + { + Logging.WriteLine($"Setting AZX cutscene confirmed failed: {ex.Message}", LogType.Error); + } + } + + private static int GetDateDay() + { + // +4 每天4点重新计算 + DateTime dateTime = DateTime.UtcNow.AddHours(4); + return dateTime.Year * 10000 + dateTime.Month * 100 + dateTime.Day; + } + + private static void AddAchievementMission(ref ResGetMiniGameAzxData response, int missionId, int progress, bool isReceived) + { + response.AchievementMissionDataList.Add(new NetMiniGameAzxAchievementMissionData() + { + MissionId = missionId, + Progress = progress, + IsReceived = isReceived + }); + } + + public static MiniGameAzxData GetAzxInfo(User user, int azxId) + { + if (!user.MiniGameAzxInfo.TryGetValue(azxId, out var azxInfo)) + { + log.Debug($"Creating new AZX info for {azxId}"); + user.MiniGameAzxInfo.TryAdd(azxId, new MiniGameAzxData() { }); + azxInfo = new MiniGameAzxData() { }; + } + log.Debug($"Getting AZX info for {azxId}, data: {JsonConvert.SerializeObject(azxInfo)}"); + return azxInfo; + } + + + } +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Event/Minigame/AZX/EnterAzx.cs b/EpinelPS/LobbyServer/Event/Minigame/AZX/EnterAzx.cs new file mode 100644 index 0000000..49c06f8 --- /dev/null +++ b/EpinelPS/LobbyServer/Event/Minigame/AZX/EnterAzx.cs @@ -0,0 +1,32 @@ +using EpinelPS.Database; +using EpinelPS.Utils; + +namespace EpinelPS.LobbyServer.Event.Minigame.AZX +{ + [PacketPath("/event/minigame/azx/enter")] + public class EnterAzx : LobbyMsgHandler + { + protected override async Task HandleAsync() + { + // ReqEnterMiniGameAzx Fields + // int AzxId + // int PlayBoardId + // int PlayCharacterId + ReqEnterMiniGameAzx req = await ReadData(); + User user = GetUser(); + + // ResEnterMiniGameAzx Fields + // int PreviousSRankCount + ResEnterMiniGameAzx response = new() + { + PreviousSRankCount = 0 + }; + + if (req.AzxId > 0 && req.PlayBoardId > 0 && req.PlayCharacterId > 0) + AzxHelper.EnterAzx(user, ref response, req.AzxId, req.PlayBoardId, req.PlayCharacterId); + + JsonDb.Save(); + await WriteDataAsync(response); + } + } +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Event/Minigame/AZX/FinishAzx.cs b/EpinelPS/LobbyServer/Event/Minigame/AZX/FinishAzx.cs new file mode 100644 index 0000000..d319acf --- /dev/null +++ b/EpinelPS/LobbyServer/Event/Minigame/AZX/FinishAzx.cs @@ -0,0 +1,22 @@ +using EpinelPS.Utils; + +namespace EpinelPS.LobbyServer.Event.Minigame.AZX +{ + [PacketPath("/event/minigame/azx/finish")] + public class FinishAzx : LobbyMsgHandler + { + protected override async Task HandleAsync() + { + // { "azxId": 1, "scoreAndTime": { "score": 50000, "timeToScore": "110.122697s" }, + // "playBoardId": 101, "playCharacterId": 101, "skillUseCountList": [ { "skillId": 102 } ], "cutSceneId": 10101 } + ReqFinishMiniGameAzx req = await ReadData(); + User user = GetUser(); + + ResFinishMiniGameAzx response = new(); + + AzxHelper.FinishAzx(user, req, ref response); + + await WriteDataAsync(response); + } + } +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Event/Minigame/AZX/GetAzxData.cs b/EpinelPS/LobbyServer/Event/Minigame/AZX/GetAzxData.cs new file mode 100644 index 0000000..30d2eb9 --- /dev/null +++ b/EpinelPS/LobbyServer/Event/Minigame/AZX/GetAzxData.cs @@ -0,0 +1,43 @@ +using EpinelPS.Data; +using EpinelPS.Utils; +using log4net; + +namespace EpinelPS.LobbyServer.Event.Minigame.AZX +{ + [PacketPath("/event/minigame/azx/get/data")] + public class GetAzxData : LobbyMsgHandler + { + private static readonly ILog log = LogManager.GetLogger(typeof(LobbyMsgHandler)); + protected override async Task HandleAsync() + { + // int AzxId + ReqGetMiniGameAzxData req = await ReadData(); + User user = GetUser(); + + // ResGetMiniGameAzxData Fields + // NetMiniGameAzxDailyMissionData DailyMissionData + // RepeatedField AchievementMissionDataList + // RepeatedField CutSceneList + // int SelectedBoardId + // int SelectedCharacterId + // RepeatedField ConditionalBoardDataList + // RepeatedField ConditionalCharacterDataList + // RepeatedField ConditionalSkillDataList + // bool IsTutorialConfirmed + ResGetMiniGameAzxData response = new() + { + DailyMissionData = new NetMiniGameAzxDailyMissionData() + { + DailyAccumulatedScore = 0, + IsDailyRewarded = false, + }, + }; + + // TODO: Add implementation for AchievementMissionDataList, CutSceneList, etc. + AzxHelper.GetAzxData(user, req.AzxId, ref response); + + + await WriteDataAsync(response); + } + } +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Event/Minigame/AZX/GetAzxRanking.cs b/EpinelPS/LobbyServer/Event/Minigame/AZX/GetAzxRanking.cs new file mode 100644 index 0000000..5ddbd46 --- /dev/null +++ b/EpinelPS/LobbyServer/Event/Minigame/AZX/GetAzxRanking.cs @@ -0,0 +1,23 @@ +using EpinelPS.Utils; + +namespace EpinelPS.LobbyServer.Event.Minigame.AZX +{ + [PacketPath("/event/minigame/azx/get/ranking")] + public class GetAzxRanking : LobbyMsgHandler + { + protected override async Task HandleAsync() + { + // int AzxId + ReqGetMiniGameAzxRanking req = await ReadData(); + User user = GetUser(); + + ResGetMiniGameAzxRanking response = new(); + + if (req.AzxId > 0) + AzxHelper.GetRanking(user, req.AzxId, ref response); + + + await WriteDataAsync(response); + } + } +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Event/Minigame/AZX/GetAzxRedDotData.cs b/EpinelPS/LobbyServer/Event/Minigame/AZX/GetAzxRedDotData.cs new file mode 100644 index 0000000..4d48f6d --- /dev/null +++ b/EpinelPS/LobbyServer/Event/Minigame/AZX/GetAzxRedDotData.cs @@ -0,0 +1,28 @@ +using EpinelPS.Utils; + +namespace EpinelPS.LobbyServer.Event.Minigame.AZX +{ + [PacketPath("/event/minigame/azx/get/reddot/data")] + public class GetAzxRedDotData : LobbyMsgHandler + { + protected override async Task HandleAsync() + { + // ReqGetMiniGameAzxRedDotData Fields + // int AzxId + ReqGetMiniGameAzxRedDotData req = await ReadData(); + User user = GetUser(); + + // ResGetMiniGameAzxRedDotData Fields + // bool IsDailyMissionAvailable + // bool AchievementMissionRewardExists + ResGetMiniGameAzxRedDotData response = new() + { + IsDailyMissionAvailable = true, + AchievementMissionRewardExists = true + }; + + + await WriteDataAsync(response); + } + } +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Event/Minigame/AZX/SetAzxBoardUnlocked.cs b/EpinelPS/LobbyServer/Event/Minigame/AZX/SetAzxBoardUnlocked.cs new file mode 100644 index 0000000..286dc54 --- /dev/null +++ b/EpinelPS/LobbyServer/Event/Minigame/AZX/SetAzxBoardUnlocked.cs @@ -0,0 +1,26 @@ +using EpinelPS.Database; +using EpinelPS.Utils; + +namespace EpinelPS.LobbyServer.Event.Minigame.AZX +{ + [PacketPath("/event/minigame/azx/set/board/unlocked")] + public class SetAzxBoardUnlocked : LobbyMsgHandler + { + protected override async Task HandleAsync() + { + // ReqSetMiniGameAzxBoardUnlocked Fields + // int AzxId + // int BoardId + ReqSetMiniGameAzxBoardUnlocked req = await ReadData(); + User user = GetUser(); + + ResSetMiniGameAzxBoardUnlocked response = new(); + + if (req.BoardId > 0 && req.AzxId > 0) + AzxHelper.SetBoardUnlocked(user, req.AzxId, req.BoardId); + + JsonDb.Save(); + await WriteDataAsync(response); + } + } +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Event/Minigame/AZX/SetAzxCharacterUnlocked.cs b/EpinelPS/LobbyServer/Event/Minigame/AZX/SetAzxCharacterUnlocked.cs new file mode 100644 index 0000000..57b941f --- /dev/null +++ b/EpinelPS/LobbyServer/Event/Minigame/AZX/SetAzxCharacterUnlocked.cs @@ -0,0 +1,26 @@ +using EpinelPS.Database; +using EpinelPS.Utils; + +namespace EpinelPS.LobbyServer.Event.Minigame.AZX +{ + [PacketPath("/event/minigame/azx/set/character/unlocked")] + public class SetAzxCharacterUnlocked : LobbyMsgHandler + { + protected override async Task HandleAsync() + { + // ReqSetMiniGameAzxCharacterUnlocked Fields + // int AzxId + // int CharacterId + ReqSetMiniGameAzxCharacterUnlocked req = await ReadData(); + User user = GetUser(); + + ResSetMiniGameAzxCharacterUnlocked response = new(); + + if (req.CharacterId > 0 && req.AzxId > 0) + AzxHelper.SetCharacterUnlocked(user, req.AzxId, req.CharacterId); + + JsonDb.Save(); + await WriteDataAsync(response); + } + } +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Event/Minigame/AZX/SetAzxCutSceneConfirmed.cs b/EpinelPS/LobbyServer/Event/Minigame/AZX/SetAzxCutSceneConfirmed.cs new file mode 100644 index 0000000..acce57b --- /dev/null +++ b/EpinelPS/LobbyServer/Event/Minigame/AZX/SetAzxCutSceneConfirmed.cs @@ -0,0 +1,26 @@ +using EpinelPS.Database; +using EpinelPS.Utils; + +namespace EpinelPS.LobbyServer.Event.Minigame.AZX +{ + [PacketPath("/event/minigame/azx/set/cutscene/confirmed")] + public class SetAzxCutSceneConfirmed : LobbyMsgHandler + { + protected override async Task HandleAsync() + { + // ReqEnterMiniGameAzx Fields + // int AzxId + // RepeatedField ConfirmedCutSceneIdList + ReqSetMiniGameAzxCutSceneConfirmed req = await ReadData(); + User user = GetUser(); + + ResSetMiniGameAzxCutSceneConfirmed response = new(); + + if (req.AzxId > 0 && req.ConfirmedCutSceneIdList.Count > 0) + AzxHelper.SetCutSceneConfirmed(user, req.AzxId, [.. req.ConfirmedCutSceneIdList]); + + JsonDb.Save(); + await WriteDataAsync(response); + } + } +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Event/Minigame/AZX/SetAzxSkillUnlocked.cs b/EpinelPS/LobbyServer/Event/Minigame/AZX/SetAzxSkillUnlocked.cs new file mode 100644 index 0000000..fcae778 --- /dev/null +++ b/EpinelPS/LobbyServer/Event/Minigame/AZX/SetAzxSkillUnlocked.cs @@ -0,0 +1,26 @@ +using EpinelPS.Database; +using EpinelPS.Utils; + +namespace EpinelPS.LobbyServer.Event.Minigame.AZX +{ + [PacketPath("/event/minigame/azx/set/skill/unlocked")] + public class SetAzxSkillUnlocked : LobbyMsgHandler + { + protected override async Task HandleAsync() + { + // ReqSetMiniGameAzxSkillUnlocked Fields + // int AzxId + // int SkillId + ReqSetMiniGameAzxSkillUnlocked req = await ReadData(); + User user = GetUser(); + + ResSetMiniGameAzxSkillUnlocked response = new(); + + if (req.SkillId > 0 && req.AzxId > 0) + AzxHelper.SetSkillUnlocked(user, req.AzxId, req.SkillId); + + JsonDb.Save(); + await WriteDataAsync(response); + } + } +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Event/Minigame/AZX/SetAzxTutorialConfirmed.cs b/EpinelPS/LobbyServer/Event/Minigame/AZX/SetAzxTutorialConfirmed.cs new file mode 100644 index 0000000..5ddf06f --- /dev/null +++ b/EpinelPS/LobbyServer/Event/Minigame/AZX/SetAzxTutorialConfirmed.cs @@ -0,0 +1,22 @@ +using EpinelPS.Database; +using EpinelPS.Utils; + +namespace EpinelPS.LobbyServer.Event.Minigame.AZX +{ + [PacketPath("/event/minigame/azx/set/tutorial/confirmed")] + public class SetAzxTutorialConfirmed : LobbyMsgHandler + { + protected override async Task HandleAsync() + { + await ReadData(); + User user = GetUser(); + + ResSetMiniGameAzxTutorialConfirmed response = new(); + + AzxHelper.SetTutorialConfirmed(user, 1); + + JsonDb.Save(); + await WriteDataAsync(response); + } + } +} \ No newline at end of file diff --git a/EpinelPS/LobbyServer/Pass/GetActiveEventPassData.cs b/EpinelPS/LobbyServer/Pass/GetActiveEventPassData.cs index 45251dd..5a7a93c 100644 --- a/EpinelPS/LobbyServer/Pass/GetActiveEventPassData.cs +++ b/EpinelPS/LobbyServer/Pass/GetActiveEventPassData.cs @@ -18,7 +18,7 @@ namespace EpinelPS.LobbyServer.Pass ResGetActiveEventPassData response = new(); // fields PassList = NetPassInfo List lobbyPrivateBanners = [];//[.. GameData.Instance.LobbyPrivateBannerTable.Values.Where(b => b.PrivateBannerShowDuration <= DateTime.UtcNow && b.EndDate >= DateTime.UtcNow)]; - lobbyPrivateBanners = EventHelper.GetLobbyPrivateBannerData(); + lobbyPrivateBanners = EventHelper.GetLobbyPrivateBannerData(user); // TODO: PrivateBannerShowDuration log.Debug($"Active lobby private banners: {JsonConvert.SerializeObject(lobbyPrivateBanners)}"); if (lobbyPrivateBanners.Count <= 0) diff --git a/EpinelPS/Models/DbModels.cs b/EpinelPS/Models/DbModels.cs index d662f13..689e33c 100644 --- a/EpinelPS/Models/DbModels.cs +++ b/EpinelPS/Models/DbModels.cs @@ -367,4 +367,55 @@ namespace EpinelPS.Models public List PassRankList { get; set; } = []; public List PassMissionList { get; set; } = []; } + + // MiniGameAzxData + public class MiniGameAzxData + { + public List ScoreDatas { get; set; } = []; + public List AchievementMissionDataList { get; set; } = []; + public List ConditionalBoardDataList { get; set; } = []; + public List ConditionalCharacterDataList { get; set; } = []; + public List ConditionalSkillDataList { get; set; } = []; + public List CutSceneDataList { get; set; } = []; + public bool IsTutorialConfirmed { get; set; } = false; + public int SelectedBoardId { get; set; } + public int SelectedCharacterId { get; set; } + public Dictionary SkillCount { get; set; } = []; + public Dictionary CharacterCount { get; set; } = []; + } + public class MiniGameAzxScoreData + { + public int AzxId { get; set; } + public int DateDay { get; set; } + public int AccumulatedScore { get; set; } + public int HighScore { get; set; } + public bool IsDailyRewarded { get; set; } = false; + public Google.Protobuf.WellKnownTypes.Duration? HighScoreTime { get; set; } + } + public class AchievementMissionData + { + public int MissionId { get; set; } + public bool IsReceived { get; set; } + + } + public class ConditionalBoardData + { + public int BoardId { get; set; } + public bool IsUnlocked { get; set; } + } + public class ConditionalCharacterData + { + public int CharacterId { get; set; } + public bool IsUnlocked { get; set; } + } + public class ConditionalSkillData + { + public int SkillId { get; set; } + public bool IsUnlocked { get; set; } + } + public class CutSceneData + { + public int CutSceneId { get; set; } + public bool IsNew { get; set; } + } } \ No newline at end of file diff --git a/EpinelPS/Models/UserModel.cs b/EpinelPS/Models/UserModel.cs index cd0c89b..c1f6345 100644 --- a/EpinelPS/Models/UserModel.cs +++ b/EpinelPS/Models/UserModel.cs @@ -122,6 +122,9 @@ public class User public Dictionary UserPassInfo = []; // user pass data, key is PassId + public List LobbyPrivateBannerIds = []; + public Dictionary MiniGameAzxInfo = []; + public TriggerModel AddTrigger(Trigger type, int value, int conditionId = 0) { TriggerModel t = new()