From 727f13aba6d93b70eb9a2fa7dc11356b1fea319a Mon Sep 17 00:00:00 2001 From: milimoe Date: Sat, 4 Apr 2026 04:13:33 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20rogue?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Library/Main.cs | 15 +- Library/Solutions/RogueLike.cs | 248 +++++++++++++++++ Library/Solutions/RogueLikeServer.cs | 387 +++++++++++++++++++++++++++ Library/Tests/FunGameBO5.cs | 26 +- Library/Tests/RogueLikeTest.cs | 264 ++++++++++++++++++ 5 files changed, 934 insertions(+), 6 deletions(-) create mode 100644 Library/Solutions/RogueLike.cs create mode 100644 Library/Solutions/RogueLikeServer.cs create mode 100644 Library/Tests/RogueLikeTest.cs diff --git a/Library/Main.cs b/Library/Main.cs index d4a2710..73fabe8 100644 --- a/Library/Main.cs +++ b/Library/Main.cs @@ -9,11 +9,10 @@ using Oshima.FunGame.OshimaServers.Service; using Oshima.FunGame.WebAPI.Controllers; //_ = new Milimoe.FunGame.Testing.Solutions.Novels(); +//Console.ReadKey(); //_ = new Milimoe.FunGame.Testing.Tests.CheckDLL(); -Console.WriteLine(); - //_ = new Milimoe.FunGame.Testing.Tests.WebSocketTest(); CharacterModule cm = new(); @@ -23,10 +22,22 @@ sm.Load(); ItemModule im = new(); im.Load(); +Milimoe.FunGame.Testing.Tests.RogueLikeTest rogue = new(); +await rogue.RogueLike.StartGame(); + FunGameConstant.InitFunGame(); FunGameSimulation.InitFunGameSimulation(); FunGameController controller = new(new Logger(new LoggerFactory())); +//foreach (OshimaRegion or in FunGameConstant.Regions) +//{ +// Console.WriteLine(or.ToString()); +//} +//Console.ReadKey(); + +//await Milimoe.FunGame.Testing.Tests.FunGameBO5.StartBO5(); +//Console.ReadKey(); + //foreach (Character c in FunGameSimulation.CharacterStatistics.Keys) //{ // Console.WriteLine(controller.GetStats((int)c.Id)); diff --git a/Library/Solutions/RogueLike.cs b/Library/Solutions/RogueLike.cs new file mode 100644 index 0000000..a228313 --- /dev/null +++ b/Library/Solutions/RogueLike.cs @@ -0,0 +1,248 @@ +using Milimoe.FunGame.Core.Entity; +using Milimoe.FunGame.Core.Library.Constant; +using Milimoe.FunGame.Core.Model; +using Milimoe.FunGame.Testing.Tests; +using Oshima.FunGame.OshimaModules.Characters; +using Oshima.FunGame.OshimaServers.Service; + +namespace Milimoe.FunGame.Testing.Solutions +{ + /// + /// 客户端逻辑层 + /// + /// + public class RogueLike(RogueLikeDispatcher dispatcher) + { + public RogueLikeDispatcher Dispatcher { get; set; } = dispatcher; + + public Dictionary> Commands { get; set; } = []; + public Dictionary CommandAlias { get; set; } = []; + public User User { get; set; } = General.UnknownUserInstance; + public bool Running { get; set; } = false; + public bool InGame { get; set; } = false; + public Lock Lock { get; } = new(); + + /// + /// 为空表示没有正在进行的询问 + /// + private Guid _currentRequestGuid = Guid.Empty; + /// + /// 询问结果,和上面的字段配合使用 + /// + private TaskCompletionSource _requestTcs = new(); + + public async Task StartGame() + { + Running = true; + Dispatcher.StartServer(); + AddDialog("???", "探索者,欢迎入职【永恒方舟计划】,我是您的专属 AI,协助您前往指定任务地点开展勘测工作。请问您的名字是?"); + string username; + do + { + username = await GetTextResultsAsync([]); + if (username == "") + { + WriteLine("请输入不为空的字符串。"); + } + } + while (username == ""); + DataRequestArgs response = await DataRequest(new("createuser") + { + Data = new() + { + { "username", username } + } + }); + if (response.Data.TryGetValue("user", out object? value) && value is User user) + { + User = user; + } + // 初始化菜单指令 + Commands["quit"] = async (args) => + { + Running = false; + WriteLine("游戏结束!"); + }; + AddCommandAlias("quit", "退出", "exit", "!q"); + Commands["test"] = async (args) => + { + WriteLine("打木桩测试"); + Character enemy = new CustomCharacter(0, "木桩") + { + Level = User.Inventory.MainCharacter.Level, + CharacterState = CharacterState.NotActionable + }; + enemy.Recovery(); + List strings = (await FunGameActionQueue.NewAndStartGame([User.Inventory.MainCharacter, enemy], showAllRound: true)).Result; + foreach (string str in strings) + { + WriteLine(str); + } + }; + Commands["help"] = Help; + Commands["start"] = async (args) => + { + if (InGame) + { + return; + } + + InGame = true; + await Start(); + InGame = false; + }; + AddCommandAlias("help", "菜单", "h", "帮助"); + AddCommandAlias("start", "开局"); + AddDialog("柔哥", $"让我再次欢迎您,{username}。入职手续已办理完毕,接下来,您可以使用菜单了。请您记住我的名字:【柔哥】。"); + await Help([]); + while (Running) + { + if (InGame) + { + await Task.Delay(1000); + continue; + } + string input = await GetTextResultsAsync([]); + string[] strings = input.Split(" ", StringSplitOptions.RemoveEmptyEntries); + if (strings.Length == 0) + { + continue; + } + string command = strings[0]; + string[] args = [.. strings.Skip(1)]; + if (Commands.TryGetValue(command.ToLower(), out Func? func) && func != null) + { + await func.Invoke(args); + } + else if (CommandAlias.TryGetValue(command.ToLower(), out string? actualCommand) && actualCommand != null) + { + if (Commands.TryGetValue(actualCommand, out Func? func2) && func2 != null) + { + await func2.Invoke(args); + } + } + else + { + WriteLine("未知指令"); + } + } + } + + public Action WriteLineHandler + { + get => field ?? Console.WriteLine; + set => field = value; + } + + public delegate Task ReadInput(Dictionary args, List results); + public delegate Task ReadInputInGameResponse(InquiryOptions options); + public event ReadInput? ReadInputStringHandler; + public event ReadInput? ReadInputNumberHandler; + public event ReadInputInGameResponse? ReadInputInGameResponseHandler; + + public async Task> GetChoiceResultsAsync(InquiryOptions options, Dictionary args) + { + int index = 0; + Dictionary indexToChoice = []; + foreach (string choice in options.Choices.Keys) + { + index++; + indexToChoice[index] = choice; + WriteLine($"{index}. {choice}:{options.Choices[choice]}"); + } + WriteLine($"--- {options.Topic} ---"); + List results = []; + if (ReadInputStringHandler != null) + { + await ReadInputStringHandler.Invoke(args, results); + } + return [.. indexToChoice.Where(kv => results.Contains(kv.Key.ToString())).Select(kv => kv.Value)]; + } + + public async Task> GetNumberResultsAsync(Dictionary args) + { + List results = []; + if (ReadInputNumberHandler != null) + { + await ReadInputNumberHandler.Invoke(args, results); + } + return results; + } + + public async Task GetTextResultsAsync(Dictionary args) + { + List results = []; + if (ReadInputStringHandler != null) + { + await ReadInputStringHandler.Invoke(args, results); + } + return results.FirstOrDefault() ?? ""; + } + + public async Task DataRequest(DataRequestArgs args) + { + _requestTcs.TrySetCanceled(); + _currentRequestGuid = Guid.NewGuid(); + + _requestTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + await Dispatcher.DataRequest(_currentRequestGuid, args); + + return await _requestTcs.Task; + } + + public async Task DataRequestComplete(Guid guid, DataRequestArgs response) + { + if (guid == _currentRequestGuid) + { + _currentRequestGuid = Guid.Empty; + _requestTcs.TrySetResult(response); + } + } + + public async Task GetInGameResponse(InquiryOptions options) + { + if (ReadInputInGameResponseHandler is null) return new(options); + return await ReadInputInGameResponseHandler.Invoke(options); + } + + public void WriteLine(string message = "") + { + WriteLineHandler(message); + } + + public void AddDialog(string speaker, string message) + { + WriteLine(BuildSpeakerDialog(speaker, message)); + } + + public static string BuildSpeakerDialog(string speaker, string message) + { + return $"【{speaker}】{message}"; + } + + private void AddCommandAlias(string command, params string[] aliases) + { + foreach (string alias in aliases) + { + CommandAlias[alias] = command; + } + } + + public string GetCommandAliases(string command) + { + string[] alias = [.. CommandAlias.Where(kv => kv.Value == command).Select(kv => kv.Key)]; + return alias.Length > 0 ? $"(替代:{string.Join(",", alias)})" : ""; + } + + private async Task Help(string[] args) + { + WriteLine($"可用指令:{string.Join(",", Commands.Keys.Select(c => $"{c}{GetCommandAliases(c)}"))}"); + } + + private async Task Start() + { + await Dispatcher.CreateGameLoop(User.Username); + } + } +} diff --git a/Library/Solutions/RogueLikeServer.cs b/Library/Solutions/RogueLikeServer.cs new file mode 100644 index 0000000..abf6847 --- /dev/null +++ b/Library/Solutions/RogueLikeServer.cs @@ -0,0 +1,387 @@ +using Milimoe.FunGame.Core.Api.Utility; +using Milimoe.FunGame.Core.Entity; +using Milimoe.FunGame.Core.Library.Constant; +using Milimoe.FunGame.Core.Model; +using Milimoe.FunGame.Testing.Tests; +using Oshima.FunGame.OshimaModules.Characters; +using Oshima.FunGame.OshimaModules.Models; +using Oshima.FunGame.OshimaModules.Regions; + +namespace Milimoe.FunGame.Testing.Solutions +{ + public class RogueLikeServer(RogueLikeDispatcher dispatcher) + { + public bool Running => Dispatcher.Running; + public Task? Guard { get; set; } = null; + public RogueLikeDispatcher Dispatcher { get; set; } = dispatcher; + public ConcurrentQueue<(Guid Guid, DataRequestArgs Args)> Inquiries { get; } = []; + public Dictionary Users { get; set; } = []; + public Dictionary RogueLikeGameDatas { get; set; } = []; + + public void ReceiveDataRequest(Guid guid, DataRequestArgs args) + { + Inquiries.Add((guid, args)); + } + + public void WriteLine(string message = "") => Dispatcher.WriteLine(message); + + public async Task DataRequestGuard() + { + while (Running) + { + if (!Inquiries.IsEmpty && Inquiries.GetFirst(out var obj)) + { + DataRequestArgs response = await HandleDataRequest(obj.Args); + await DataRequestCallback(obj.Guid, response); + } + await Task.Delay(50); + } + } + + public async Task CreateGameLoop(string username) + { + Users.TryGetValue(username, out User? user); + user ??= General.UnknownUserInstance; + Character character = user.Inventory.MainCharacter; + RogueLikeGameData data = new(character); + RogueLikeGameDatas[username] = data; + await GameLoop(data); + } + + private async Task GameLoop(RogueLikeGameData data) + { + while (data.RogueState != RogueState.Finish) + { + data.RogueState = await NextRogueState(data, data.RogueState); + if (data.RogueState == RogueState.Init) + { + data.RogueState = RogueState.Finish; + } + await Task.Delay(100); + } + } + + private async Task HandleDataRequest(DataRequestArgs args) + { + DataRequestArgs response = new(args.RequestType); + + switch (response.RequestType) + { + case "createuser": + { + string username = ""; + if (args.Data.TryGetValue("username", out object? value) && value is string s) + { + username = s; + } + User user = Factory.GetUser(1, username); + Character character = new CustomCharacter(user.Id, username) + { + Level = 1 + }; + character.Recovery(); + user.Inventory.Characters.Add(character); + user.Inventory.MainCharacter = character; + Users[username] = user; + response.Data["user"] = user; + break; + } + default: + break; + } + + return response; + } + + private async Task DataRequestCallback(Guid guid, DataRequestArgs response) + { + await Dispatcher.DataRequestComplete(guid, response); + } + + private async Task NextRogueState(RogueLikeGameData data, RogueState state) + { + RogueState newState = RogueState.Init; + + switch (state) + { + case RogueState.Init: + { + newState = RogueState.InArk; + } + break; + case RogueState.InArk: + { + OshimaRegion? region = await ChooseRegion(1); + if (region != null) + { + WriteLine("-- 【永恒方舟计划】进程·Ⅰ:启动 --"); + data.CurrentRegion = region; + data.Chapter1Region = region; + newState = RogueState.Chapter1InArk; + } + else + { + WriteLine("未选择地区,永恒方舟计划终止。"); + newState = RogueState.Finish; + } + } + break; + case RogueState.Chapter1InArk: + { + WriteLine("探索前的准备。"); + await RestInArk(); + newState = RogueState.ExploringChapter1Area1; + } + break; + case RogueState.Chapter2InArk: + { + WriteLine("回到方舟后,我们得到了新的任务。"); + // TODO:提供一个菜单,玩家可以选择第二章的地区(随机从3-4★的地区里抽取3个来选一个) + OshimaRegion? region = await ChooseRegion(2); + if (region != null) + { + WriteLine("-- 【永恒方舟计划】进程·Ⅱ:启动 --"); + data.CurrentRegion = region; + data.Chapter2Region = region; + } + else + { + WriteLine("未选择地区,永恒方舟计划终止。"); + newState = RogueState.Finish; + WriteLine("结算到目前为止的奖励。"); + break; + } + await RestInArk(); + newState = RogueState.ExploringChapter2Area1; + } + break; + case RogueState.Chapter3InArk: + { + OshimaRegion? region = await ChooseRegion(3); + if (region != null) + { + WriteLine("-- 【永恒方舟计划】进程·Ⅲ:启动 --"); + data.CurrentRegion = region; + data.Chapter3Region = region; + } + else + { + WriteLine("未选择地区,永恒方舟计划终止。"); + newState = RogueState.Finish; + WriteLine("结算到目前为止的奖励。"); + break; + } + await RestInArk(); + newState = RogueState.ExploringChapter3Area1; + } + break; + case RogueState.FinalInArk: + WriteLine("-- 【永恒方舟计划】紧急事件·夺还:启动 --"); + newState = RogueState.ExploringArk; + break; + case RogueState.ExploringChapter1Area1: + newState = await ExploreRegion(data, 1, 1); + break; + case RogueState.ExploringChapter1Area2: + newState = await ExploreRegion(data, 1, 2); + break; + case RogueState.ExploringChapter2Area1: + newState = await ExploreRegion(data, 2, 1); + break; + case RogueState.ExploringChapter2Area2: + newState = await ExploreRegion(data, 2, 2); + break; + case RogueState.ExploringChapter3Area1: + newState = await ExploreRegion(data, 3, 1); + break; + case RogueState.ExploringChapter3Area2: + newState = await ExploreRegion(data, 3, 2); + break; + case RogueState.ExploringArk: + await ExploreArk(); + newState = RogueState.FinalBossBattle; + break; + case RogueState.Chapter1BossBattle: + { + WriteLine("剧情过后,战斗一触即发。"); + // TODO:调用FunGameActionQueue的战斗系统 + WriteLine("战胜了BOSS,返回方舟汇报工作,并进行战后整备!"); + newState = RogueState.Chapter2InArk; + } + break; + case RogueState.Chapter2BossBattle: + { + WriteLine("剧情过后,战斗一触即发。"); + // TODO:调用FunGameActionQueue的战斗系统 + WriteLine("战胜了BOSS,返回方舟汇报工作,并进行战后整备!"); + newState = RogueState.Chapter3InArk; + } + break; + case RogueState.Chapter3BossBattle: + { + WriteLine("剧情过后,战斗一触即发。"); + // TODO:调用FunGameActionQueue的战斗系统 + WriteLine("战胜了BOSS,返回方舟汇报工作,并进行战后整备!"); + newState = RogueState.FinalInArk; + } + break; + case RogueState.FinalBossBattle: + { + WriteLine("剧情过后,战斗一触即发。"); + // TODO:调用FunGameActionQueue的战斗系统 + WriteLine("战胜了BOSS,永恒方舟成功夺还!"); + WriteLine("本次【永恒方舟计划】成功!"); + newState = RogueState.Finish; + } + break; + case RogueState.Finish: + break; + default: + break; + } + + return newState; + } + + private async Task ChooseRegion(int chapter) + { + string topic = chapter switch + { + 1 => "选择初始探索地区", + _ => "选择下一个探索地区", + }; + Func predicate = chapter switch + { + 2 => r => (int)r.Difficulty >= (int)RarityType.ThreeStar && (int)r.Difficulty <= (int)RarityType.FourStar, + 3 => r => (int)r.Difficulty == (int)RarityType.FiveStar, + _ => r => (int)r.Difficulty <= (int)RarityType.TwoStar + }; + InquiryOptions options = new(InquiryType.Choice, topic) + { + Choices = FunGameConstant.Regions.Where(predicate).ToDictionary(r => r.Name, r => r.ToString()) + }; + InquiryResponse response = await Dispatcher.GetInGameResponse(options); + if (!response.Cancel) + { + if (response.Choices.FirstOrDefault() is string regionName) + { + OshimaRegion? region = FunGameConstant.Regions.FirstOrDefault(r => r.Name == regionName); + if (region != null && region.Areas.Count > 0) + { + return region; + } + } + } + return null; + } + + private async Task ExploreRegion(RogueLikeGameData data, int chapter, int areaSeq) + { + RogueState newState = RogueState.Finish; + if (data.CurrentRegion is null) + { + WriteLine("突发!小队和方舟失联了!永恒方舟计划终止。"); + return newState; + } + if (areaSeq == 1) + { + WriteLine("正在降落..."); + data.CurrentArea = data.CurrentRegion.Areas.OrderByDescending(o => Random.Shared.Next()).First(); + WriteLine($"在【{data.CurrentRegion.Name}】的【{data.CurrentArea}】区域完成降落!"); + } + else if (areaSeq == 2) + { + WriteLine("该地区的第一个探索区域任务已完成!正在前往第二个区域……"); + data.CurrentArea = data.CurrentRegion.Areas.OrderByDescending(o => Random.Shared.Next()).First(a => a != data.CurrentArea); + WriteLine($"在【{data.CurrentRegion.Name}】的【{data.CurrentArea}】区域完成降落!"); + } + else + { + WriteLine("你误入了神秘地带,与方舟失联,游戏结束。"); + return newState; + } + bool fin = false; + while (!fin) + { + // TODO:开始探索区域,主要抉择 + fin = true; + } + if (areaSeq == 1) + { + newState = chapter switch + { + 1 => RogueState.ExploringChapter1Area2, + 2 => RogueState.ExploringChapter2Area2, + 3 => RogueState.ExploringChapter3Area2, + _ => RogueState.Finish, + }; + } + else if (areaSeq == 2) + { + WriteLine("BOSS房间出现了!做好准备再继续出发吧。"); + fin = false; + while (!fin) + { + // TODO:BOSS房前的准备,提供菜单 + fin = true; + } + newState = chapter switch + { + 1 => RogueState.Chapter1BossBattle, + 2 => RogueState.Chapter2BossBattle, + 3 => RogueState.Chapter3BossBattle, + _ => RogueState.Finish, + }; + } + else + { + WriteLine("你误入了神秘地带,与方舟失联,游戏结束。"); + } + return newState; + } + + private async Task RestInArk() + { + bool fin = false; + while (!fin) + { + // TODO:战后整备,提供菜单回复和提升等 + fin = true; + } + WriteLine("出发!"); + } + + private async Task ExploreArk() + { + bool fin = false; + while (!fin) + { + // TODO:方舟事变,需进行方舟探索和收复功能房间并找到最终BOSS房间 + fin = true; + } + WriteLine("出发!"); + } + } + + public enum RogueState + { + Init, + InArk, + Chapter1InArk, + Chapter2InArk, + Chapter3InArk, + FinalInArk, + ExploringChapter1Area1, + ExploringChapter1Area2, + ExploringChapter2Area1, + ExploringChapter2Area2, + ExploringChapter3Area1, + ExploringChapter3Area2, + ExploringArk, + Chapter1BossBattle, + Chapter2BossBattle, + Chapter3BossBattle, + FinalBossBattle, + Finish + } +} diff --git a/Library/Tests/FunGameBO5.cs b/Library/Tests/FunGameBO5.cs index 5549d49..eb9befa 100644 --- a/Library/Tests/FunGameBO5.cs +++ b/Library/Tests/FunGameBO5.cs @@ -100,6 +100,24 @@ namespace Milimoe.FunGame.Testing.Tests Console.WriteLine($"{userTeams[u2]} 禁用了 {x}"); } } + else if (i == 6 || i == 7) + { + if (ban.Count < 4) + { + Character? x = xx.FirstOrDefault(c => !ban.Contains(c)); + if (x is null) continue; + if (i % 2 != 0) + { + ban.Add(x); + Console.WriteLine($"{userTeams[u1]} 禁用了 {x}"); + } + else if (i % 2 == 0) + { + ban.Add(x); + Console.WriteLine($"{userTeams[u2]} 禁用了 {x}"); + } + } + } else { // 选秀 @@ -109,7 +127,7 @@ namespace Milimoe.FunGame.Testing.Tests c.Level = 60; c.NormalAttack.Level = 8; FunGameService.AddCharacterSkills(c, 1, 6, 6); - if (i % 2 == 0 && t1.Count < 5) + if ((i < 7 ? i % 2 == 0 : i % 2 != 0) && t1.Count < 5) { User? uu = u1.FirstOrDefault(u => !t1.ContainsKey(u)); if (uu is null) continue; @@ -119,7 +137,7 @@ namespace Milimoe.FunGame.Testing.Tests Console.WriteLine($"{userTeams[u1]}.{uu} 选择了 {c}"); c.User = uu; } - if (i % 2 != 0 && t2.Count < 5) + if ((i < 7 ? i % 2 != 0 : i % 2 == 0) && t2.Count < 5) { User? uu = u2.FirstOrDefault(u => !t2.ContainsKey(u)); if (uu is null) continue; @@ -148,8 +166,8 @@ namespace Milimoe.FunGame.Testing.Tests DropItems(team2.Members); team1.Members.ForEach(c => c.Recovery()); team2.Members.ForEach(c => c.Recovery()); - FunGameActionQueue queue = new(); - List msgs = await queue.StartTeamGame(teams, -1, 30); + FunGameActionQueue queue = await FunGameActionQueue.NewAndStartTeamGame(teams, -1, 30); + List msgs = queue.Result; foreach (Character character in queue.GamingQueue.CharacterStatistics.Keys) { FunGameTesting.UpdateStatistics(stats[character.User], queue.GamingQueue.CharacterStatistics[character]); diff --git a/Library/Tests/RogueLikeTest.cs b/Library/Tests/RogueLikeTest.cs new file mode 100644 index 0000000..78ee429 --- /dev/null +++ b/Library/Tests/RogueLikeTest.cs @@ -0,0 +1,264 @@ +using Milimoe.FunGame.Core.Entity; +using Milimoe.FunGame.Core.Model; +using Milimoe.FunGame.Testing.Solutions; +using Oshima.FunGame.OshimaModules.Regions; + +namespace Milimoe.FunGame.Testing.Tests +{ + /// + /// 肉鸽客户端原型测试 + /// + public class RogueLikeTest + { + public RogueLike RogueLike { get; set; } + public RogueLikeServer RogueLikeServer { get; set; } + + public RogueLikeTest() + { + RogueLikeDispatcher dispatcher = new(); + RogueLike = new(dispatcher); + RogueLikeServer = new(dispatcher); + dispatcher.RogueLikeInstance = RogueLike; + dispatcher.RogueLikeServer = RogueLikeServer; + RogueLike.ReadInputStringHandler += RogueLike_ReadInputStringHandler; + RogueLike.ReadInputNumberHandler += RogueLike_ReadInputNumberHandler; + RogueLike.ReadInputInGameResponseHandler += RogueLike_ReadInputInGameResponseHandler; + } + + /// + /// 此方法完全自主处理options并输入 + /// + /// + /// + private async Task RogueLike_ReadInputInGameResponseHandler(InquiryOptions options) + { + InquiryResponse response = new(options); + + switch (options.InquiryType) + { + case Core.Library.Constant.InquiryType.Choice: + case Core.Library.Constant.InquiryType.BinaryChoice: + { + int index = 0; + Dictionary indexToChoice = []; + foreach (string choice in options.Choices.Keys) + { + index++; + indexToChoice[index] = choice; + RogueLike.WriteLine($"{index}. {choice}:{options.Choices[choice]}"); + } + RogueLike.WriteLine($"--- {options.Topic} ---"); + bool resolve = false; + while (!resolve) + { + RogueLike.WriteLine($"选择一个选项(输入序号,输入 !c 表示取消):"); + string result = (await Console.In.ReadLineAsync())?.Trim() ?? ""; + if (result == "!c") + { + response.Cancel = true; + resolve = true; + } + else if (int.TryParse(result, out int inputIndex) && indexToChoice.TryGetValue(inputIndex, out string? value)) + { + response.Choices = [value]; + resolve = true; + } + } + break; + } + case Core.Library.Constant.InquiryType.MultipleChoice: + { + int index = 0; + Dictionary indexToChoice = []; + foreach (string choice in options.Choices.Keys) + { + index++; + indexToChoice[index] = choice; + RogueLike.WriteLine($"{index}. {choice}:{options.Choices[choice]}"); + } + RogueLike.WriteLine($"--- {options.Topic} ---"); + bool resolve = false; + while (!resolve) + { + RogueLike.WriteLine($"选择多个选项(输入序号,空格分隔,包含 !c 时表示取消):"); + string result = (await Console.In.ReadLineAsync())?.Trim() ?? ""; + if (result.Contains("!c")) + { + response.Cancel = true; + break; + } + string[] strings = result.Split(' ', StringSplitOptions.RemoveEmptyEntries); + foreach (string str in strings) + { + if (int.TryParse(str, out int inputIndex) && indexToChoice.TryGetValue(inputIndex, out string? value)) + { + response.Choices.Add(value); + } + } + if (response.Choices.Count > 0) + { + resolve = true; + } + } + break; + } + case Core.Library.Constant.InquiryType.TextInput: + { + RogueLike.WriteLine($"--- {options.Topic} ---"); + bool resolve = false; + while (!resolve) + { + RogueLike.WriteLine("输入回应(输入 !c 表示取消):"); + string result = (await Console.In.ReadLineAsync())?.Trim() ?? ""; + if (result == "!c") + { + response.Cancel = true; + resolve = true; + } + else if (result != "") + { + response.TextResult = result; + resolve = true; + } + } + break; + } + case Core.Library.Constant.InquiryType.NumberInput: + { + RogueLike.WriteLine($"--- {options.Topic} ---"); + bool resolve = false; + while (!resolve) + { + RogueLike.WriteLine("输入结果(输入 !c 表示取消):"); + string result = await Console.In.ReadLineAsync() ?? ""; + if (result.Trim() == "!c") + { + response.Cancel = true; + resolve = true; + } + else if (double.TryParse(result, out double doubleResult)) + { + response.NumberResult = doubleResult; + resolve = true; + } + } + break; + } + case Core.Library.Constant.InquiryType.Custom: + break; + default: + break; + } + + return response; + } + + private async Task RogueLike_ReadInputStringHandler(Dictionary args, List results) + { + string input = await Console.In.ReadLineAsync() ?? ""; + if (input.Trim().Equals("!c", StringComparison.CurrentCultureIgnoreCase)) + { + args["cancel"] = true; + } + results.Add(input.Trim()); + } + + private async Task RogueLike_ReadInputNumberHandler(Dictionary args, List results) + { + string input = await Console.In.ReadLineAsync() ?? ""; + if (input.Trim().Equals("!c", StringComparison.CurrentCultureIgnoreCase)) + { + args["cancel"] = true; + } + if (double.TryParse(input.Trim(), out double result)) + { + results.Add(result); + } + } + } + + /// + /// 仅本地测试原型用,实际使用时需替换为网络层 + /// + public class RogueLikeDispatcher + { + public RogueLike? RogueLikeInstance { get; set; } = null; + public RogueLikeServer? RogueLikeServer { get; set; } = null; + + public bool Running => RogueLikeInstance?.Running ?? false; + + public void WriteLine(string str) + { + if (RogueLikeInstance is null) return; + RogueLikeInstance.WriteLine(str); + } + + public async Task> GetChoiceResultsAsync(InquiryOptions options, Dictionary args) + { + if (RogueLikeInstance is null) return []; + return await RogueLikeInstance.GetChoiceResultsAsync(options, args); + } + + public async Task> GetNumberResultsAsync(Dictionary args) + { + if (RogueLikeInstance is null) return []; + return await RogueLikeInstance.GetNumberResultsAsync(args); + } + + public async Task GetTextResultsAsync(Dictionary args) + { + if (RogueLikeInstance is null) return ""; + return await RogueLikeInstance.GetTextResultsAsync(args); + } + + public async Task GetInGameResponse(InquiryOptions options) + { + if (RogueLikeInstance is null) return new(options); + return await RogueLikeInstance.GetInGameResponse(options); + } + + public async Task DataRequestComplete(Guid guid, DataRequestArgs response) + { + if (RogueLikeInstance is null) return; + await RogueLikeInstance.DataRequestComplete(guid, response); + } + + public async Task DataRequest(Guid guid, DataRequestArgs args) + { + if (RogueLikeServer is null) return; + RogueLikeServer.ReceiveDataRequest(guid, args); + await Task.CompletedTask; + } + + public void StartServer() + { + RogueLikeServer?.Guard ??= Task.Run(RogueLikeServer.DataRequestGuard); + } + + public async Task CreateGameLoop(string username) + { + if (RogueLikeServer is null) return; + await RogueLikeServer.CreateGameLoop(username); + } + } + + public class RogueLikeGameData(Character character) + { + public RogueState RogueState { get; set; } = RogueState.Init; + public Character Character { get; set; } = character; + public int Chapter { get; set; } = 1; + public OshimaRegion? CurrentRegion { get; set; } = null; + public string CurrentArea { get; set; } = ""; + public int RoomId { get; set; } = -1; + public OshimaRegion? Chapter1Region { get; set; } = null; + public OshimaRegion? Chapter2Region { get; set; } = null; + public OshimaRegion? Chapter3Region { get; set; } = null; + } + + public class DataRequestArgs(string type) + { + public string RequestType { get; } = type; + public bool Success { get; set; } = true; + public Dictionary Data { get; set; } = []; + } +}