diff --git a/Desktop/App.xaml b/Desktop/App.xaml
new file mode 100644
index 0000000..5e6c830
--- /dev/null
+++ b/Desktop/App.xaml
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/Desktop/App.xaml.cs b/Desktop/App.xaml.cs
new file mode 100644
index 0000000..0273963
--- /dev/null
+++ b/Desktop/App.xaml.cs
@@ -0,0 +1,12 @@
+using Application = System.Windows.Application;
+
+namespace Milimoe.FunGame.Testing.Desktop
+{
+ public partial class App : Application
+ {
+ public App()
+ {
+
+ }
+ }
+}
diff --git a/Desktop/FunGame.Testing.Desktop.csproj b/Desktop/FunGame.Testing.Desktop.csproj
index 1f93424..acfa424 100644
--- a/Desktop/FunGame.Testing.Desktop.csproj
+++ b/Desktop/FunGame.Testing.Desktop.csproj
@@ -8,6 +8,7 @@
enable
..\bin\
Milimoe.$(MSBuildProjectName.Replace(" ", "_"))
+ True
@@ -22,6 +23,10 @@
+
+
+
+
..\..\FunGame.Core\bin\Debug\net9.0\FunGame.Core.dll
diff --git a/Desktop/Program.cs b/Desktop/Program.cs
index f971433..cd073c1 100644
--- a/Desktop/Program.cs
+++ b/Desktop/Program.cs
@@ -1,20 +1,20 @@
-using Milimoe.FunGame.Testing.Desktop.Solutions;
+//using Milimoe.FunGame.Testing.Desktop.Solutions;
-namespace Desktop
-{
- internal static class Program
- {
- ///
- /// The main entry point for the application.
- ///
- [STAThread]
- static void Main()
- {
- // To customize application configuration such as set high DPI settings or default font,
- // see https://aka.ms/applicationconfiguration.
- ApplicationConfiguration.Initialize();
- //Application.Run(new ChessBoardExample.ChessBoardExample());
- Application.Run(new EntityEditor());
- }
- }
-}
\ No newline at end of file
+//namespace Desktop
+//{
+// internal static class Program
+// {
+// ///
+// /// The main entry point for the application.
+// ///
+// [STAThread]
+// static void Main()
+// {
+// // To customize application configuration such as set high DPI settings or default font,
+// // see https://aka.ms/applicationconfiguration.
+// ApplicationConfiguration.Initialize();
+// //Application.Run(new ChessBoardExample.ChessBoardExample());
+// Application.Run(new EntityEditor());
+// }
+// }
+//}
\ No newline at end of file
diff --git a/Desktop/Solutions/NovelEditor/NovelEditor.xaml b/Desktop/Solutions/NovelEditor/NovelEditor.xaml
new file mode 100644
index 0000000..1a02d3c
--- /dev/null
+++ b/Desktop/Solutions/NovelEditor/NovelEditor.xaml
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Desktop/Solutions/NovelEditor/NovelEditor.xaml.cs b/Desktop/Solutions/NovelEditor/NovelEditor.xaml.cs
new file mode 100644
index 0000000..ff0f38b
--- /dev/null
+++ b/Desktop/Solutions/NovelEditor/NovelEditor.xaml.cs
@@ -0,0 +1,218 @@
+using System.Windows;
+using System.Windows.Controls;
+using Milimoe.FunGame.Core.Api.Utility;
+using Milimoe.FunGame.Core.Entity;
+using Milimoe.FunGame.Core.Model;
+using Button = System.Windows.Controls.Button;
+using MessageBox = System.Windows.MessageBox;
+
+namespace Milimoe.FunGame.Testing.Desktop.Solutions.NovelEditor
+{
+ ///
+ /// NovelEditor.xaml 的交互逻辑
+ ///
+ public partial class NovelEditor : Window
+ {
+ public static Dictionary Likability { get; } = [];
+ public static Dictionary> Conditions { get; } = [];
+ public static Character MainCharacter { get; set; } = Factory.GetCharacter();
+ public static Character 马猴烧酒 { get; set; } = Factory.GetCharacter();
+
+ private readonly NovelConfig _config;
+
+ public NovelEditor()
+ {
+ InitializeComponent();
+
+ MainCharacter.Name = "主角";
+ MainCharacter.NickName = "主角";
+ 马猴烧酒.Name = "马猴烧酒";
+ 马猴烧酒.NickName = "魔法少女";
+ Likability.Add(马猴烧酒, 100);
+
+ Conditions.Add("马猴烧酒的好感度低于50", () => 好感度低于50(马猴烧酒));
+ Conditions.Add("主角攻击力大于20", () => 攻击力大于20(MainCharacter));
+ Conditions.Add("马猴烧酒攻击力大于20", () => 攻击力大于20(马猴烧酒));
+
+ // 如果需要,初始化小说
+ //NovelTest.CreateNovels();
+
+ // 小说配置
+ _config = new NovelConfig("novel1", "chapter1");
+ LoadNovelData();
+
+ // 绑定节点列表
+ NodeListBox.ItemsSource = _config.Values;
+ if (_config.Count > 0)
+ {
+ NodeListBox.SelectedIndex = 0;
+ }
+ }
+
+ private void LoadNovelData()
+ {
+ // 加载已有的小说数据
+ _config.LoadConfig(Conditions);
+ }
+
+ private void NodeListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
+ {
+ if (NodeListBox.SelectedItem is NovelNode selectedNode)
+ {
+ // 显示节点详情
+ NodeKeyText.Text = "编号:" + selectedNode.Key;
+ NodeNameText.Text = "角色:" + selectedNode.Name;
+ NodeContentText.Text = "文本:" + selectedNode.Content;
+
+ // 显示节点的显示条件
+ NodeConditionsText.Text = FormatConditions(selectedNode);
+
+ // 显示选项
+ OptionsListBox.ItemsSource = selectedNode.Options.Select(option => new
+ {
+ option.Name,
+ option.Targets,
+ Conditions = FormatOptionConditions(option)
+ }).ToList();
+ }
+ }
+ private void PreviousSentence_Click(object sender, RoutedEventArgs e)
+ {
+ if (NodeListBox.SelectedItem is NovelNode selectedNode)
+ {
+ if (selectedNode.Previous is null)
+ {
+ if (NodeListBox.SelectedIndex - 1 >= 0)
+ {
+ MessageBox.Show("此节点没有定义上一个节点!现在跳转列表的上一个节点。");
+ NodeListBox.SelectedIndex--;
+ }
+ else
+ {
+ MessageBox.Show("此节点没有定义上一个节点,并且已经达到列表顶部!");
+ }
+ }
+ else
+ {
+ NodeListBox.SelectedItem = selectedNode.Previous;
+ }
+ }
+ }
+
+ private void NextSentence_Click(object sender, RoutedEventArgs e)
+ {
+ if (NodeListBox.SelectedItem is NovelNode selectedNode)
+ {
+ NovelNode? next = selectedNode.Next;
+ if (next is null)
+ {
+ if (NodeListBox.SelectedIndex + 1 < NodeListBox.Items.Count)
+ {
+ MessageBox.Show("此节点没有定义下一个节点!现在跳转列表的下一个节点。");
+ NodeListBox.SelectedIndex++;
+ }
+ else
+ {
+ MessageBox.Show("此节点没有定义下一个节点,并且已经到达列表底部!现在跳转到列表的起始处。");
+ NodeListBox.SelectedIndex = 0;
+ }
+ }
+ else
+ {
+ NodeListBox.SelectedItem = next;
+ }
+ }
+ }
+
+ private static string FormatConditions(NovelNode node)
+ {
+ List conditions = [];
+
+ if (node.AndPredicates.Count != 0)
+ {
+ conditions.Add("需满足以下所有条件:");
+ foreach (string condition in node.AndPredicates.Keys)
+ {
+ conditions.Add($"- {condition}");
+ }
+ }
+
+ if (node.OrPredicates.Count != 0)
+ {
+ conditions.Add("需满足以下任意一个条件:");
+ foreach (string condition in node.OrPredicates.Keys)
+ {
+ conditions.Add($"- {condition}");
+ }
+ }
+
+ return conditions.Count != 0 ? string.Join(Environment.NewLine, conditions) : "无显示条件";
+ }
+
+ private static string FormatOptionConditions(NovelOption option)
+ {
+ List conditions = [];
+
+ if (option.AndPredicates.Count != 0)
+ {
+ conditions.Add("需满足以下所有条件:");
+ foreach (string condition in option.AndPredicates.Keys)
+ {
+ conditions.Add($"- {condition}");
+ }
+ }
+
+ if (option.OrPredicates.Count != 0)
+ {
+ conditions.Add("需满足以下任意一个条件:");
+ foreach (string condition in option.OrPredicates.Keys)
+ {
+ conditions.Add($"- {condition}");
+ }
+ }
+
+ return conditions.Count != 0 ? string.Join(Environment.NewLine, conditions) : "";
+ }
+
+ private void AddNodeButton_Click(object sender, RoutedEventArgs e)
+ {
+ NovelNode newNode = new()
+ {
+ Key = "示例节点编号",
+ Name = "示例发言人",
+ Content = "示例发言内容"
+ };
+
+ _config.Add(newNode.Key, newNode);
+ NodeListBox.Items.Refresh();
+ }
+
+ private void EditNodeButton_Click(object sender, RoutedEventArgs e)
+ {
+ if (NodeListBox.SelectedItem is NovelNode selectedNode)
+ {
+ selectedNode.Content = "此示例节点已被编辑";
+ NodeContentText.Text = "文本:" + selectedNode.Content;
+ }
+ }
+
+ private void OptionButton_Click(object sender, RoutedEventArgs e)
+ {
+ if (sender is Button button && button.Tag is NovelNode node && NodeListBox.Items.OfType().FirstOrDefault(n => n.Key == node.Key) is NovelNode target)
+ {
+ NodeListBox.SelectedItem = target;
+ NodeListBox.ScrollIntoView(target);
+ }
+ }
+
+ public static bool 攻击力大于20(Character character)
+ {
+ return character.ATK > 20;
+ }
+
+ public static bool 好感度低于50(Character character)
+ {
+ return Likability[character] > 50;
+ }
+ }
+}
diff --git a/Desktop/Solutions/NovelEditor/NovelTest.cs b/Desktop/Solutions/NovelEditor/NovelTest.cs
new file mode 100644
index 0000000..884bcba
--- /dev/null
+++ b/Desktop/Solutions/NovelEditor/NovelTest.cs
@@ -0,0 +1,104 @@
+using Milimoe.FunGame.Core.Api.Utility;
+using Milimoe.FunGame.Core.Model;
+
+namespace Milimoe.FunGame.Testing.Desktop.Solutions.NovelEditor
+{
+ internal class NovelTest
+ {
+ internal static void CreateNovels()
+ {
+ NovelNode node1 = new()
+ {
+ Key = "node1",
+ Name = "声音",
+ Content = "听说你在等我?我来了!"
+ };
+ NovelNode node2 = new()
+ {
+ Key = "node2",
+ Name = NovelEditor.MainCharacter.NickName,
+ Content = "什么人!"
+ };
+ node1.NextNodes.Add(node2);
+ node2.Previous = node1;
+ NovelOption option1 = new()
+ {
+ Key = "option1",
+ Name = "你好。"
+ };
+ NovelOption option2 = new()
+ {
+ Key = "option2",
+ Name = "我不认识你。",
+ AndPredicates = new()
+ {
+ { "主角攻击力大于20", NovelEditor.Conditions["主角攻击力大于20"] }
+ }
+ };
+ NovelNode node3 = new()
+ {
+ Key = "node3",
+ Name = NovelEditor.马猴烧酒.NickName,
+ Content = "你好,我叫【马猴烧酒】!",
+ Options = [option1, option2]
+ };
+ NovelNode node4 = new()
+ {
+ Key = "node4",
+ Name = NovelEditor.马猴烧酒.NickName,
+ Content = "你的名字是?"
+ };
+ NovelNode node5 = new()
+ {
+ Key = "node5",
+ Name = NovelEditor.马猴烧酒.NickName,
+ Content = "滚,谁要认识你?"
+ };
+ node2.NextNodes.Add(node3);
+ option1.Targets.Add(node4);
+ option2.Targets.Add(node5);
+ NovelNode node6 = new()
+ {
+ Key = "node6",
+ Content = "旁白:示例结束。"
+ };
+ NovelOption option3 = new()
+ {
+ Key = "option3",
+ Name = "重新开始游戏",
+ Targets = [node1]
+ };
+ NovelNode node7 = new()
+ {
+ Key = "node7",
+ Priority = 2,
+ Content = "旁白:示例结束,你被马猴烧酒吃掉了。",
+ Options = [option3],
+ AndPredicates = new()
+ {
+ { "主角攻击力大于20", NovelEditor.Conditions["主角攻击力大于20"] }
+ },
+ OrPredicates = new()
+ {
+ { "马猴烧酒的好感度低于50", NovelEditor.Conditions["马猴烧酒的好感度低于50"] },
+ { "马猴烧酒攻击力大于20", NovelEditor.Conditions["马猴烧酒攻击力大于20"] }
+ }
+ };
+ node4.NextNodes.Add(node6);
+ node5.NextNodes.Add(node6);
+ node5.NextNodes.Add(node7);
+
+ NovelConfig config = new("novel1", "chapter1")
+ {
+ { node1.Key, node1 },
+ { node2.Key, node2 },
+ { node3.Key, node3 },
+ { node4.Key, node4 },
+ { node5.Key, node5 },
+ { node6.Key, node6 },
+ { node7.Key, node7 }
+ };
+ config.SaveConfig();
+ }
+ }
+}
diff --git a/Library/Main.cs b/Library/Main.cs
index ddb0af1..6f36253 100644
--- a/Library/Main.cs
+++ b/Library/Main.cs
@@ -4,7 +4,7 @@ using Oshima.FunGame.OshimaModules;
using Oshima.FunGame.OshimaServers.Service;
using Oshima.FunGame.WebAPI.Controllers;
-//7
+_ = new Milimoe.FunGame.Testing.Solutions.Novels();
//_ = new Milimoe.FunGame.Testing.Tests.CheckDLL();
diff --git a/Library/Solutions/Novels.cs b/Library/Solutions/Novels.cs
new file mode 100644
index 0000000..aac7ded
--- /dev/null
+++ b/Library/Solutions/Novels.cs
@@ -0,0 +1,214 @@
+using System.Text;
+using Milimoe.FunGame.Core.Api.Utility;
+using Milimoe.FunGame.Core.Entity;
+using Milimoe.FunGame.Core.Model;
+using MilimoeFunGame.Testing.Characters;
+
+namespace Milimoe.FunGame.Testing.Solutions
+{
+ public class Novels
+ {
+ public static Dictionary Likability { get; } = [];
+ public static Dictionary> Conditions { get; } = [];
+
+ public static bool 攻击力大于20(Character character)
+ {
+ return character.ATK > 20;
+ }
+
+ public static bool 好感度低于50(Character character)
+ {
+ return Likability[character] > 50;
+ }
+
+ public Novels()
+ {
+ Character main = OshimaCharacters.Oshima;
+ Character character = OshimaCharacters.马猴烧酒;
+ Likability.Add(character, 100);
+
+ Conditions.Add("马猴烧酒的好感度低于50", () => 好感度低于50(character));
+ Conditions.Add("主角攻击力大于20", () => 攻击力大于20(main));
+ Conditions.Add("马猴烧酒攻击力大于20", () => 攻击力大于20(character));
+ NovelNode node1 = new()
+ {
+ Key = "node1",
+ Name = "声音",
+ Content = "听说你在等我?我来了!"
+ };
+ NovelNode node2 = new()
+ {
+ Key = "node2",
+ Name = main.NickName,
+ Content = "什么人!"
+ };
+ node1.NextNodes.Add(node2);
+ node2.Previous = node1;
+ NovelOption option1 = new()
+ {
+ Key = "option1",
+ Name = "你好。"
+ };
+ NovelOption option2 = new()
+ {
+ Key = "option2",
+ Name = "我不认识你。",
+ AndPredicates = new()
+ {
+ { "主角攻击力大于20", Conditions["主角攻击力大于20"] }
+ }
+ };
+ NovelNode node3 = new()
+ {
+ Key = "node3",
+ Name = character.NickName,
+ Content = "你好,我叫【马猴烧酒】!",
+ Options = [option1, option2]
+ };
+ NovelNode node4 = new()
+ {
+ Key = "node4",
+ Name = character.NickName,
+ Content = "你的名字是?"
+ };
+ NovelNode node5 = new()
+ {
+ Key = "node5",
+ Name = character.NickName,
+ Content = "滚,谁要认识你?"
+ };
+ node2.NextNodes.Add(node3);
+ option1.Targets.Add(node4);
+ option2.Targets.Add(node5);
+ NovelNode node6 = new()
+ {
+ Key = "node6",
+ Content = "旁白:示例结束。"
+ };
+ NovelNode node7 = new()
+ {
+ Key = "node7",
+ Priority = 2,
+ Content = "旁白:示例结束,你被马猴烧酒吃掉了。",
+ AndPredicates = new()
+ {
+ { "主角攻击力大于20", Conditions["主角攻击力大于20"] }
+ },
+ OrPredicates = new()
+ {
+ { "马猴烧酒的好感度低于50", Conditions["马猴烧酒的好感度低于50"] },
+ { "马猴烧酒攻击力大于20", Conditions["马猴烧酒攻击力大于20"] }
+ }
+ };
+ node4.NextNodes.Add(node6);
+ node5.NextNodes.Add(node6);
+ node5.NextNodes.Add(node7);
+
+ NovelConfig config = new("novel1", "chapter1")
+ {
+ { node1.Key, node1 },
+ { node2.Key, node2 },
+ { node3.Key, node3 },
+ { node4.Key, node4 },
+ { node5.Key, node5 },
+ { node6.Key, node6 },
+ { node7.Key, node7 }
+ };
+ config.SaveConfig();
+
+ NovelConfig config2 = new("novel1", "chapter1");
+ config2.LoadConfig(Conditions);
+
+ foreach (NovelNode node in config2.Values)
+ {
+ StringBuilder builder = new();
+ builder.AppendLine("== 节点:" + node.Key + " ==");
+
+ if (node.AndPredicates.Union(node.OrPredicates).Any())
+ {
+ builder.AppendLine("对话触发条件(需满足以下所有条件):");
+ int count = 0;
+ if (node.AndPredicates.Count > 0)
+ {
+ count++;
+ builder.AppendLine(count + ". " + "满足以下所有子条件:");
+ int subCount = 0;
+ foreach (string ap in node.AndPredicates.Keys)
+ {
+ subCount++;
+ builder.AppendLine("(" + subCount + ") " + ap);
+ }
+ }
+ if (node.OrPredicates.Count > 0)
+ {
+ count++;
+ builder.AppendLine(count + ". " + "满足以下任意一个子条件:");
+ int subCount = 0;
+ foreach (string op in node.OrPredicates.Keys)
+ {
+ subCount++;
+ builder.AppendLine("(" + subCount + ") " + op);
+ }
+ }
+ }
+
+ if (node.Name != "")
+ {
+ builder.Append(node.Name + "说:");
+ }
+ builder.AppendLine(node.Content);
+
+ if (node.Options.Count > 0)
+ {
+ builder.AppendLine("选项:");
+ int count = 0;
+ foreach (NovelOption option in node.Options)
+ {
+ count++;
+ builder.AppendLine(count + ". " + option.Name + "【可跳转:" + string.Join(",", option.Targets.Select(t => t.Key)) + "】");
+ if (option.AndPredicates.Union(option.OrPredicates).Any())
+ {
+ builder.AppendLine("选项显示条件(需满足以下所有条件):");
+ int optionCount = 0;
+ if (option.AndPredicates.Count > 0)
+ {
+ optionCount++;
+ builder.AppendLine(optionCount + ". " + "满足以下所有子条件:");
+ int subCount = 0;
+ foreach (string ap in option.AndPredicates.Keys)
+ {
+ subCount++;
+ builder.AppendLine("(" + subCount + ") " + ap);
+ }
+ }
+ if (option.OrPredicates.Count > 0)
+ {
+ optionCount++;
+ builder.AppendLine(optionCount + ". " + "满足以下任意一个子条件:");
+ int subCount = 0;
+ foreach (string op in option.OrPredicates.Keys)
+ {
+ subCount++;
+ builder.AppendLine("(" + subCount + ") " + op);
+ }
+ }
+ }
+ }
+ }
+
+ if (node.NextNodes.Count > 0)
+ {
+ builder.AppendLine("下一句对话:");
+ int count = 0;
+ foreach (NovelNode next in node.NextNodes)
+ {
+ count++;
+ builder.AppendLine(count + ". " + next.Key);
+ }
+ }
+
+ Console.WriteLine(builder.ToString());
+ }
+ }
+ }
+}