From 5134ac187be9f9f5dde376077e3460532ba7d009 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Sun, 14 Jul 2024 19:07:42 -0400 Subject: [PATCH] Implement stage skipping --- ProtobufViewUtil/Program.cs | 4 +- README.md | 4 + .../Msgs/Arena/ShowSpecialArenaReward.cs | 22 ++ .../FavoriteItem/GetFavoriteItemLibrary.cs | 24 +++ .../Msgs/FavoriteItem/ListFavoriteItem.cs | 23 +++ .../FavoriteItem/ListFavoriteItemQuests.cs | 23 +++ .../Msgs/Liberate/GetLiberateData.cs | 25 +++ .../Msgs/Lostsector/GetLostSectorData.cs | 25 +++ .../Msgs/Sidestory/ListSideStory.cs | 25 +++ nksrv/LobbyServer/Msgs/Stage/ClearStage.cs | 102 +++++----- nksrv/Program.cs | 191 +++++++++++++++++- nksrv/Protos/allmsgs.proto | 102 ++++++++++ nksrv/StaticInfo/JsonStaticData.cs | 1 + nksrv/StaticInfo/StaticDataParser.cs | 17 ++ nksrv/Utils/JsonDb.cs | 19 ++ 15 files changed, 558 insertions(+), 49 deletions(-) create mode 100644 nksrv/LobbyServer/Msgs/Arena/ShowSpecialArenaReward.cs create mode 100644 nksrv/LobbyServer/Msgs/FavoriteItem/GetFavoriteItemLibrary.cs create mode 100644 nksrv/LobbyServer/Msgs/FavoriteItem/ListFavoriteItem.cs create mode 100644 nksrv/LobbyServer/Msgs/FavoriteItem/ListFavoriteItemQuests.cs create mode 100644 nksrv/LobbyServer/Msgs/Liberate/GetLiberateData.cs create mode 100644 nksrv/LobbyServer/Msgs/Lostsector/GetLostSectorData.cs create mode 100644 nksrv/LobbyServer/Msgs/Sidestory/ListSideStory.cs diff --git a/ProtobufViewUtil/Program.cs b/ProtobufViewUtil/Program.cs index 28692fe..9cb44b7 100644 --- a/ProtobufViewUtil/Program.cs +++ b/ProtobufViewUtil/Program.cs @@ -10,8 +10,8 @@ namespace ProtobufViewUtil { Console.WriteLine("Hello, World!"); - ResGetOutpostData s = new ResGetOutpostData(); - var inn = File.ReadAllBytes(@"C:\Users\Misha\Downloads\getoutpostdatach2done"); + StaticDataPackResponse s = new StaticDataPackResponse(); + var inn = File.ReadAllBytes(@"C:\Users\Misha\Desktop\response"); s.MergeFrom(inn); Console.WriteLine(s.ToString()); var outt = s.ToByteArray(); diff --git a/README.md b/README.md index 928e007..2556805 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,10 @@ If the game does not get past the title screen, open an issue and send %appdata% Note that this was tested with the latest version (122.8.20c) +To access the admin panel, go to https://127.0.0.1/admin/ and sign in. Note that IsAdmin needs to be true for the user account. Note that this interface does not have anything yet. + +To skip stages, a basic command line interface is implemented. + ## Progress Stage, character, outpost and story information is saved and works, as well as player nickname. diff --git a/nksrv/LobbyServer/Msgs/Arena/ShowSpecialArenaReward.cs b/nksrv/LobbyServer/Msgs/Arena/ShowSpecialArenaReward.cs new file mode 100644 index 0000000..23489ac --- /dev/null +++ b/nksrv/LobbyServer/Msgs/Arena/ShowSpecialArenaReward.cs @@ -0,0 +1,22 @@ +using nksrv.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace nksrv.LobbyServer.Msgs.Arena +{ + [PacketPath("/arena/special/showreward")] + public class ShowSpecialArenaReward : LobbyMsgHandler + { + protected override async Task HandleAsync() + { + var req = await ReadData(); + + var response = new ResShowSpecialArenaReward(); + // TODO + await WriteDataAsync(response); + } + } +} diff --git a/nksrv/LobbyServer/Msgs/FavoriteItem/GetFavoriteItemLibrary.cs b/nksrv/LobbyServer/Msgs/FavoriteItem/GetFavoriteItemLibrary.cs new file mode 100644 index 0000000..72d2326 --- /dev/null +++ b/nksrv/LobbyServer/Msgs/FavoriteItem/GetFavoriteItemLibrary.cs @@ -0,0 +1,24 @@ +using nksrv.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace nksrv.LobbyServer.Msgs.FavoriteItem +{ + [PacketPath("/favoriteitem/library")] + public class GetFavoriteItemLibrary : LobbyMsgHandler + { + protected override async Task HandleAsync() + { + var req = await ReadData(); + + var response = new ResGetFavoriteItemLibrary(); + var user = GetUser(); + + + await WriteDataAsync(response); + } + } +} diff --git a/nksrv/LobbyServer/Msgs/FavoriteItem/ListFavoriteItem.cs b/nksrv/LobbyServer/Msgs/FavoriteItem/ListFavoriteItem.cs new file mode 100644 index 0000000..b9a5274 --- /dev/null +++ b/nksrv/LobbyServer/Msgs/FavoriteItem/ListFavoriteItem.cs @@ -0,0 +1,23 @@ +using nksrv.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace nksrv.LobbyServer.Msgs.FavoriteItem +{ + [PacketPath("/favoriteitem/list")] + public class ListFavoriteItem : LobbyMsgHandler + { + protected override async Task HandleAsync() + { + var req = await ReadData(); + var user = GetUser(); + + var response = new ResListFavoriteItem(); + + await WriteDataAsync(response); + } + } +} diff --git a/nksrv/LobbyServer/Msgs/FavoriteItem/ListFavoriteItemQuests.cs b/nksrv/LobbyServer/Msgs/FavoriteItem/ListFavoriteItemQuests.cs new file mode 100644 index 0000000..3aa493c --- /dev/null +++ b/nksrv/LobbyServer/Msgs/FavoriteItem/ListFavoriteItemQuests.cs @@ -0,0 +1,23 @@ +using nksrv.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace nksrv.LobbyServer.Msgs.FavoriteItem +{ + [PacketPath("/favoriteitem/quest/list")] + public class ListFavoriteItemQuests : LobbyMsgHandler + { + protected override async Task HandleAsync() + { + var req = await ReadData(); + var user = GetUser(); + + var response = new ResListFavoriteItemQuest(); + + await WriteDataAsync(response); + } + } +} diff --git a/nksrv/LobbyServer/Msgs/Liberate/GetLiberateData.cs b/nksrv/LobbyServer/Msgs/Liberate/GetLiberateData.cs new file mode 100644 index 0000000..094bd61 --- /dev/null +++ b/nksrv/LobbyServer/Msgs/Liberate/GetLiberateData.cs @@ -0,0 +1,25 @@ +using nksrv.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace nksrv.LobbyServer.Msgs.Liberate +{ + [PacketPath("/liberate/get")] + public class GetLiberateData : LobbyMsgHandler + { + protected override async Task HandleAsync() + { + var req = await ReadData(); + var user = GetUser(); + + var response = new ResGetLiberateData() { }; + + // TODO + + await WriteDataAsync(response); + } + } +} diff --git a/nksrv/LobbyServer/Msgs/Lostsector/GetLostSectorData.cs b/nksrv/LobbyServer/Msgs/Lostsector/GetLostSectorData.cs new file mode 100644 index 0000000..bf97655 --- /dev/null +++ b/nksrv/LobbyServer/Msgs/Lostsector/GetLostSectorData.cs @@ -0,0 +1,25 @@ +using nksrv.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace nksrv.LobbyServer.Msgs.Lostsector +{ + [PacketPath("/lostsector/get")] + public class GetLostSectorData : LobbyMsgHandler + { + protected override async Task HandleAsync() + { + var req = await ReadData(); + var user = GetUser(); + + var response = new ResGetLostSectorData(); + + // TODO + + await WriteDataAsync(response); + } + } +} diff --git a/nksrv/LobbyServer/Msgs/Sidestory/ListSideStory.cs b/nksrv/LobbyServer/Msgs/Sidestory/ListSideStory.cs new file mode 100644 index 0000000..d0c4b38 --- /dev/null +++ b/nksrv/LobbyServer/Msgs/Sidestory/ListSideStory.cs @@ -0,0 +1,25 @@ +using nksrv.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace nksrv.LobbyServer.Msgs.Sidestory +{ + [PacketPath("/sidestory/list")] + public class ListSideStory : LobbyMsgHandler + { + protected override async Task HandleAsync() + { + var req = await ReadData(); + var user = GetUser(); + + var response = new ResListSideStory(); + + // TODO + + await WriteDataAsync(response); + } + } +} diff --git a/nksrv/LobbyServer/Msgs/Stage/ClearStage.cs b/nksrv/LobbyServer/Msgs/Stage/ClearStage.cs index 2a41ac9..456630c 100644 --- a/nksrv/LobbyServer/Msgs/Stage/ClearStage.cs +++ b/nksrv/LobbyServer/Msgs/Stage/ClearStage.cs @@ -24,56 +24,68 @@ namespace nksrv.LobbyServer.Msgs.Stage // TODO: check if user has already cleared this stage if (req.BattleResult == 1) { - var clearedStage = StaticDataParser.Instance.GetStageData(req.StageId); - if (clearedStage == null) throw new Exception("cleared stage cannot be null"); - - - if (user.FieldInfo.Count == 0) - { - user.FieldInfo.Add("0_" + clearedStage.chapter_mod, new FieldInfo() { }); - } - - DoQuestSpecificUserOperations(user, req.StageId); - var rewardData = StaticDataParser.Instance.GetRewardTableEntry(clearedStage.reward_id); - - if (rewardData != null) - response.StageClearReward = RegisterRewardsForUser(user, rewardData); - else - Logger.Warn("rewardId is null for stage " + req.StageId); - - - if (clearedStage.stage_category == "Normal" || clearedStage.stage_category == "Boss" || clearedStage.stage_category == "Hard") - { - if (clearedStage.chapter_mod == "Hard") - { - user.LastHardStageCleared = req.StageId; - } - else if (clearedStage.chapter_mod == "Normal") - { - user.LastNormalStageCleared = req.StageId; - } - else - { - Logger.Warn("Unknown chapter mod " + clearedStage.chapter_mod); - } - } - else if (clearedStage.stage_category == "Extra") - { - - } - else - { - Logger.Warn("Unknown stage category " + clearedStage.stage_category); - } - - user.FieldInfo[(clearedStage.chapter_id - 1) + "_" + clearedStage.chapter_mod].CompletedStages.Add(new NetFieldStageData() { StageId = req.StageId }); - JsonDb.Save(); + response = CompleteStage(user, req.StageId); } await WriteDataAsync(response); } - private NetRewardData RegisterRewardsForUser(Utils.User user, RewardTableRecord rewardData) + + public static ResClearStage CompleteStage(Utils.User user, int StageId) + { + var response = new ResClearStage(); + var clearedStage = StaticDataParser.Instance.GetStageData(StageId); + if (clearedStage == null) throw new Exception("cleared stage cannot be null"); + + + if (user.FieldInfo.Count == 0) + { + user.FieldInfo.Add("0_" + clearedStage.chapter_mod, new FieldInfo() { }); + } + + DoQuestSpecificUserOperations(user, StageId); + var rewardData = StaticDataParser.Instance.GetRewardTableEntry(clearedStage.reward_id); + + if (rewardData != null) + response.StageClearReward = RegisterRewardsForUser(user, rewardData); + else + Logger.Warn("rewardId is null for stage " + StageId); + + + if (clearedStage.stage_category == "Normal" || clearedStage.stage_category == "Boss" || clearedStage.stage_category == "Hard") + { + if (clearedStage.chapter_mod == "Hard") + { + user.LastHardStageCleared = StageId; + } + else if (clearedStage.chapter_mod == "Normal") + { + user.LastNormalStageCleared = StageId; + } + else + { + Logger.Warn("Unknown chapter mod " + clearedStage.chapter_mod); + } + } + else if (clearedStage.stage_category == "Extra") + { + + } + else + { + Logger.Warn("Unknown stage category " + clearedStage.stage_category); + } + + var key = (clearedStage.chapter_id - 1) + "_" + clearedStage.chapter_mod; + if (!user.FieldInfo.ContainsKey(key)) + user.FieldInfo.Add(key, new FieldInfo()); + + user.FieldInfo[key].CompletedStages.Add(new NetFieldStageData() { StageId = StageId }); + JsonDb.Save(); + return response; + } + + private static NetRewardData RegisterRewardsForUser(Utils.User user, RewardTableRecord rewardData) { NetRewardData ret = new(); if (rewardData.rewards == null) return ret; diff --git a/nksrv/Program.cs b/nksrv/Program.cs index 00eb350..387dc68 100644 --- a/nksrv/Program.cs +++ b/nksrv/Program.cs @@ -21,6 +21,7 @@ using Swan; using Google.Api; using nksrv.StaticInfo; using EmbedIO.WebApi; +using nksrv.LobbyServer.Msgs.Stage; namespace nksrv { @@ -39,9 +40,195 @@ namespace nksrv LobbyHandler.Init(); Logger.Info("Starting server"); + new Thread(() => + { + var server = CreateWebServer(); + server.RunAsync(); + }).Start(); - using var server = CreateWebServer(); - await server.RunAsync(); + // cli interface + + ulong selectedUser = 0; + string prompt = "# "; + while (true) + { + Console.Write(prompt); + + var input = Console.ReadLine(); + var args = input.Split(' '); + if (string.IsNullOrEmpty(input) || string.IsNullOrWhiteSpace(input)) + { + + } + else if (input == "?" || input == "help") + { + Console.WriteLine("Nikke Private Server CLI interface"); + Console.WriteLine(); + Console.WriteLine("Commands:"); + Console.WriteLine(" help - show this help"); + Console.WriteLine(" ls /users - show all users"); + Console.WriteLine(" cd (user id) - select user by id"); + Console.WriteLine(" rmuser - delete selected user"); + Console.WriteLine(" ban - ban selected user from game"); + Console.WriteLine(" unban - unban selected user from game"); + Console.WriteLine(" exit - exit server application"); + Console.WriteLine(" completestage (chapter num)-(stage number) - complete selected stage and get rewards (and all previous ones). Example completestage 15-1. Note that the exact stage number cleared may not be exact."); + } + else if (input == "ls /users") + { + Console.WriteLine("Id,Username,Nickname"); + foreach (var item in JsonDb.Instance.Users) + { + Console.WriteLine($"{item.ID},{item.Username},{item.Nickname}"); + } + } + else if (input.StartsWith("cd")) + { + if (args.Length == 2) + { + if (ulong.TryParse(args[1], out ulong id)) + { + // check if user id exists + var user = JsonDb.Instance.Users.FirstOrDefault(x => x.ID == id); + if (user != null) + { + selectedUser = user.ID; + Console.WriteLine("Selected user: " + user.Username); + prompt = "/users/" + user.Username + "# "; + } + else + { + Console.WriteLine("User not found"); + } + } + else + { + Console.WriteLine("Argument #1 should be a number"); + Console.WriteLine("Usage: chroot (user id)"); + } + } + else + { + Console.WriteLine("Incorrect number of arguments for chroot"); + Console.WriteLine("Usage: chroot (user id)"); + } + } + else if (input.StartsWith("rmuser")) + { + if (selectedUser == 0) + { + Console.WriteLine("No user selected"); + } + else + { + var user = JsonDb.Instance.Users.FirstOrDefault(x => x.ID == selectedUser); + if (user == null) + { + Console.WriteLine("Selected user does not exist"); + selectedUser = 0; + prompt = "# "; + } + else + { + Console.Write("Are you sure you want to delete user " + user.Username + "? (y/n) "); + var confirm = Console.ReadLine(); + if (confirm == "y") + { + JsonDb.Instance.Users.Remove(user); + JsonDb.Save(); + Console.WriteLine("User deleted"); + selectedUser = 0; + prompt = "# "; + } + else + { + Console.WriteLine("User not deleted"); + } + } + } + } + else if (input.StartsWith("completestage")) + { + if (selectedUser == 0) + { + Console.WriteLine("No user selected"); + } + else + { + var user = JsonDb.Instance.Users.FirstOrDefault(x => x.ID == selectedUser); + if (user == null) + { + Console.WriteLine("Selected user does not exist"); + selectedUser = 0; + prompt = "# "; + } + else + { + if (args.Length == 2) + { + var input2 = args[1]; + try + { + var chapter = int.TryParse(input2.Split('-')[0], out int chapterNumber); + var stage = int.TryParse(input2.Split('-')[1], out int stageNumber); + + if (chapter && stage) + { + for (int i = 0; i < chapterNumber + 1; i++) + { + var stages = StaticDataParser.Instance.GetStageIdsForChapter(i, true); + int target = 1; + foreach (var item in stages) + { + if (!user.IsStageCompleted(item, true)) + { + Console.WriteLine("Completing stage " + item); + ClearStage.CompleteStage(user, item); + } + + if (i == chapterNumber && target == stageNumber) + { + break; + } + + target++; + } + } + } + else + { + Console.WriteLine("chapter and stage number must be a 32 bit integer"); + } + } + catch (Exception ex) + { + Console.WriteLine("exception:" + ex.ToString()); + } + } + else + { + Console.WriteLine("invalid argument length, must be 1"); + } + } + } + } + else if (input == "exit") + { + Environment.Exit(0); + } + else if (input == "ban") + { + Console.WriteLine("Not implemented"); + } + else if (input == "unban") + { + Console.WriteLine("Not implemented"); + } + else + { + Console.WriteLine("Unknown command"); + } + } } private static WebServer CreateWebServer() { diff --git a/nksrv/Protos/allmsgs.proto b/nksrv/Protos/allmsgs.proto index 33af354..80f47f3 100644 --- a/nksrv/Protos/allmsgs.proto +++ b/nksrv/Protos/allmsgs.proto @@ -2195,4 +2195,106 @@ message ReqAllClearEquipment { message ResAllClearEquipment { int64 Csn = 2; repeated NetUserItemData Items = 3; +} + +message NetFavoriteItemLibraryElement { + int32 Tid = 1; + int64 ReceivedAt = 2; +} + +message ReqGetFavoriteItemLibrary { + +} +message ResGetFavoriteItemLibrary { + repeated NetFavoriteItemLibraryElement FavoriteItemLibrary = 1; +} + + +message ReqListFavoriteItem { + +} +message ResListFavoriteItem { + repeated NetUserFavoriteItemData FavoriteItems = 1; +} + +message NetUserFavoriteItemQuestData { + int32 QuestId = 1; + bool Clear = 2; + bool Received = 3; +} +message ReqListFavoriteItemQuest { + +} +message ResListFavoriteItemQuest { + repeated NetUserFavoriteItemQuestData FavoriteItemQuests = 1; +} + +message NetUserLostSectorData { + int32 SectorId = 1; + int32 RewardCount = 2; + bool IsFinalReward = 3; + bool IsPlaying = 4; + bool IsOpen = 5; + int32 CurrentClearStageCount = 6; + int32 MaxClearStageCount = 7; + bool IsPerfectReward = 8; +} +message ReqGetLostSectorData { + +} +message ResGetLostSectorData { + repeated NetUserLostSectorData LostSector = 2; + int32 LastEnterSectorId = 3; + repeated NetFieldStageData ClearedStages = 4; +} + +message NetSideStoryStageData { + int32 SideStoryStageId = 1; + google.protobuf.Timestamp ClearedAt = 2; +} + +message ReqListSideStory {} +message ResListSideStory { + repeated NetSideStoryStageData SideStoryStageDataList = 1; +} + +enum LiberateMissionState { + LiberateMissionState_Running = 0; + LiberateMissionState_Rewarded = 1; + LiberateMissionState_Closed = 2; +} + +message NetLiberateMissionData { + int64 Id = 1; + int32 MissionTid = 2; + int32 LiberateCharacterId = 3; + LiberateMissionState MissionState = 4; + int64 CreatedAt = 6; + int64 TriggerStartAt = 7; + int64 TriggerEndAt = 8; + optional int64 ReceivedAt = 9; +} + +message NetLiberateData { + int32 CharacterId = 2; + int32 StepId = 3; + int32 ProgressPoint = 4; + repeated NetLiberateMissionData MissionData = 5; + int32 RewardedCount = 6; + bool IsCompleted = 7; +} + +message ReqGetLiberateData {} +message ResGetLiberateData { + repeated int32 OpenLiberateTypeIdList = 2; + NetLiberateData LiberateData = 3; +} + +message ReqShowSpecialArenaReward {} +message ResShowSpecialArenaReward { + NetRewardData reward = 4; + /*SpecialArenaContentsState SpecialArenaContentsState = 5; + NetSpecialArenaRewardHistory History = 6;*/ + bool IsBan = 7; + NetArenaBanInfo BanInfo = 8; } \ No newline at end of file diff --git a/nksrv/StaticInfo/JsonStaticData.cs b/nksrv/StaticInfo/JsonStaticData.cs index 2cb8b5b..aae8d07 100644 --- a/nksrv/StaticInfo/JsonStaticData.cs +++ b/nksrv/StaticInfo/JsonStaticData.cs @@ -26,6 +26,7 @@ namespace nksrv.StaticInfo /// Can be Normal or Hard /// public string chapter_mod = ""; + public string stage_type = ""; } public class RewardTableRecord { diff --git a/nksrv/StaticInfo/StaticDataParser.cs b/nksrv/StaticInfo/StaticDataParser.cs index d5439a7..900f5db 100644 --- a/nksrv/StaticInfo/StaticDataParser.cs +++ b/nksrv/StaticInfo/StaticDataParser.cs @@ -448,5 +448,22 @@ namespace nksrv.StaticInfo return null; } + + internal IEnumerable GetStageIdsForChapter(int chapterNumber, bool normal) + { + string mod = normal ? "Normal" : "Hard"; + foreach (JObject item in stageDataRecords) + { + CampaignStageRecord? data = JsonConvert.DeserializeObject(item.ToString()); + if (data == null) throw new Exception("failed to deserialize stage data"); + + int chVal = data.chapter_id - 1; + + if (chapterNumber == chVal && data.chapter_mod == mod && data.stage_type == "Main") + { + yield return data.id; + } + } + } } } diff --git a/nksrv/Utils/JsonDb.cs b/nksrv/Utils/JsonDb.cs index 7997a4c..1d0be0b 100644 --- a/nksrv/Utils/JsonDb.cs +++ b/nksrv/Utils/JsonDb.cs @@ -1,6 +1,7 @@ using ASodium; using Newtonsoft.Json; using nksrv.LobbyServer; +using nksrv.LobbyServer.Msgs.Stage; using nksrv.StaticInfo; using Swan.Logging; using System; @@ -135,6 +136,24 @@ namespace nksrv.Utils return num; } + + public bool IsStageCompleted(int id, bool isNorm) + { + foreach (var item in FieldInfo) + { + if (item.Key.Contains("hard") && isNorm) continue; + if (item.Key.Contains("normal") && !isNorm) continue; + foreach (var s in item.Value.CompletedStages) + { + if (s.StageId == id) + { + return true; + } + } + } + + return false; + } } public class CoreInfo {