diff --git a/EpinelPS/Data/GameData.cs b/EpinelPS/Data/GameData.cs index a1d9eca..db21364 100644 --- a/EpinelPS/Data/GameData.cs +++ b/EpinelPS/Data/GameData.cs @@ -163,6 +163,12 @@ namespace EpinelPS.Data [LoadRecord("ScenarioRewardsTable.json", "condition_id", typeof(ScenarioRewardTable))] public readonly Dictionary ScenarioRewards = []; + // Note: same data types are intentional + [LoadRecord("ProductOfferTable.json", "id", typeof(ProductOfferTable))] + public readonly Dictionary ProductOffers = []; + + [LoadRecord("PopupPackageListTable.json", "id", typeof(ProductOfferTable))] + public readonly Dictionary PopupPackages = []; static async Task BuildAsync() { @@ -586,5 +592,20 @@ namespace EpinelPS.Data return false; } + + internal string GetMapIdFromChapter(int chapter, int mod) + { + CampaignChapterRecord data = ChapterCampaignData[chapter - 1]; + if (mod != 0) + return data.hard_field_id; + else return data.field_id; + } + internal string GetMapIdFromChapter(int chapter, string mod) + { + CampaignChapterRecord data = ChapterCampaignData[chapter - 1]; + if (mod == "Hard") + return data.hard_field_id; + else return data.field_id; + } } } diff --git a/EpinelPS/Data/JsonStaticData.cs b/EpinelPS/Data/JsonStaticData.cs index 5b1a05b..e7bec92 100644 --- a/EpinelPS/Data/JsonStaticData.cs +++ b/EpinelPS/Data/JsonStaticData.cs @@ -760,4 +760,14 @@ { public List records = []; } + + public class ProductOfferRecord + { + public int id; + } + public class ProductOfferTable + { + public List records = []; + } + } diff --git a/EpinelPS/Database/JsonDb.cs b/EpinelPS/Database/JsonDb.cs index 6728982..b412740 100644 --- a/EpinelPS/Database/JsonDb.cs +++ b/EpinelPS/Database/JsonDb.cs @@ -587,7 +587,6 @@ namespace EpinelPS.Database public List LauncherAccessTokens = []; public Dictionary AdminAuthTokens = new(); - public string ServerName = "Private Server"; public byte[] LauncherTokenKey = []; public byte[] EncryptionTokenKey = []; public LogType LogLevel = LogType.Debug; @@ -679,6 +678,45 @@ namespace EpinelPS.Database } Console.WriteLine("Database update completed"); } + if (Instance.DbVersion == 4) + { + Console.WriteLine("Starting database update..."); + Instance.DbVersion = 5; + // FieldInfoNew uses MapId instead of ChapterNum_ChapterDifficulty format + foreach (var user in Instance.Users) + { + Dictionary info = new(); + foreach (var item in user.FieldInfoNew) + { + if (item.Key.EndsWith("_Normal") || item.Key.EndsWith("_Hard")) + { + var newKey = GameData.Instance.GetMapIdFromDBFieldName(item.Key); + if (newKey != null) + { + if (!info.ContainsKey(newKey)) + { + info.Add(newKey, item.Value); + } + else + { + // overwrite old data + info[newKey] = item.Value; + } + } + else + Console.WriteLine("Unknown chapter/difficulty: " + item.Value + ", discarding"); + } + else + { + if (!info.ContainsKey(item.Key)) + info.Add(item.Key, item.Value); + } + } + + user.FieldInfoNew = info; + } + Console.WriteLine("Database update completed"); + } if (Instance.LauncherTokenKey.Length == 0) { @@ -699,6 +737,8 @@ namespace EpinelPS.Database Save(); + Logging.SetOutputLevel(Instance.LogLevel); + ValidateDb(); Console.WriteLine("JsonDb: Loaded"); } @@ -706,6 +746,7 @@ namespace EpinelPS.Database { throw new Exception("Failed to read configuration json file"); } + } public static void Reload() diff --git a/EpinelPS/LobbyServer/Campaign/GetCampaignField.cs b/EpinelPS/LobbyServer/Campaign/GetCampaignField.cs index cf06794..dd8f08a 100644 --- a/EpinelPS/LobbyServer/Campaign/GetCampaignField.cs +++ b/EpinelPS/LobbyServer/Campaign/GetCampaignField.cs @@ -15,7 +15,7 @@ namespace EpinelPS.LobbyServer.Campaign Console.WriteLine("Map ID: " + req.MapId); var response = new ResGetCampaignFieldData(); - response.Field = GetStage.CreateFieldInfo(user, GameData.Instance.GetNormalChapterNumberFromFieldName(req.MapId), req.MapId.Contains("hard") ? "Hard" : "Normal", out bool bossEntered); + response.Field = GetStage.CreateFieldInfo(user, req.MapId, out bool bossEntered); // todo save this data response.Team = new NetUserTeamData() { LastContentsTeamNumber = 1, Type = 1 }; diff --git a/EpinelPS/LobbyServer/Campaign/GetFieldObjectsCount.cs b/EpinelPS/LobbyServer/Campaign/GetFieldObjectsCount.cs index cc07547..a5a3ec9 100644 --- a/EpinelPS/LobbyServer/Campaign/GetFieldObjectsCount.cs +++ b/EpinelPS/LobbyServer/Campaign/GetFieldObjectsCount.cs @@ -19,7 +19,7 @@ namespace EpinelPS.LobbyServer.Campaign { response.FieldObjectItemsNum.Add(new NetCampaignFieldObjectItemsNum() { - MapId = GameData.Instance.GetMapIdFromDBFieldName(map.Key), + MapId = map.Key, Count = map.Value.CompletedObjects.Count }); } diff --git a/EpinelPS/LobbyServer/Campaign/ObtainItem.cs b/EpinelPS/LobbyServer/Campaign/ObtainItem.cs index dd8b4fa..bd89954 100644 --- a/EpinelPS/LobbyServer/Campaign/ObtainItem.cs +++ b/EpinelPS/LobbyServer/Campaign/ObtainItem.cs @@ -15,20 +15,10 @@ namespace EpinelPS.LobbyServer.Campaign var response = new ResObtainCampaignItem(); - var chapter = GameData.Instance.GetNormalChapterNumberFromFieldName(req.MapId); - var mod = req.MapId.Contains("hard") ? "Hard" : "Normal"; - var key = chapter + "_" + mod; - - if (chapter == -1) - { - Logging.WriteLine("Warning: unknown chapter id for " + req.MapId, LogType.Warning); - key = req.MapId; - } - - if (!user.FieldInfoNew.TryGetValue(key, out FieldInfoNew? field)) + if (!user.FieldInfoNew.TryGetValue(req.MapId, out FieldInfoNew? field)) { field = new FieldInfoNew(); - user.FieldInfoNew.Add(key, field); + user.FieldInfoNew.Add(req.MapId, field); } diff --git a/EpinelPS/LobbyServer/Campaign/SaveFieldObject.cs b/EpinelPS/LobbyServer/Campaign/SaveFieldObject.cs index 2ad69fa..b1d8527 100644 --- a/EpinelPS/LobbyServer/Campaign/SaveFieldObject.cs +++ b/EpinelPS/LobbyServer/Campaign/SaveFieldObject.cs @@ -16,10 +16,7 @@ namespace EpinelPS.LobbyServer.Campaign Logging.WriteLine($"save {req.MapId} with {req.FieldObject.PositionId}", LogType.Debug); - var chapter = GameData.Instance.GetNormalChapterNumberFromFieldName(req.MapId); - var mod = req.MapId.Contains("hard") ? "Hard" : "Normal"; - var key = chapter + "_" + mod; - var field = user.FieldInfoNew[key]; + var field = user.FieldInfoNew[req.MapId]; field.CompletedObjects.Add(new NetFieldObject() { PositionId = req.FieldObject.PositionId, Json = req.FieldObject.Json, Type = req.FieldObject.Type }); JsonDb.Save(); diff --git a/EpinelPS/LobbyServer/Shop/GetProductOffers.cs b/EpinelPS/LobbyServer/Shop/GetProductOffers.cs index 9a27bc0..a9f9eb9 100644 --- a/EpinelPS/LobbyServer/Shop/GetProductOffers.cs +++ b/EpinelPS/LobbyServer/Shop/GetProductOffers.cs @@ -1,3 +1,4 @@ +using EpinelPS.Data; using EpinelPS.Utils; namespace EpinelPS.LobbyServer.Shop @@ -9,9 +10,12 @@ namespace EpinelPS.LobbyServer.Shop { var x = await ReadData(); - // TODO: Figure out a way to disable ads - + // Disable in game ads var response = new ResListSeenProductOffer(); + foreach(var item in GameData.Instance.ProductOffers) + { + response.Result.Add(new NetUserProductOfferSeenHistory() { ProductOfferId = item.Key }); + } await WriteDataAsync(response); } diff --git a/EpinelPS/LobbyServer/Shop/InApp/GetProductListInApp.cs b/EpinelPS/LobbyServer/Shop/InApp/GetProductListInApp.cs index 2ccc387..7ac756a 100644 --- a/EpinelPS/LobbyServer/Shop/InApp/GetProductListInApp.cs +++ b/EpinelPS/LobbyServer/Shop/InApp/GetProductListInApp.cs @@ -11,7 +11,7 @@ namespace EpinelPS.LobbyServer.Shop.InApp var response = new ResGetInAppShopData(); - // TODO + response.InAppShopDataList.Add(new NetInAppShopData() { Id = 10001, StartDate = DateTime.Now.Ticks, EndDate = DateTime.Now.AddDays(2).Ticks }); await WriteDataAsync(response); } diff --git a/EpinelPS/LobbyServer/Shop/PackageShop/GetPackagePopupState.cs b/EpinelPS/LobbyServer/Shop/PackageShop/GetPackagePopupState.cs index 1425910..fd3d03a 100644 --- a/EpinelPS/LobbyServer/Shop/PackageShop/GetPackagePopupState.cs +++ b/EpinelPS/LobbyServer/Shop/PackageShop/GetPackagePopupState.cs @@ -1,4 +1,5 @@ -using EpinelPS.Utils; +using EpinelPS.Data; +using EpinelPS.Utils; namespace EpinelPS.LobbyServer.Shop.PackageShop { @@ -10,6 +11,11 @@ namespace EpinelPS.LobbyServer.Shop.PackageShop var req = await ReadData(); var response = new ResGetPopupPackageState(); + + // disable ads + foreach (var item in GameData.Instance.PopupPackages) + response.AppearedList.Add(item.Key); + await WriteDataAsync(response); } } diff --git a/EpinelPS/LobbyServer/Stage/ClearStage.cs b/EpinelPS/LobbyServer/Stage/ClearStage.cs index 8edb6e6..3cb2a29 100644 --- a/EpinelPS/LobbyServer/Stage/ClearStage.cs +++ b/EpinelPS/LobbyServer/Stage/ClearStage.cs @@ -31,15 +31,16 @@ namespace EpinelPS.LobbyServer.Stage var response = new ResClearStage(); var clearedStage = GameData.Instance.GetStageData(StageId) ?? throw new Exception("cleared stage cannot be null"); + var stageMapId = GameData.Instance.GetMapIdFromChapter(clearedStage.chapter_id, clearedStage.chapter_mod); + if (user.FieldInfoNew.Count == 0) { - user.FieldInfoNew.Add("0_" + clearedStage.chapter_mod, new FieldInfoNew() { }); + user.FieldInfoNew.Add(stageMapId, new FieldInfoNew() { }); } DoQuestSpecificUserOperations(user, StageId); var rewardData = GameData.Instance.GetRewardTableEntry(clearedStage.reward_id); - if (forceCompleteScenarios) { if (!user.CompletedScenarios.Contains(clearedStage.enter_scenario) && !string.IsNullOrEmpty(clearedStage.enter_scenario) && !string.IsNullOrWhiteSpace(clearedStage.enter_scenario)) @@ -144,13 +145,10 @@ namespace EpinelPS.LobbyServer.Stage user.AddTrigger(TriggerType.ChapterClear, 1, clearedStage.chapter_id); } - // CreateClearInfo(user); + if (!user.FieldInfoNew.ContainsKey(stageMapId)) + user.FieldInfoNew.Add(stageMapId, new FieldInfoNew()); - var key = (clearedStage.chapter_id - 1) + "_" + clearedStage.chapter_mod; - if (!user.FieldInfoNew.ContainsKey(key)) - user.FieldInfoNew.Add(key, new FieldInfoNew()); - - user.FieldInfoNew[key].CompletedStages.Add(StageId); + user.FieldInfoNew[stageMapId].CompletedStages.Add(StageId); JsonDb.Save(); return response; } diff --git a/EpinelPS/LobbyServer/Stage/EnterStage.cs b/EpinelPS/LobbyServer/Stage/EnterStage.cs index e80b819..ffc14ca 100644 --- a/EpinelPS/LobbyServer/Stage/EnterStage.cs +++ b/EpinelPS/LobbyServer/Stage/EnterStage.cs @@ -15,15 +15,15 @@ namespace EpinelPS.LobbyServer.Stage var response = new ResEnterStage(); var clearedStage = GameData.Instance.GetStageData(req.StageId) ?? throw new Exception("cleared stage cannot be null"); + var map = GameData.Instance.GetMapIdFromChapter(clearedStage.chapter_id, clearedStage.chapter_id); if (clearedStage.stage_category == "Boss") { // When entering a boss stage, unlock boss information in campaign - var key = (clearedStage.chapter_id - 1) + "_" + clearedStage.chapter_mod; - if (!user.FieldInfoNew.ContainsKey(key)) - user.FieldInfoNew.Add(key, new FieldInfoNew()); + if (!user.FieldInfoNew.ContainsKey(map)) + user.FieldInfoNew.Add(map, new FieldInfoNew()); - if (user.FieldInfoNew.TryGetValue(key, out FieldInfoNew? info)) + if (user.FieldInfoNew.TryGetValue(map, out FieldInfoNew? info)) info.BossEntered = true; } diff --git a/EpinelPS/LobbyServer/Stage/GetStage.cs b/EpinelPS/LobbyServer/Stage/GetStage.cs index 37e35e4..87faf91 100644 --- a/EpinelPS/LobbyServer/Stage/GetStage.cs +++ b/EpinelPS/LobbyServer/Stage/GetStage.cs @@ -1,4 +1,5 @@ -using EpinelPS.Database; +using EpinelPS.Data; +using EpinelPS.Database; using EpinelPS.Utils; namespace EpinelPS.LobbyServer.Stage @@ -11,9 +12,11 @@ namespace EpinelPS.LobbyServer.Stage ReqGetStageData req = await ReadData(); var user = GetUser(); + var mapId = GameData.Instance.GetMapIdFromChapter(req.Chapter, req.Mod); + ResGetStageData response = new() { - Field = CreateFieldInfo(user, req.Chapter - 1, req.Mod == 0 ? "Normal" : "Hard", out bool bossEntered), + Field = CreateFieldInfo(user, mapId, out bool bossEntered), HasChapterBossEntered = bossEntered, SquadData = "" }; @@ -21,15 +24,14 @@ namespace EpinelPS.LobbyServer.Stage await WriteDataAsync(response); } - public static NetFieldObjectData CreateFieldInfo(Database.User user, int chapter, string mod, out bool BossEntered) + public static NetFieldObjectData CreateFieldInfo(User user, string mapId, out bool BossEntered) { var f = new NetFieldObjectData(); bool found = false; - string key = chapter + "_" + mod; BossEntered = false; foreach (var item in user.FieldInfoNew) { - if (item.Key == key) + if (item.Key == mapId) { found = true; foreach (var stage in item.Value.CompletedStages) @@ -47,8 +49,8 @@ namespace EpinelPS.LobbyServer.Stage if (!found) { - user.FieldInfoNew.Add(key, new FieldInfoNew()); - return CreateFieldInfo(user, chapter, mod, out BossEntered); + user.FieldInfoNew.Add(mapId, new FieldInfoNew()); + return CreateFieldInfo(user, mapId, out BossEntered); } return f; diff --git a/EpinelPS/Program.cs b/EpinelPS/Program.cs index 560328f..e0a6763 100644 --- a/EpinelPS/Program.cs +++ b/EpinelPS/Program.cs @@ -20,12 +20,14 @@ namespace EpinelPS try { Console.WriteLine($"EpinelPS v{Assembly.GetExecutingAssembly().GetName().Version} - https://github.com/EpinelPS/EpinelPS/"); - Console.WriteLine("Targeting Game Version " + GameConfig.Root.GameMaxVer); - Console.WriteLine("Initializing database"); - JsonDb.Save(); + Console.WriteLine("This software is licensed under the AGPL-3.0 License"); + Console.WriteLine("Targeting game version " + GameConfig.Root.GameMaxVer); GameData.Instance.GetAllCostumes(); // force static data to be loaded + Console.WriteLine("Initializing database"); + JsonDb.Save(); + Logging.WriteLine("Register handlers"); LobbyHandler.Init(); diff --git a/EpinelPS/Utils/GameConfig.cs b/EpinelPS/Utils/GameConfig.cs index 3df89d6..aa9f5e8 100644 --- a/EpinelPS/Utils/GameConfig.cs +++ b/EpinelPS/Utils/GameConfig.cs @@ -8,6 +8,10 @@ namespace EpinelPS.Utils public string ResourceBaseURL { get; set; } = ""; public string GameMinVer { get; set; } = ""; public string GameMaxVer { get; set; } = ""; + /// + /// this is only for displaying the target version in admin console or cli + /// + public string TargetVersion { get; set; } } public class StaticData diff --git a/EpinelPS/Utils/Logging.cs b/EpinelPS/Utils/Logging.cs index 8067fc1..c93f21c 100644 --- a/EpinelPS/Utils/Logging.cs +++ b/EpinelPS/Utils/Logging.cs @@ -3,8 +3,13 @@ using EpinelPS.Database; namespace EpinelPS.Utils { - public class Logging + public static class Logging { + private static LogType LogLevel = LogType.Info; + public static void SetOutputLevel(LogType level) + { + LogLevel = level; + } public static void WriteLine(string msg, LogType level = LogType.Info) { var originalFG = Console.ForegroundColor; @@ -13,7 +18,7 @@ namespace EpinelPS.Utils // todo write to some file - if (JsonDb.Instance.LogLevel <= level) + if (LogLevel <= level) Console.WriteLine(msg); Console.ForegroundColor = originalFG; diff --git a/EpinelPS/wwwroot/admin/assets/css/nikke-theme.css b/EpinelPS/wwwroot/admin/assets/css/nikke-theme.css index 2f98eee..a73c2b2 100644 --- a/EpinelPS/wwwroot/admin/assets/css/nikke-theme.css +++ b/EpinelPS/wwwroot/admin/assets/css/nikke-theme.css @@ -138,7 +138,6 @@ a.nav-link:hover:after { width: 28px; height: 28px; margin-right: 10px; - background-image: url('/admin/assets/img/nikke-logo-icon.png'); background-size: contain; background-repeat: no-repeat; } diff --git a/EpinelPS/wwwroot/admin/assets/i18n/en.json b/EpinelPS/wwwroot/admin/assets/i18n/en.json index 9125138..807318d 100644 --- a/EpinelPS/wwwroot/admin/assets/i18n/en.json +++ b/EpinelPS/wwwroot/admin/assets/i18n/en.json @@ -1,15 +1,15 @@ { "app": { - "name": "NIKKE Admin Console", - "version": "Version v2.5.3", - "footer": "© 2025 - NIKKE: Goddess of Victory - Admin Console" + "name": "EpinelPS Admin Console", + "version": "Version v1.0.0", + "footer": "© 2025 EpinelPS" }, "auth": { "login": "Login", "username": "Username", "password": "Password", - "enter": "Enter Console", - "welcome": "Please login to access admin features", + "enter": "Login", + "welcome": "Please login", "verifying": "Verifying...", "success": "Login successful", "error": { @@ -49,7 +49,7 @@ }, "dashboard": { "title": "Console Overview", - "refresh": "Refresh Data", + "refresh": "Refresh", "refreshing": "Refreshing...", "statsCards": { "users": "Registered Users", diff --git a/EpinelPS/wwwroot/admin/assets/i18n/ja.json b/EpinelPS/wwwroot/admin/assets/i18n/ja.json index c1aadde..06c0e15 100644 --- a/EpinelPS/wwwroot/admin/assets/i18n/ja.json +++ b/EpinelPS/wwwroot/admin/assets/i18n/ja.json @@ -1,8 +1,8 @@ { "app": { - "name": "NIKKEアドミンコンソール", - "version": "バージョン v2.5.3", - "footer": "© 2025 - NIKKE: 勝利の女神 - アドミンコンソール" + "name": "EpinelPSアドミンコンソール", + "version": "バージョン v1.0.0", + "footer": "© 2025 EpinelPS" }, "auth": { "login": "ログイン", diff --git a/EpinelPS/wwwroot/admin/assets/i18n/ko.json b/EpinelPS/wwwroot/admin/assets/i18n/ko.json index d6ddbf6..5f3b9a1 100644 --- a/EpinelPS/wwwroot/admin/assets/i18n/ko.json +++ b/EpinelPS/wwwroot/admin/assets/i18n/ko.json @@ -1,8 +1,8 @@ { "app": { - "name": "NIKKE 관리 콘솔", - "version": "버전 v2.5.3", - "footer": "© 2025 - NIKKE: 승리의 여신 - 관리 콘솔" + "name": "EpinelPS 관리 콘솔", + "version": "버전 v1.0.0", + "footer": "© 2025 EpinelPS" }, "auth": { "login": "로그인", diff --git a/EpinelPS/wwwroot/admin/assets/i18n/zh.json b/EpinelPS/wwwroot/admin/assets/i18n/zh.json index 4f24fca..689eb7a 100644 --- a/EpinelPS/wwwroot/admin/assets/i18n/zh.json +++ b/EpinelPS/wwwroot/admin/assets/i18n/zh.json @@ -1,8 +1,8 @@ { "app": { "name": "胜利女神控制台", - "version": "版本 v2.5.3", - "footer": "© 2025 - NIKKE: 胜利女神 - 管理控制台" + "version": "版本 v1.0.0", + "footer": "© 2025 EpinelPS" }, "auth": { "login": "登录", diff --git a/EpinelPS/wwwroot/admin/index.html b/EpinelPS/wwwroot/admin/index.html index 33f50c7..a6ec56e 100644 --- a/EpinelPS/wwwroot/admin/index.html +++ b/EpinelPS/wwwroot/admin/index.html @@ -4,7 +4,7 @@ - NIKKE: 胜利女神 - 管理控制台 + EpinelPS @@ -32,7 +32,6 @@