diff --git a/DataLoader.cs b/DataLoader.cs index 526ee26..b868da8 100644 --- a/DataLoader.cs +++ b/DataLoader.cs @@ -3,7 +3,6 @@ using System.Threading.Tasks; using System.IO; using System; -using System.Windows; using UndertaleModLib.Util; using System.Linq; using Microsoft.Win32; @@ -20,8 +19,10 @@ public class DataLoader public static UndertaleData data = new(); internal static string dataPath = ""; internal static string savedDataPath = ""; + internal static string exportPath = ""; public delegate void FileMessageEventHandler(string message); public static event FileMessageEventHandler FileMessageEvent; + public static int LastCountContext; public static void ShowWarning(string warning, string title) { Console.WriteLine(title + ":" + warning); @@ -54,17 +55,17 @@ private static void ExportData() File.WriteAllText("json_dump_code.json", JsonConvert.SerializeObject(data.Code.Select(t => t.Name.Content))); File.WriteAllText("json_dump_variables.json", JsonConvert.SerializeObject(data.Variables.Select(t => t.Name.Content))); File.WriteAllText("json_dump_rooms.json", JsonConvert.SerializeObject(data.Rooms.Select(t => t.Name.Content))); - Msl.GenerateNRandomLinesFromCode(data.Code, new GlobalDecompileContext(data, false), 100, 1, 0); + RandomUtils.GenerateNRandomLinesFromCode(data.Code, new GlobalDecompileContext(data, false), 100, 1, 0); } /// /// Export all items, weapons and armors in csv files. /// - private static void ExportItems() + private static void ExportItems(bool deleteBeforeExport = false) { try { - DirectoryInfo dir = new("export"); - if (dir.Exists) dir.Delete(true); + DirectoryInfo dir = new(exportPath); + if (deleteBeforeExport && dir.Exists) dir.Delete(true); dir.Create(); List? weapons = ModLoader.GetTable("gml_GlobalScript_table_weapons"); @@ -168,6 +169,8 @@ public static async Task LoadFile(string filename, bool re = false) { // save the filename for later dataPath = filename; + // save export folder + exportPath = Path.Join(Directory.GetCurrentDirectory(), Path.DirectorySeparatorChar.ToString(), "export"); // create a new dialog box LoadingDialog dialog = new() { @@ -197,6 +200,7 @@ public static async Task LoadFile(string filename, bool re = false) ModLoader.Initalize(); // cleaning loot table LootUtils.ResetLootTables(); + LastCountContext = ContextMenuUtils.ReadLastContextIndex(); ExportItems(); } public static async Task DoSaveDialog() diff --git a/FilePacker.cs b/FilePacker.cs index 260e302..a7bc1e8 100644 --- a/FilePacker.cs +++ b/FilePacker.cs @@ -23,7 +23,7 @@ public static bool Pack(string path) null, path, ModLoader.ModPath, - path, + Path.GetDirectoryName(Path.GetDirectoryName(path)) ?? path, Main.Instance.mslVersion, new Type[2] {typeof(ModShardLauncher.Mods.Mod), typeof(UndertaleModLib.Models.UndertaleCode)} ); diff --git a/ModLoader.cs b/ModLoader.cs index 519d4cc..5df3ce6 100644 --- a/ModLoader.cs +++ b/ModLoader.cs @@ -175,7 +175,6 @@ public static void PatchMods() Disclaimers = new(); List mods = ModInfos.Instance.Mods; Menus = new(); - Stopwatch watch = Stopwatch.StartNew(); foreach (ModFile mod in mods) { @@ -204,6 +203,10 @@ public static void PatchMods() Msl.AddDisclaimerRoom(Credits.Select(x => x.Item1).ToArray(), Credits.SelectMany(x => x.Item2).Distinct().ToArray()); Msl.ChainDisclaimerRooms(Disclaimers); Msl.CreateMenu(Menus); + Msl.AddFunction(@"function msl_always_true() { + return true; +}", "msl_always_true"); + watch.Stop(); long elapsedMs = watch.ElapsedMilliseconds; diff --git a/ModShardLauncher.csproj b/ModShardLauncher.csproj index dc1d6a8..b3941ea 100644 --- a/ModShardLauncher.csproj +++ b/ModShardLauncher.csproj @@ -14,26 +14,11 @@ - - - - - - - - - - - - - - - - + @@ -218,9 +203,9 @@ - - + + diff --git a/ModShardLauncherTest/LocalizationUtilsTest.cs b/ModShardLauncherTest/LocalizationUtilsTest.cs deleted file mode 100644 index 3919ab5..0000000 --- a/ModShardLauncherTest/LocalizationUtilsTest.cs +++ /dev/null @@ -1,211 +0,0 @@ -using Xunit; -using System.Collections.Generic; -using ModShardLauncher.Mods; -using System.Reflection; -using ModShardLauncher.Extensions; -using ModShardLauncher; - -namespace ModShardLauncherTest -{ - public static class LocalizationUtilsData - { - public const string oneLanguageString = "testEn"; - public const string multipleLanguagesString = "testRu;testEn;testCh"; - public const string allLanguagesString = "testRu;testEn;testCh;testGe;testSp;testFr;testIt;testPr;testPl;testTu;testJp;testKr"; - } - - public class ToDictTest - { - [Theory] - [InlineData(LocalizationUtilsData.oneLanguageString, 0)] - [InlineData(LocalizationUtilsData.oneLanguageString, 1)] - public void ToDict_OneElement(string str, int index) - { - // Arrange - Dictionary expectedResult = new() - { - {ModLanguage.Russian, "testEn"}, {ModLanguage.English, "testEn"}, {ModLanguage.Chinese, "testEn"}, {ModLanguage.German, "testEn"}, {ModLanguage.Spanish, "testEn"}, - {ModLanguage.French, "testEn"}, {ModLanguage.Italian, "testEn"}, {ModLanguage.Portuguese, "testEn"}, {ModLanguage.Polish, "testEn"}, {ModLanguage.Turkish, "testEn"}, - {ModLanguage.Japanese, "testEn"}, {ModLanguage.Korean, "testEn"} - }; - - // Act - MethodInfo? methodInfo = typeof(ModShardLauncher.Localization).GetMethod("ToDict", BindingFlags.NonPublic | BindingFlags.Static); - if (methodInfo == null) - { - Assert.Fail("Cannot find the tested method ToDict"); - } - - object? result = methodInfo.Invoke(null, new object[] { str, index }); - if (result == null) - { - Assert.Fail("Invalid result from ToDict"); - } - - Dictionary res = (Dictionary)result; - - // Assert - foreach (ModLanguage modLanguage in Localization.LanguageList) - { - Assert.Equal(expectedResult[modLanguage], res[modLanguage]); - } - } - - [Theory] - [InlineData(LocalizationUtilsData.multipleLanguagesString)] - public void ToDict_MultipleElements(string str) - { - // Arrange - Dictionary expectedResult = new() - { - {ModLanguage.Russian, "testRu"}, {ModLanguage.English, "testEn"}, {ModLanguage.Chinese, "testCh"}, {ModLanguage.German, "testEn"}, {ModLanguage.Spanish, "testEn"}, - {ModLanguage.French, "testEn"}, {ModLanguage.Italian, "testEn"}, {ModLanguage.Portuguese, "testEn"}, {ModLanguage.Polish, "testEn"}, {ModLanguage.Turkish, "testEn"}, - {ModLanguage.Japanese, "testEn"}, {ModLanguage.Korean, "testEn"} - }; - - // Act - MethodInfo? methodInfo = typeof(Localization).GetMethod("ToDict", BindingFlags.NonPublic | BindingFlags.Static); - if (methodInfo == null) - { - Assert.Fail("Cannot find the tested method ToDict"); - } - - object? result = methodInfo.Invoke(null, new object[] { str, 1 }); - if (result == null) - { - Assert.Fail("Invalid result from ToDict"); - } - - Dictionary res = (Dictionary)result; - - // Assert - foreach (ModLanguage modLanguage in Localization.LanguageList) - { - Assert.Equal(expectedResult[modLanguage], res[modLanguage]); - } - } - - [Theory] - [InlineData(LocalizationUtilsData.multipleLanguagesString, 0)] - [InlineData(LocalizationUtilsData.multipleLanguagesString, 100)] - public void ToDict_MultipleElementsDifferentDefault(string str, int index) - { - // Arrange - Dictionary expectedResult = new() - { - {ModLanguage.Russian, "testRu"}, {ModLanguage.English, "testEn"}, {ModLanguage.Chinese, "testCh"}, {ModLanguage.German, "testRu"}, {ModLanguage.Spanish, "testRu"}, - {ModLanguage.French, "testRu"}, {ModLanguage.Italian, "testRu"}, {ModLanguage.Portuguese, "testRu"}, {ModLanguage.Polish, "testRu"}, {ModLanguage.Turkish, "testRu"}, - {ModLanguage.Japanese, "testRu"}, {ModLanguage.Korean, "testRu"} - }; - - // Act - MethodInfo? methodInfo = typeof(Localization).GetMethod("ToDict", BindingFlags.NonPublic | BindingFlags.Static); - if (methodInfo == null) - { - Assert.Fail("Cannot find the tested method ToDict"); - } - - object? result = methodInfo.Invoke(null, new object[] { str, index }); - if (result == null) - { - Assert.Fail("Invalid result from ToDict"); - } - - Dictionary res = (Dictionary)result; - - // Assert - foreach (ModLanguage modLanguage in Localization.LanguageList) - { - Assert.Equal(expectedResult[modLanguage], res[modLanguage]); - } - } - - [Theory] - [InlineData(LocalizationUtilsData.allLanguagesString)] - public void ToDict_AllElements(string str) - { - // Arrange - Dictionary expectedResult = new() - { - {ModLanguage.Russian, "testRu"}, {ModLanguage.English, "testEn"}, {ModLanguage.Chinese, "testCh"}, {ModLanguage.German, "testGe"}, {ModLanguage.Spanish, "testSp"}, - {ModLanguage.French, "testFr"}, {ModLanguage.Italian, "testIt"}, {ModLanguage.Portuguese, "testPr"}, {ModLanguage.Polish, "testPl"}, {ModLanguage.Turkish, "testTu"}, - {ModLanguage.Japanese, "testJp"}, {ModLanguage.Korean, "testKr"} - }; - - // Act - MethodInfo? methodInfo = typeof(Localization).GetMethod("ToDict", BindingFlags.NonPublic | BindingFlags.Static); - if (methodInfo == null) - { - Assert.Fail("Cannot find the tested method ToDict"); - } - - object? result = methodInfo.Invoke(null, new object[] { str, 1 }); - if (result == null) - { - Assert.Fail("Invalid result from ToDict"); - } - - Dictionary res = (Dictionary)result; - - // Assert - foreach (ModLanguage modLanguage in Localization.LanguageList) - { - Assert.Equal(expectedResult[modLanguage], res[modLanguage]); - } - } - } - - public class CreateLineLocalizationItemTest - { - [Fact] - public void CreateLine() - { - // Arrange - string expectedResult = "testItem;testRu;testEn;testCh;testGe;testSp;testFr;testIt;testPr;testPl;testTu;testJp;testKr;"; - Dictionary input = new() - { - {ModLanguage.Russian, "testRu"}, {ModLanguage.English, "testEn"}, {ModLanguage.Chinese, "testCh"}, {ModLanguage.German, "testGe"}, {ModLanguage.Spanish, "testSp"}, - {ModLanguage.French, "testFr"}, {ModLanguage.Italian, "testIt"}, {ModLanguage.Portuguese, "testPr"}, {ModLanguage.Polish, "testPl"}, {ModLanguage.Turkish, "testTu"}, - {ModLanguage.Japanese, "testJp"}, {ModLanguage.Korean, "testKr"} - }; - - // Act - MethodInfo? methodInfo = typeof(LocalizationItem).GetMethod("CreateLine", BindingFlags.NonPublic | BindingFlags.Static); - if (methodInfo == null) - { - Assert.Fail("Cannot find the tested method CreateLine"); - } - - object? result = methodInfo.Invoke(null, new object[] { "testItem", input }); - if (result == null) - { - Assert.Fail("Invalid result from CreateLine"); - } - - string res = (string)result; - - // Assert - Assert.Equal(expectedResult, res); - - } - } - - public class CreateLineLocalizationSentenceTest - { - [Theory] - [InlineData(LocalizationUtilsData.oneLanguageString, "id;any;any;any;any;any;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;")] - [InlineData(LocalizationUtilsData.multipleLanguagesString, "id;any;any;any;any;any;testRu;testEn;testCh;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;")] - [InlineData(LocalizationUtilsData.allLanguagesString, "id;any;any;any;any;any;testRu;testEn;testCh;testGe;testSp;testFr;testIt;testPr;testPl;testTu;testJp;testKr;")] - public void CreateLine(string str, string expectedResult) - { - // Arrange - LocalizationSentence sentence = new("id", str); - - // Act - string res = sentence.CreateLine(); - - // Assert - Assert.Equal(expectedResult, res); - } - } -} \ No newline at end of file diff --git a/ModShardLauncherTest/ModShardLauncherTest.csproj b/ModShardLauncherTest/ModShardLauncherTest.csproj index dc46e43..4c4b542 100644 --- a/ModShardLauncherTest/ModShardLauncherTest.csproj +++ b/ModShardLauncherTest/ModShardLauncherTest.csproj @@ -10,7 +10,7 @@ - + diff --git a/ModShardLauncherTest/TableTest/BooksTest.cs b/ModShardLauncherTest/TableTest/BooksTest.cs new file mode 100644 index 0000000..6e4f142 --- /dev/null +++ b/ModShardLauncherTest/TableTest/BooksTest.cs @@ -0,0 +1,131 @@ +using ModShardLauncher.Mods; +using Serilog; +using System.Reflection; + +namespace ModShardLauncherTest; +public class LocalizationBookTest +{ + [Theory] + [InlineData(LocalizationUtilsData.oneLanguageString, "testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;", "name")] + [InlineData(LocalizationUtilsData.multipleLanguagesString, "testRu;testEn;testCh;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;", "name")] + [InlineData(LocalizationUtilsData.allLanguagesString, "testRu;testEn;testCh;testGe;testSp;testFr;testIt;testPr;testPl;testTu;testJp;testKr;", "name")] + [InlineData(LocalizationUtilsData.oneLanguageString, "testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;", "content")] + [InlineData(LocalizationUtilsData.multipleLanguagesString, "testRu;testEn;testCh;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;", "content")] + [InlineData(LocalizationUtilsData.allLanguagesString, "testRu;testEn;testCh;testGe;testSp;testFr;testIt;testPr;testPl;testTu;testJp;testKr;", "content")] + [InlineData(LocalizationUtilsData.oneLanguageString, "testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;", "mid_text")] + [InlineData(LocalizationUtilsData.multipleLanguagesString, "testRu;testEn;testCh;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;", "mid_text")] + [InlineData(LocalizationUtilsData.allLanguagesString, "testRu;testEn;testCh;testGe;testSp;testFr;testIt;testPr;testPl;testTu;testJp;testKr;", "mid_text")] + [InlineData(LocalizationUtilsData.oneLanguageString, "testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;", "desc")] + [InlineData(LocalizationUtilsData.multipleLanguagesString, "testRu;testEn;testCh;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;", "desc")] + [InlineData(LocalizationUtilsData.allLanguagesString, "testRu;testEn;testCh;testGe;testSp;testFr;testIt;testPr;testPl;testTu;testJp;testKr;", "desc")] + [InlineData(LocalizationUtilsData.oneLanguageString, "testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;", "type")] + [InlineData(LocalizationUtilsData.multipleLanguagesString, "testRu;testEn;testCh;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;", "type")] + [InlineData(LocalizationUtilsData.allLanguagesString, "testRu;testEn;testCh;testGe;testSp;testFr;testIt;testPr;testPl;testTu;testJp;testKr;", "type")] + public void CreateLine(string input, string output, string selector) + { + // Arrange + output = $"testItem;{output}"; + + // Act + string res = new LocalizationBook("testItem", input, input, input, input, input) + .CreateLine(selector) + .Collect(); + + // Assert + Assert.Equal(output, res); + } + [Theory] + [InlineData("name")] + [InlineData("content")] + [InlineData("mid_text")] + [InlineData("desc")] + [InlineData("type")] + public void CreateLineFromExistingData(string selector) + { + // Arrange + string output = "book;Монастырская книга;Monastic Book;《修院纪实》;Buch der Abtei;Libro monacal;Livre monastique;Libro Monastico;Livro Monástico;Klasztorna księga;Manastır Kitabı;修道士の本;수도원 일지;"; + Dictionary input = new() + { + {ModLanguage.Russian, "Монастырская книга"}, + {ModLanguage.English, "Monastic Book"}, + {ModLanguage.Chinese, "《修院纪实》"}, + {ModLanguage.German, "Buch der Abtei"}, + {ModLanguage.Spanish, "Libro monacal"}, + {ModLanguage.French, "Livre monastique"}, + {ModLanguage.Italian, "Libro Monastico"}, + {ModLanguage.Portuguese, "Livro Monástico"}, + {ModLanguage.Polish, "Klasztorna księga"}, + {ModLanguage.Turkish, "Manastır Kitabı"}, + {ModLanguage.Japanese, "修道士の本"}, + {ModLanguage.Korean, "수도원 일지"} + }; + + // Act + string res = new LocalizationBook("book", input, input, input, input, input) + .CreateLine(selector) + .Collect(); + + // Assert + Assert.Equal(output, res); + } + [Fact] + public void CreateInjectionBooksLocalization() + { + // Arrange + string inputTable = string.Format(LocalizationUtilsData.tableString, @"push.s ""book_type_end;"" +conv.s.v +push.s ""book_desc_end;"" +conv.s.v +push.s ""book_mid_text_end;"" +conv.s.v +push.s ""book_content_end;"" +conv.s.v +push.s ""book_name_end;"" +conv.s.v", 5); + + string outputTable = string.Format(LocalizationUtilsData.tableString, @"push.s ""book_type_end;"" +conv.s.v +push.s ""book;Монастырская книга;Monastic Book;《修院纪实》;Buch der Abtei;Libro monacal;Livre monastique;Libro Monastico;Livro Monástico;Klasztorna księga;Manastır Kitabı;修道士の本;수도원 일지;"" +conv.s.v +push.s ""book_desc_end;"" +conv.s.v +push.s ""book;Монастырская книга;Monastic Book;《修院纪实》;Buch der Abtei;Libro monacal;Livre monastique;Libro Monastico;Livro Monástico;Klasztorna księga;Manastır Kitabı;修道士の本;수도원 일지;"" +conv.s.v +push.s ""book_mid_text_end;"" +conv.s.v +push.s ""book;Монастырская книга;Monastic Book;《修院纪实》;Buch der Abtei;Libro monacal;Livre monastique;Libro Monastico;Livro Monástico;Klasztorna księga;Manastır Kitabı;修道士の本;수도원 일지;"" +conv.s.v +push.s ""book_content_end;"" +conv.s.v +push.s ""book;Монастырская книга;Monastic Book;《修院纪实》;Buch der Abtei;Libro monacal;Livre monastique;Libro Monastico;Livro Monástico;Klasztorna księga;Manastır Kitabı;修道士の本;수도원 일지;"" +conv.s.v +push.s ""book_name_end;"" +conv.s.v +push.s ""book;Монастырская книга;Monastic Book;《修院纪实》;Buch der Abtei;Libro monacal;Livre monastique;Libro Monastico;Livro Monástico;Klasztorna księga;Manastır Kitabı;修道士の本;수도원 일지;"" +conv.s.v", 10); + + Dictionary input = new() + { + {ModLanguage.Russian, "Монастырская книга"}, + {ModLanguage.English, "Monastic Book"}, + {ModLanguage.Chinese, "《修院纪实》"}, + {ModLanguage.German, "Buch der Abtei"}, + {ModLanguage.Spanish, "Libro monacal"}, + {ModLanguage.French, "Livre monastique"}, + {ModLanguage.Italian, "Libro Monastico"}, + {ModLanguage.Portuguese, "Livro Monástico"}, + {ModLanguage.Polish, "Klasztorna księga"}, + {ModLanguage.Turkish, "Manastır Kitabı"}, + {ModLanguage.Japanese, "修道士の本"}, + {ModLanguage.Korean, "수도원 일지"} + }; + + LocalizationBook[] Locs = new[] {new LocalizationBook("book", input, input, input, input, input)}; + + // Act + string res = Msl.CreateInjectionBooksLocalization(Locs)(inputTable.Split('\n')).Collect(); + + // Assert + Assert.Equal(outputTable.Replace("\r\n", "\n"), res.Replace("\r\n", "\n")); + } +} \ No newline at end of file diff --git a/ModShardLauncherTest/TableTest/ConsumablesTest.cs b/ModShardLauncherTest/TableTest/ConsumablesTest.cs new file mode 100644 index 0000000..1cfe673 --- /dev/null +++ b/ModShardLauncherTest/TableTest/ConsumablesTest.cs @@ -0,0 +1,111 @@ +using ModShardLauncher.Mods; +using Serilog; +using System.Reflection; + +namespace ModShardLauncherTest; +public class LocalizationItemTest +{ + [Theory] + [InlineData(LocalizationUtilsData.oneLanguageString, "testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;", "name")] + [InlineData(LocalizationUtilsData.multipleLanguagesString, "testRu;testEn;testCh;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;", "name")] + [InlineData(LocalizationUtilsData.allLanguagesString, "testRu;testEn;testCh;testGe;testSp;testFr;testIt;testPr;testPl;testTu;testJp;testKr;", "name")] + [InlineData(LocalizationUtilsData.oneLanguageString, "testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;", "effect")] + [InlineData(LocalizationUtilsData.multipleLanguagesString, "testRu;testEn;testCh;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;", "effect")] + [InlineData(LocalizationUtilsData.allLanguagesString, "testRu;testEn;testCh;testGe;testSp;testFr;testIt;testPr;testPl;testTu;testJp;testKr;", "effect")] + [InlineData(LocalizationUtilsData.oneLanguageString, "testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;", "description")] + [InlineData(LocalizationUtilsData.multipleLanguagesString, "testRu;testEn;testCh;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;", "description")] + [InlineData(LocalizationUtilsData.allLanguagesString, "testRu;testEn;testCh;testGe;testSp;testFr;testIt;testPr;testPl;testTu;testJp;testKr;", "description")] + public void CreateLine(string input, string output, string selector) + { + // Arrange + output = $"testItem;{output}"; + + // Act + string res = new LocalizationItem("testItem", input, input, input) + .CreateLine(selector) + .Collect(); + + // Assert + Assert.Equal(output, res); + } + [Theory] + [InlineData("name")] + [InlineData("effect")] + [InlineData("description")] + public void CreateLineFromExistingData(string selector) + { + // Arrange + string output = "bandage;Бинт;Bandage;绷带;Verband;Venda;Bandage;Benda;Atadura;Bandaż;Bandaj;包帯;붕대;"; + Dictionary input = new() + { + {ModLanguage.Russian, "Бинт"}, + {ModLanguage.English, "Bandage"}, + {ModLanguage.Chinese, "绷带"}, + {ModLanguage.German, "Verband"}, + {ModLanguage.Spanish, "Venda"}, + {ModLanguage.French, "Bandage"}, + {ModLanguage.Italian, "Benda"}, + {ModLanguage.Portuguese, "Atadura"}, + {ModLanguage.Polish, "Bandaż"}, + {ModLanguage.Turkish, "Bandaj"}, + {ModLanguage.Japanese, "包帯"}, + {ModLanguage.Korean, "붕대"} + }; + + // Act + string res = new LocalizationItem("bandage", input, input, input) + .CreateLine(selector) + .Collect(); + + // Assert + Assert.Equal(output, res); + } + [Fact] + public void CreateInjectionItemsLocalization() + { + // Arrange + string inputTable = string.Format(LocalizationUtilsData.tableString, @"push.s ""consum_desc_end;"" +conv.s.v +push.s ""consum_mid_end;"" +conv.s.v +push.s ""consum_name_end;"" +conv.s.v", 3); + + string outputTable = string.Format(LocalizationUtilsData.tableString, @"push.s ""consum_desc_end;"" +conv.s.v +push.s ""bandage;Бинт;Bandage;绷带;Verband;Venda;Bandage;Benda;Atadura;Bandaż;Bandaj;包帯;붕대;"" +conv.s.v +push.s ""consum_mid_end;"" +conv.s.v +push.s ""bandage;Бинт;Bandage;绷带;Verband;Venda;Bandage;Benda;Atadura;Bandaż;Bandaj;包帯;붕대;"" +conv.s.v +push.s ""consum_name_end;"" +conv.s.v +push.s ""bandage;Бинт;Bandage;绷带;Verband;Venda;Bandage;Benda;Atadura;Bandaż;Bandaj;包帯;붕대;"" +conv.s.v", 6); + + Dictionary input = new() + { + {ModLanguage.Russian, "Бинт"}, + {ModLanguage.English, "Bandage"}, + {ModLanguage.Chinese, "绷带"}, + {ModLanguage.German, "Verband"}, + {ModLanguage.Spanish, "Venda"}, + {ModLanguage.French, "Bandage"}, + {ModLanguage.Italian, "Benda"}, + {ModLanguage.Portuguese, "Atadura"}, + {ModLanguage.Polish, "Bandaż"}, + {ModLanguage.Turkish, "Bandaj"}, + {ModLanguage.Japanese, "包帯"}, + {ModLanguage.Korean, "붕대"} + }; + + LocalizationItem[] Locs = new[] {new LocalizationItem("bandage", input, input, input)}; + + // Act + string res = Msl.CreateInjectionItemsLocalization(Locs)(inputTable.Split('\n')).Collect(); + + // Assert + Assert.Equal(outputTable.Replace("\r\n", "\n"), res.Replace("\r\n", "\n")); + } +} \ No newline at end of file diff --git a/ModShardLauncherTest/TableTest/LocalizationUtilsTest.cs b/ModShardLauncherTest/TableTest/LocalizationUtilsTest.cs new file mode 100644 index 0000000..276c92f --- /dev/null +++ b/ModShardLauncherTest/TableTest/LocalizationUtilsTest.cs @@ -0,0 +1,191 @@ +using ModShardLauncher.Mods; +using System.Reflection; + +namespace ModShardLauncherTest; +public static class LocalizationUtilsData +{ + public const string oneLanguageString = "testEn"; + public const string multipleLanguagesString = "testRu;testEn;testCh"; + public const string allLanguagesString = "testRu;testEn;testCh;testGe;testSp;testFr;testIt;testPr;testPl;testTu;testJp;testKr"; + public const string tableString = @":[0] +b [3] + +> gml_Script_table_example (locals=0, argc=0) +:[1] +push.i 1 +setowner.e +{0} +call.i @@NewGMLArray@@(argc={1}) +ret.v + +:[2] +exit.i + +:[3] +push.i gml_Script_table_example +conv.i.v +pushi.e -1 +conv.i.v +call.i method(argc=2) +dup.v 0 +pushi.e -1 +pop.v.v [stacktop]self.table_example +popz.v + +:[end]"; +} +public class ToDictTest +{ + [Theory] + [InlineData(LocalizationUtilsData.oneLanguageString, 0)] // First element aka English as default language + [InlineData(LocalizationUtilsData.oneLanguageString, 1)] // Second element, but there is none then first element aka English as default language + public void ToDict_OneElement(string str, int index) + { + // Arrange + Dictionary expectedResult = new() + { + {ModLanguage.Russian, "testEn"}, {ModLanguage.English, "testEn"}, {ModLanguage.Chinese, "testEn"}, {ModLanguage.German, "testEn"}, {ModLanguage.Spanish, "testEn"}, + {ModLanguage.French, "testEn"}, {ModLanguage.Italian, "testEn"}, {ModLanguage.Portuguese, "testEn"}, {ModLanguage.Polish, "testEn"}, {ModLanguage.Turkish, "testEn"}, + {ModLanguage.Japanese, "testEn"}, {ModLanguage.Korean, "testEn"} + }; + + // Act + MethodInfo? methodInfo = typeof(ModShardLauncher.Localization).GetMethod("ToDict", BindingFlags.NonPublic | BindingFlags.Static); + if (methodInfo == null) + { + Assert.Fail("Cannot find the tested method ToDict"); + } + + object? result = methodInfo.Invoke(null, new object[] { str, index }); + if (result == null) + { + Assert.Fail("Invalid result from ToDict"); + } + + Dictionary res = (Dictionary)result; + + // Assert + foreach (ModLanguage modLanguage in Localization.LanguageList) + { + Assert.Equal(expectedResult[modLanguage], res[modLanguage]); + } + } + [Theory] + [InlineData(LocalizationUtilsData.multipleLanguagesString)] + public void ToDict_MultipleElements(string str) + { + // Arrange + Dictionary expectedResult = new() + { + {ModLanguage.Russian, "testRu"}, {ModLanguage.English, "testEn"}, {ModLanguage.Chinese, "testCh"}, {ModLanguage.German, "testEn"}, {ModLanguage.Spanish, "testEn"}, + {ModLanguage.French, "testEn"}, {ModLanguage.Italian, "testEn"}, {ModLanguage.Portuguese, "testEn"}, {ModLanguage.Polish, "testEn"}, {ModLanguage.Turkish, "testEn"}, + {ModLanguage.Japanese, "testEn"}, {ModLanguage.Korean, "testEn"} + }; + + // Act + MethodInfo? methodInfo = typeof(Localization).GetMethod("ToDict", BindingFlags.NonPublic | BindingFlags.Static); + if (methodInfo == null) + { + Assert.Fail("Cannot find the tested method ToDict"); + } + + object? result = methodInfo.Invoke(null, new object[] { str, 1 }); + if (result == null) + { + Assert.Fail("Invalid result from ToDict"); + } + + Dictionary res = (Dictionary)result; + + // Assert + foreach (ModLanguage modLanguage in Localization.LanguageList) + { + Assert.Equal(expectedResult[modLanguage], res[modLanguage]); + } + } + [Theory] + [InlineData(LocalizationUtilsData.multipleLanguagesString, 0)] // First element aka Russian as default language + [InlineData(LocalizationUtilsData.multipleLanguagesString, 100)] // 101st element, but there is none then first element aka Russian as default language + public void ToDict_MultipleElementsDifferentDefault(string str, int index) + { + // Arrange + Dictionary expectedResult = new() + { + {ModLanguage.Russian, "testRu"}, {ModLanguage.English, "testEn"}, {ModLanguage.Chinese, "testCh"}, {ModLanguage.German, "testRu"}, {ModLanguage.Spanish, "testRu"}, + {ModLanguage.French, "testRu"}, {ModLanguage.Italian, "testRu"}, {ModLanguage.Portuguese, "testRu"}, {ModLanguage.Polish, "testRu"}, {ModLanguage.Turkish, "testRu"}, + {ModLanguage.Japanese, "testRu"}, {ModLanguage.Korean, "testRu"} + }; + + // Act + MethodInfo? methodInfo = typeof(Localization).GetMethod("ToDict", BindingFlags.NonPublic | BindingFlags.Static); + if (methodInfo == null) + { + Assert.Fail("Cannot find the tested method ToDict"); + } + + object? result = methodInfo.Invoke(null, new object[] { str, index }); + if (result == null) + { + Assert.Fail("Invalid result from ToDict"); + } + + Dictionary res = (Dictionary)result; + + // Assert + foreach (ModLanguage modLanguage in Localization.LanguageList) + { + Assert.Equal(expectedResult[modLanguage], res[modLanguage]); + } + } + [Theory] + [InlineData(LocalizationUtilsData.allLanguagesString)] + public void ToDict_AllElements(string str) + { + // Arrange + Dictionary expectedResult = new() + { + {ModLanguage.Russian, "testRu"}, {ModLanguage.English, "testEn"}, {ModLanguage.Chinese, "testCh"}, {ModLanguage.German, "testGe"}, {ModLanguage.Spanish, "testSp"}, + {ModLanguage.French, "testFr"}, {ModLanguage.Italian, "testIt"}, {ModLanguage.Portuguese, "testPr"}, {ModLanguage.Polish, "testPl"}, {ModLanguage.Turkish, "testTu"}, + {ModLanguage.Japanese, "testJp"}, {ModLanguage.Korean, "testKr"} + }; + + // Act + MethodInfo? methodInfo = typeof(Localization).GetMethod("ToDict", BindingFlags.NonPublic | BindingFlags.Static); + if (methodInfo == null) + { + Assert.Fail("Cannot find the tested method ToDict"); + } + + object? result = methodInfo.Invoke(null, new object[] { str, 1 }); + if (result == null) + { + Assert.Fail("Invalid result from ToDict"); + } + + Dictionary res = (Dictionary)result; + + // Assert + foreach (ModLanguage modLanguage in Localization.LanguageList) + { + Assert.Equal(expectedResult[modLanguage], res[modLanguage]); + } + } +} +public class CreateLineLocalizationSentenceTest +{ + [Theory] + [InlineData(LocalizationUtilsData.oneLanguageString, "id;any;any;any;any;any;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;")] + [InlineData(LocalizationUtilsData.multipleLanguagesString, "id;any;any;any;any;any;testRu;testEn;testCh;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;")] + [InlineData(LocalizationUtilsData.allLanguagesString, "id;any;any;any;any;any;testRu;testEn;testCh;testGe;testSp;testFr;testIt;testPr;testPl;testTu;testJp;testKr;")] + public void CreateLine(string str, string expectedResult) + { + // Arrange + LocalizationSentence sentence = new("id", str); + + // Act + string res = sentence.CreateLine(null).First(); + + // Assert + Assert.Equal(expectedResult, res); + } +} \ No newline at end of file diff --git a/ModShardLauncherTest/TableTest/ModifierTest.cs b/ModShardLauncherTest/TableTest/ModifierTest.cs new file mode 100644 index 0000000..e099282 --- /dev/null +++ b/ModShardLauncherTest/TableTest/ModifierTest.cs @@ -0,0 +1,101 @@ +using ModShardLauncher.Mods; +using Serilog; +using System.Reflection; + +namespace ModShardLauncherTest; +public class LocalizationModifierTest +{ + [Theory] + [InlineData(LocalizationUtilsData.oneLanguageString, "testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;", "name")] + [InlineData(LocalizationUtilsData.multipleLanguagesString, "testRu;testEn;testCh;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;", "name")] + [InlineData(LocalizationUtilsData.allLanguagesString, "testRu;testEn;testCh;testGe;testSp;testFr;testIt;testPr;testPl;testTu;testJp;testKr;", "name")] + [InlineData(LocalizationUtilsData.oneLanguageString, "testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;", "description")] + [InlineData(LocalizationUtilsData.multipleLanguagesString, "testRu;testEn;testCh;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;", "description")] + [InlineData(LocalizationUtilsData.allLanguagesString, "testRu;testEn;testCh;testGe;testSp;testFr;testIt;testPr;testPl;testTu;testJp;testKr;", "description")] + public void CreateLine(string input, string output, string selector) + { + // Arrange + output = $"testItem;{output}"; + + // Act + string res = new LocalizationModifier("testItem", input, input) + .CreateLine(selector) + .Collect(); + + // Assert + Assert.Equal(output, res); + } + [Theory] + [InlineData("name")] + [InlineData("description")] + public void CreateLineFromExistingData(string selector) + { + // Arrange + string output = "o_db_blind;Слепота;Blindness;盲目;Blindheit;Ceguera;Cécité;Cecità;Cegueira;Oślepienie;Körlük;盲目;맹목;"; + Dictionary input = new() + { + {ModLanguage.Russian, "Слепота"}, + {ModLanguage.English, "Blindness"}, + {ModLanguage.Chinese, "盲目"}, + {ModLanguage.German, "Blindheit"}, + {ModLanguage.Spanish, "Ceguera"}, + {ModLanguage.French, "Cécité"}, + {ModLanguage.Italian, "Cecità"}, + {ModLanguage.Portuguese, "Cegueira"}, + {ModLanguage.Polish, "Oślepienie"}, + {ModLanguage.Turkish, "Körlük"}, + {ModLanguage.Japanese, "盲目"}, + {ModLanguage.Korean, "맹목"} + }; + + // Act + string res = new LocalizationModifier("o_db_blind", input, input) + .CreateLine(selector) + .Collect(); + + // Assert + Assert.Equal(output, res); + } + [Fact] + public void CreateInjectionModifiersLocalization() + { + // Arrange + string inputTable = string.Format(LocalizationUtilsData.tableString, @"push.s ""buff_desc_end;"" +conv.s.v +push.s ""buff_name_end;"" +conv.s.v", 2); + + string outputTable = string.Format(LocalizationUtilsData.tableString, @"push.s ""buff_desc_end;"" +conv.s.v +push.s ""o_db_blind;Слепота;Blindness;盲目;Blindheit;Ceguera;Cécité;Cecità;Cegueira;Oślepienie;Körlük;盲目;맹목;"" +conv.s.v +push.s ""buff_name_end;"" +conv.s.v +push.s ""o_db_blind;Слепота;Blindness;盲目;Blindheit;Ceguera;Cécité;Cecità;Cegueira;Oślepienie;Körlük;盲目;맹목;"" +conv.s.v", 4); + + Dictionary input = new() + { + {ModLanguage.Russian, "Слепота"}, + {ModLanguage.English, "Blindness"}, + {ModLanguage.Chinese, "盲目"}, + {ModLanguage.German, "Blindheit"}, + {ModLanguage.Spanish, "Ceguera"}, + {ModLanguage.French, "Cécité"}, + {ModLanguage.Italian, "Cecità"}, + {ModLanguage.Portuguese, "Cegueira"}, + {ModLanguage.Polish, "Oślepienie"}, + {ModLanguage.Turkish, "Körlük"}, + {ModLanguage.Japanese, "盲目"}, + {ModLanguage.Korean, "맹목"} + }; + + LocalizationModifier[] Locs = new[] {new LocalizationModifier("o_db_blind", input, input)}; + + // Act + string res = Msl.CreateInjectionModifiersLocalization(Locs)(inputTable.Split('\n')).Collect(); + + // Assert + Assert.Equal(outputTable.Replace("\r\n", "\n"), res.Replace("\r\n", "\n")); + } +} \ No newline at end of file diff --git a/ModShardLauncherTest/TableTest/NPCLinesTest.cs b/ModShardLauncherTest/TableTest/NPCLinesTest.cs new file mode 100644 index 0000000..9793d09 --- /dev/null +++ b/ModShardLauncherTest/TableTest/NPCLinesTest.cs @@ -0,0 +1,89 @@ +using ModShardLauncher.Mods; +using System.Reflection; + +namespace ModShardLauncherTest; +public class LocalizationSentenceTest +{ + [Theory] + [InlineData(LocalizationUtilsData.oneLanguageString, "testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;")] + [InlineData(LocalizationUtilsData.multipleLanguagesString, "testRu;testEn;testCh;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;")] + [InlineData(LocalizationUtilsData.allLanguagesString, "testRu;testEn;testCh;testGe;testSp;testFr;testIt;testPr;testPl;testTu;testJp;testKr;")] + public void CreateLine(string input, string output) + { + // Arrange + output = $"testItem;any;any;any;any;any;{output}"; + + // Act + string res = new LocalizationSentence("testItem", input) + .CreateLine(null) + .Collect(); + + // Assert + Assert.Equal(output, res); + } + [Fact] + public void CreateLineFromExistingData() + { + // Arrange + string output = "greeting;any;any;any;any;any;Да?..;Yes?..;什么事儿?;Ja ...?;Yes?..;Oui...?;Sì...?;Sim..?;Tak?..;Yes?..;何か…?;뭔가...?;"; + Dictionary input = new() + { + {ModLanguage.Russian, "Да?.."}, + {ModLanguage.English, "Yes?.."}, + {ModLanguage.Chinese, "什么事儿?"}, + {ModLanguage.German, "Ja ...?"}, + {ModLanguage.Spanish, "Yes?.."}, + {ModLanguage.French, "Oui...?"}, + {ModLanguage.Italian, "Sì...?"}, + {ModLanguage.Portuguese, "Sim..?"}, + {ModLanguage.Polish, "Tak?.."}, + {ModLanguage.Turkish, "Yes?.."}, + {ModLanguage.Japanese, "何か…?"}, + {ModLanguage.Korean, "뭔가...?"} + }; + + // Act + string res = new LocalizationSentence("greeting", input) + .CreateLine(null) + .Collect(); + + // Assert + Assert.Equal(output, res); + } + [Fact] + public void CreateInjectionSentenceLocalization() + { + // Arrange + string inputTable = string.Format(LocalizationUtilsData.tableString, @"push.s ""[NPC] GREETINGS;"" +conv.s.v", 1); + + string outputTable = string.Format(LocalizationUtilsData.tableString, @"push.s ""[NPC] GREETINGS;"" +conv.s.v +push.s ""greeting;any;any;any;any;any;Да?..;Yes?..;什么事儿?;Ja ...?;Yes?..;Oui...?;Sì...?;Sim..?;Tak?..;Yes?..;何か…?;뭔가...?;"" +conv.s.v", 2); + + Dictionary input = new() + { + {ModLanguage.Russian, "Да?.."}, + {ModLanguage.English, "Yes?.."}, + {ModLanguage.Chinese, "什么事儿?"}, + {ModLanguage.German, "Ja ...?"}, + {ModLanguage.Spanish, "Yes?.."}, + {ModLanguage.French, "Oui...?"}, + {ModLanguage.Italian, "Sì...?"}, + {ModLanguage.Portuguese, "Sim..?"}, + {ModLanguage.Polish, "Tak?.."}, + {ModLanguage.Turkish, "Yes?.."}, + {ModLanguage.Japanese, "何か…?"}, + {ModLanguage.Korean, "뭔가...?"} + }; + + LocalizationSentence[] Locs = new[] {new LocalizationSentence("greeting", input)}; + + // Act + string res = Msl.CreateInjectionDialogLocalization(Locs)(inputTable.Split('\n')).Collect(); + + // Assert + Assert.Equal(outputTable.Replace("\r\n", "\n"), res.Replace("\r\n", "\n")); + } +} \ No newline at end of file diff --git a/ModShardLauncherTest/TableTest/NPCNamesTest.cs b/ModShardLauncherTest/TableTest/NPCNamesTest.cs new file mode 100644 index 0000000..4e1ef2d --- /dev/null +++ b/ModShardLauncherTest/TableTest/NPCNamesTest.cs @@ -0,0 +1,259 @@ +using ModShardLauncher.Mods; +using System.Reflection; + +namespace ModShardLauncherTest; +public class LocalizationNameTest +{ + [Theory] + [InlineData(LocalizationUtilsData.oneLanguageString, "testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;")] + [InlineData(LocalizationUtilsData.multipleLanguagesString, "testRu;testEn;testCh;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;")] + [InlineData(LocalizationUtilsData.allLanguagesString, "testRu;testEn;testCh;testGe;testSp;testFr;testIt;testPr;testPl;testTu;testJp;testKr;")] + public void CreateLine(string input, string output) + { + // Arrange + output = $";{output}"; + + // Act + string res = new LocalizationName(input) + .CreateLine(null) + .Collect(); + + // Assert + Assert.Equal(output, res); + } + [Fact] + public void CreateLineFromExistingData() + { + // Arrange + string output = ";Адал;Adal;阿达尔;Adal;Adal;Adal;Adal;Adal;Adal;Adal;アダル;에이들;"; + Dictionary input = new() + { + {ModLanguage.Russian, "Адал"}, + {ModLanguage.English, "Adal"}, + {ModLanguage.Chinese, "阿达尔"}, + {ModLanguage.German, "Adal"}, + {ModLanguage.Spanish, "Adal"}, + {ModLanguage.French, "Adal"}, + {ModLanguage.Italian, "Adal"}, + {ModLanguage.Portuguese, "Adal"}, + {ModLanguage.Polish, "Adal"}, + {ModLanguage.Turkish, "Adal"}, + {ModLanguage.Japanese, "アダル"}, + {ModLanguage.Korean, "에이들"} + }; + + // Act + string res = new LocalizationName(input) + .CreateLine(null) + .Collect(); + + // Assert + Assert.Equal(output, res); + } + [Fact] + public void CreateInjectionNamesLocalization() + { + // Arrange + string inputTable = string.Format(LocalizationUtilsData.tableString, @"push.s ""Names_end;"" +conv.s.v", 1); + + string outputTable = string.Format(LocalizationUtilsData.tableString, @"push.s ""Names_end;"" +conv.s.v +push.s "";Адал;Adal;阿达尔;Adal;Adal;Adal;Adal;Adal;Adal;Adal;アダル;에이들;"" +conv.s.v", 2); + + Dictionary input = new() + { + {ModLanguage.Russian, "Адал"}, + {ModLanguage.English, "Adal"}, + {ModLanguage.Chinese, "阿达尔"}, + {ModLanguage.German, "Adal"}, + {ModLanguage.Spanish, "Adal"}, + {ModLanguage.French, "Adal"}, + {ModLanguage.Italian, "Adal"}, + {ModLanguage.Portuguese, "Adal"}, + {ModLanguage.Polish, "Adal"}, + {ModLanguage.Turkish, "Adal"}, + {ModLanguage.Japanese, "アダル"}, + {ModLanguage.Korean, "에이들"} + }; + + LocalizationName[] Locs = new[] {new LocalizationName(input)}; + + // Act + string res = Msl.CreateInjectionNamesLocalization(Locs)(inputTable.Split('\n')).Collect(); + + // Assert + Assert.Equal(outputTable.Replace("\r\n", "\n"), res.Replace("\r\n", "\n")); + } +} +public class LocalizationQuestNameTest +{ + [Theory] + [InlineData(LocalizationUtilsData.oneLanguageString, "testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;")] + [InlineData(LocalizationUtilsData.multipleLanguagesString, "testRu;testEn;testCh;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;")] + [InlineData(LocalizationUtilsData.allLanguagesString, "testRu;testEn;testCh;testGe;testSp;testFr;testIt;testPr;testPl;testTu;testJp;testKr;")] + public void CreateLine(string input, string output) + { + // Arrange + output = $"testItem;{output}"; + + // Act + string res = new LocalizationQuestName("testItem", input) + .CreateLine(null) + .Collect(); + + // Assert + Assert.Equal(output, res); + } + [Fact] + public void CreateLineFromExistingData() + { + // Arrange + string output = "47;Арвон;Arvon;阿冯;Arvon;Arvon;Arvon;Arvon;Arvon;Arvon;Arvon;アルヴォン;아르본;"; + Dictionary input = new() + { + {ModLanguage.Russian, "Арвон"}, + {ModLanguage.English, "Arvon"}, + {ModLanguage.Chinese, "阿冯"}, + {ModLanguage.German, "Arvon"}, + {ModLanguage.Spanish, "Arvon"}, + {ModLanguage.French, "Arvon"}, + {ModLanguage.Italian, "Arvon"}, + {ModLanguage.Portuguese, "Arvon"}, + {ModLanguage.Polish, "Arvon"}, + {ModLanguage.Turkish, "Arvon"}, + {ModLanguage.Japanese, "アルヴォン"}, + {ModLanguage.Korean, "아르본"} + }; + + // Act + string res = new LocalizationQuestName("47", input) + .CreateLine(null) + .Collect(); + + // Assert + Assert.Equal(output, res); + } + [Fact] + public void CreateInjectionNamesLocalization() + { + // Arrange + string inputTable = string.Format(LocalizationUtilsData.tableString, @"push.s ""Constant_Name_end;"" +conv.s.v", 1); + + string outputTable = string.Format(LocalizationUtilsData.tableString, @"push.s ""Constant_Name_end;"" +conv.s.v +push.s ""47;Арвон;Arvon;阿冯;Arvon;Arvon;Arvon;Arvon;Arvon;Arvon;Arvon;アルヴォン;아르본;"" +conv.s.v", 2); + + Dictionary input = new() + { + {ModLanguage.Russian, "Арвон"}, + {ModLanguage.English, "Arvon"}, + {ModLanguage.Chinese, "阿冯"}, + {ModLanguage.German, "Arvon"}, + {ModLanguage.Spanish, "Arvon"}, + {ModLanguage.French, "Arvon"}, + {ModLanguage.Italian, "Arvon"}, + {ModLanguage.Portuguese, "Arvon"}, + {ModLanguage.Polish, "Arvon"}, + {ModLanguage.Turkish, "Arvon"}, + {ModLanguage.Japanese, "アルヴォン"}, + {ModLanguage.Korean, "아르본"} + }; + + LocalizationQuestName[] Locs = new[] {new LocalizationQuestName("47", input)}; + + // Act + string res = Msl.CreateInjectionQuestNamesLocalization(Locs)(inputTable.Split('\n')).Collect(); + + // Assert + Assert.Equal(outputTable.Replace("\r\n", "\n"), res.Replace("\r\n", "\n")); + } +} +public class LocalizationOccupationNameTest +{ + [Theory] + [InlineData(LocalizationUtilsData.oneLanguageString, "testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;")] + [InlineData(LocalizationUtilsData.multipleLanguagesString, "testRu;testEn;testCh;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;")] + [InlineData(LocalizationUtilsData.allLanguagesString, "testRu;testEn;testCh;testGe;testSp;testFr;testIt;testPr;testPl;testTu;testJp;testKr;")] + public void CreateLine(string input, string output) + { + // Arrange + output = $"testItem;{output}"; + + // Act + string res = new LocalizationOccupationName("testItem", input) + .CreateLine(null) + .Collect(); + + // Assert + Assert.Equal(output, res); + } + [Fact] + public void CreateLineFromExistingData() + { + // Arrange + string output = "messenger;Гонец;Courier;急使;Kurier;;Messager;Corriere;Portador;Kurier;Courier;配達人;특사;"; + Dictionary input = new() + { + {ModLanguage.Russian, "Гонец"}, + {ModLanguage.English, "Courier"}, + {ModLanguage.Chinese, "急使"}, + {ModLanguage.German, "Kurier"}, + {ModLanguage.Spanish, ""}, + {ModLanguage.French, "Messager"}, + {ModLanguage.Italian, "Corriere"}, + {ModLanguage.Portuguese, "Portador"}, + {ModLanguage.Polish, "Kurier"}, + {ModLanguage.Turkish, "Courier"}, + {ModLanguage.Japanese, "配達人"}, + {ModLanguage.Korean, "특사"} + }; + + // Act + string res = new LocalizationOccupationName("messenger", input) + .CreateLine(null) + .Collect(); + + // Assert + Assert.Equal(output, res); + } + [Fact] + public void CreateInjectionNamesLocalization() + { + // Arrange + string inputTable = string.Format(LocalizationUtilsData.tableString, @"push.s ""NPC_info_end;"" +conv.s.v", 1); + + string outputTable = string.Format(LocalizationUtilsData.tableString, @"push.s ""NPC_info_end;"" +conv.s.v +push.s ""messenger;Гонец;Courier;急使;Kurier;;Messager;Corriere;Portador;Kurier;Courier;配達人;특사;"" +conv.s.v", 2); + + Dictionary input = new() + { + {ModLanguage.Russian, "Гонец"}, + {ModLanguage.English, "Courier"}, + {ModLanguage.Chinese, "急使"}, + {ModLanguage.German, "Kurier"}, + {ModLanguage.Spanish, ""}, + {ModLanguage.French, "Messager"}, + {ModLanguage.Italian, "Corriere"}, + {ModLanguage.Portuguese, "Portador"}, + {ModLanguage.Polish, "Kurier"}, + {ModLanguage.Turkish, "Courier"}, + {ModLanguage.Japanese, "配達人"}, + {ModLanguage.Korean, "특사"} + }; + + LocalizationOccupationName[] Locs = new[] {new LocalizationOccupationName("messenger", input)}; + + // Act + string res = Msl.CreateInjectionOccupationNamesLocalization(Locs)(inputTable.Split('\n')).Collect(); + + // Assert + Assert.Equal(outputTable.Replace("\r\n", "\n"), res.Replace("\r\n", "\n")); + } +} \ No newline at end of file diff --git a/ModShardLauncherTest/TableTest/SkillsTest.cs b/ModShardLauncherTest/TableTest/SkillsTest.cs new file mode 100644 index 0000000..4b57b1e --- /dev/null +++ b/ModShardLauncherTest/TableTest/SkillsTest.cs @@ -0,0 +1,101 @@ +using ModShardLauncher.Mods; +using Serilog; +using System.Reflection; + +namespace ModShardLauncherTest; +public class LocalizationSkillTest +{ + [Theory] + [InlineData(LocalizationUtilsData.oneLanguageString, "testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;", "name")] + [InlineData(LocalizationUtilsData.multipleLanguagesString, "testRu;testEn;testCh;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;", "name")] + [InlineData(LocalizationUtilsData.allLanguagesString, "testRu;testEn;testCh;testGe;testSp;testFr;testIt;testPr;testPl;testTu;testJp;testKr;", "name")] + [InlineData(LocalizationUtilsData.oneLanguageString, "testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;", "description")] + [InlineData(LocalizationUtilsData.multipleLanguagesString, "testRu;testEn;testCh;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;", "description")] + [InlineData(LocalizationUtilsData.allLanguagesString, "testRu;testEn;testCh;testGe;testSp;testFr;testIt;testPr;testPl;testTu;testJp;testKr;", "description")] + public void CreateLine(string input, string output, string selector) + { + // Arrange + output = $"testItem;{output}"; + + // Act + string res = new LocalizationSkill("testItem", input, input) + .CreateLine(selector) + .Collect(); + + // Assert + Assert.Equal(output, res); + } + [Theory] + [InlineData("name")] + [InlineData("description")] + public void CreateLineFromExistingData(string selector) + { + // Arrange + string output = "Backwards_Dash;Отпрыгивание;Jump Away;后撤;Wegspringen;Salto;Bond en Arrière;Balzo;Pular Fora;Odskok;Uzaklaşma;飛びのき;뒤로 뛰기;"; + Dictionary input = new() + { + {ModLanguage.Russian, "Отпрыгивание"}, + {ModLanguage.English, "Jump Away"}, + {ModLanguage.Chinese, "后撤"}, + {ModLanguage.German, "Wegspringen"}, + {ModLanguage.Spanish, "Salto"}, + {ModLanguage.French, "Bond en Arrière"}, + {ModLanguage.Italian, "Balzo"}, + {ModLanguage.Portuguese, "Pular Fora"}, + {ModLanguage.Polish, "Odskok"}, + {ModLanguage.Turkish, "Uzaklaşma"}, + {ModLanguage.Japanese, "飛びのき"}, + {ModLanguage.Korean, "뒤로 뛰기"} + }; + + // Act + string res = new LocalizationSkill("Backwards_Dash", input, input) + .CreateLine(selector) + .Collect(); + + // Assert + Assert.Equal(output, res); + } + [Fact] + public void CreateInjectionSkillsLocalization() + { + // Arrange + string inputTable = string.Format(LocalizationUtilsData.tableString, @"push.s ""skill_desc_end;"" +conv.s.v +push.s ""skill_name_end;"" +conv.s.v", 2); + + string outputTable = string.Format(LocalizationUtilsData.tableString, @"push.s ""skill_desc_end;"" +conv.s.v +push.s ""Backwards_Dash;Отпрыгивание;Jump Away;后撤;Wegspringen;Salto;Bond en Arrière;Balzo;Pular Fora;Odskok;Uzaklaşma;飛びのき;뒤로 뛰기;"" +conv.s.v +push.s ""skill_name_end;"" +conv.s.v +push.s ""Backwards_Dash;Отпрыгивание;Jump Away;后撤;Wegspringen;Salto;Bond en Arrière;Balzo;Pular Fora;Odskok;Uzaklaşma;飛びのき;뒤로 뛰기;"" +conv.s.v", 4); + + Dictionary input = new() + { + {ModLanguage.Russian, "Отпрыгивание"}, + {ModLanguage.English, "Jump Away"}, + {ModLanguage.Chinese, "后撤"}, + {ModLanguage.German, "Wegspringen"}, + {ModLanguage.Spanish, "Salto"}, + {ModLanguage.French, "Bond en Arrière"}, + {ModLanguage.Italian, "Balzo"}, + {ModLanguage.Portuguese, "Pular Fora"}, + {ModLanguage.Polish, "Odskok"}, + {ModLanguage.Turkish, "Uzaklaşma"}, + {ModLanguage.Japanese, "飛びのき"}, + {ModLanguage.Korean, "뒤로 뛰기"} + }; + + LocalizationSkill[] Locs = new[] {new LocalizationSkill("Backwards_Dash", input, input)}; + + // Act + string res = Msl.CreateInjectionSkillsLocalization(Locs)(inputTable.Split('\n')).Collect(); + + // Assert + Assert.Equal(outputTable.Replace("\r\n", "\n"), res.Replace("\r\n", "\n")); + } +} \ No newline at end of file diff --git a/ModShardLauncherTest/TableTest/SpeechesTest.cs b/ModShardLauncherTest/TableTest/SpeechesTest.cs new file mode 100644 index 0000000..ec36ff5 --- /dev/null +++ b/ModShardLauncherTest/TableTest/SpeechesTest.cs @@ -0,0 +1,102 @@ +using ModShardLauncher.Mods; +using Serilog; +using System.Reflection; + +namespace ModShardLauncherTest; +public class LocalizationSpeechTest +{ + [Theory] + [InlineData(LocalizationUtilsData.oneLanguageString, "testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;")] + [InlineData(LocalizationUtilsData.multipleLanguagesString, "testRu;testEn;testCh;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;")] + [InlineData(LocalizationUtilsData.allLanguagesString, "testRu;testEn;testCh;testGe;testSp;testFr;testIt;testPr;testPl;testTu;testJp;testKr;")] + public void CreateLine(string input, string output) + { + // Arrange + string start = "kill;kill;kill;kill;kill;kill;kill;kill;kill;kill;kill;kill;kill;"; + string end = "kill_end;kill_end;kill_end;kill_end;kill_end;kill_end;kill_end;kill_end;kill_end;kill_end;kill_end;kill_end;kill_end;"; + output = $"{start}\n;{output}\n;{output}\n{end}"; + + // Act + string res = new LocalizationSpeech("kill", input, input) + .CreateLine(null) + .Collect(); + + // Assert + Assert.Equal(output, res); + } + [Fact] + public void CreateLineFromExistingData() + { + // Arrange + string start = "kill;kill;kill;kill;kill;kill;kill;kill;kill;kill;kill;kill;kill;"; + string end = "kill_end;kill_end;kill_end;kill_end;kill_end;kill_end;kill_end;kill_end;kill_end;kill_end;kill_end;kill_end;kill_end;"; + string txt = ";Ха!;Ha!;哈!;Ha!;¡Ja!;Ha !;Ha!;Rá!;Ha!;Hah!;はあっ!;흥!;"; + string output = $"{start}\n{txt}\n{txt}\n{end}"; + + Dictionary input = new() + { + {ModLanguage.Russian, "Ха!"}, + {ModLanguage.English, "Ha!"}, + {ModLanguage.Chinese, "哈!"}, + {ModLanguage.German, "Ha!"}, + {ModLanguage.Spanish, "¡Ja!"}, + {ModLanguage.French, "Ha !"}, + {ModLanguage.Italian, "Ha!"}, + {ModLanguage.Portuguese, "Rá!"}, + {ModLanguage.Polish, "Ha!"}, + {ModLanguage.Turkish, "Hah!"}, + {ModLanguage.Japanese, "はあっ!"}, + {ModLanguage.Korean, "흥!"} + }; + + // Act + string res = new LocalizationSpeech("kill", input, input) + .CreateLine(null) + .Collect(); + + // Assert + Assert.Equal(output, res); + } + [Fact] + public void CreateInjectionSkillsLocalization() + { + // Arrange + string inputTable = string.Format(LocalizationUtilsData.tableString, @"push.s ""FORBIDDEN MAGIC;"" +conv.s.v", 1); + + string outputTable = string.Format(LocalizationUtilsData.tableString, @"push.s ""FORBIDDEN MAGIC;"" +conv.s.v +push.s ""kill_end;kill_end;kill_end;kill_end;kill_end;kill_end;kill_end;kill_end;kill_end;kill_end;kill_end;kill_end;kill_end;"" +conv.s.v +push.s "";Ха!;Ha!;哈!;Ha!;¡Ja!;Ha !;Ha!;Rá!;Ha!;Hah!;はあっ!;흥!;"" +conv.s.v +push.s "";Ха!;Ha!;哈!;Ha!;¡Ja!;Ha !;Ha!;Rá!;Ha!;Hah!;はあっ!;흥!;"" +conv.s.v +push.s ""kill;kill;kill;kill;kill;kill;kill;kill;kill;kill;kill;kill;kill;"" +conv.s.v", 5); + + Dictionary input = new() + { + {ModLanguage.Russian, "Ха!"}, + {ModLanguage.English, "Ha!"}, + {ModLanguage.Chinese, "哈!"}, + {ModLanguage.German, "Ha!"}, + {ModLanguage.Spanish, "¡Ja!"}, + {ModLanguage.French, "Ha !"}, + {ModLanguage.Italian, "Ha!"}, + {ModLanguage.Portuguese, "Rá!"}, + {ModLanguage.Polish, "Ha!"}, + {ModLanguage.Turkish, "Hah!"}, + {ModLanguage.Japanese, "はあっ!"}, + {ModLanguage.Korean, "흥!"} + }; + + LocalizationSpeech[] Locs = new[] {new LocalizationSpeech("kill", input, input)}; + + // Act + string res = Msl.CreateInjectionSpeechesLocalization(Locs)(inputTable.Split('\n')).Collect(); + + // Assert + Assert.Equal(outputTable.Replace("\r\n", "\n"), res.Replace("\r\n", "\n")); + } +} \ No newline at end of file diff --git a/ModShardLauncherTest/TableTest/TextsTest.cs b/ModShardLauncherTest/TableTest/TextsTest.cs new file mode 100644 index 0000000..d765e39 --- /dev/null +++ b/ModShardLauncherTest/TableTest/TextsTest.cs @@ -0,0 +1,271 @@ +using ModShardLauncher.Mods; +using Serilog; +using System.Reflection; + +namespace ModShardLauncherTest; +public class LocalizationTextTreeTest +{ + [Theory] + [InlineData(LocalizationUtilsData.oneLanguageString, "testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;", "tier")] + [InlineData(LocalizationUtilsData.multipleLanguagesString, "testRu;testEn;testCh;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;", "tier")] + [InlineData(LocalizationUtilsData.allLanguagesString, "testRu;testEn;testCh;testGe;testSp;testFr;testIt;testPr;testPl;testTu;testJp;testKr;", "tier")] + [InlineData(LocalizationUtilsData.oneLanguageString, "testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;", "hover")] + [InlineData(LocalizationUtilsData.multipleLanguagesString, "testRu;testEn;testCh;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;", "hover")] + [InlineData(LocalizationUtilsData.allLanguagesString, "testRu;testEn;testCh;testGe;testSp;testFr;testIt;testPr;testPl;testTu;testJp;testKr;", "hover")] + public void CreateLine(string input, string output, string selector) + { + // Arrange + output = $"testItem;{output}"; + + // Act + string res = new LocalizationTextTree("testItem", input, input) + .CreateLine(selector) + .Collect(); + + // Assert + Assert.Equal(output, res); + } + [Theory] + [InlineData("tier")] + [InlineData("hover")] + public void CreateLineFromExistingData(string selector) + { + // Arrange + string output = "Swords;Мечи;Swords;单手刀剑;Schwerter;Espadas;Épées;Spade;Espadas;Miecze;Kılıçlar;剣;검;"; + Dictionary input = new() + { + {ModLanguage.Russian, "Мечи"}, + {ModLanguage.English, "Swords"}, + {ModLanguage.Chinese, "单手刀剑"}, + {ModLanguage.German, "Schwerter"}, + {ModLanguage.Spanish, "Espadas"}, + {ModLanguage.French, "Épées"}, + {ModLanguage.Italian, "Spade"}, + {ModLanguage.Portuguese, "Espadas"}, + {ModLanguage.Polish, "Miecze"}, + {ModLanguage.Turkish, "Kılıçlar"}, + {ModLanguage.Japanese, "剣"}, + {ModLanguage.Korean, "검"} + }; + + // Act + string res = new LocalizationTextTree("Swords", input, input) + .CreateLine(selector) + .Collect(); + + // Assert + Assert.Equal(output, res); + } + [Fact] + public void CreateInjectionTextTreesLocalization() + { + // Arrange + string inputTable = string.Format(LocalizationUtilsData.tableString, @"push.s ""skilltree_hover_end;"" +conv.s.v +push.s ""Tier_name_end;"" +conv.s.v", 2); + + string outputTable = string.Format(LocalizationUtilsData.tableString, @"push.s ""skilltree_hover_end;"" +conv.s.v +push.s ""Swords;Мечи;Swords;单手刀剑;Schwerter;Espadas;Épées;Spade;Espadas;Miecze;Kılıçlar;剣;검;"" +conv.s.v +push.s ""Tier_name_end;"" +conv.s.v +push.s ""Swords;Мечи;Swords;单手刀剑;Schwerter;Espadas;Épées;Spade;Espadas;Miecze;Kılıçlar;剣;검;"" +conv.s.v", 4); + + Dictionary input = new() + { + {ModLanguage.Russian, "Мечи"}, + {ModLanguage.English, "Swords"}, + {ModLanguage.Chinese, "单手刀剑"}, + {ModLanguage.German, "Schwerter"}, + {ModLanguage.Spanish, "Espadas"}, + {ModLanguage.French, "Épées"}, + {ModLanguage.Italian, "Spade"}, + {ModLanguage.Portuguese, "Espadas"}, + {ModLanguage.Polish, "Miecze"}, + {ModLanguage.Turkish, "Kılıçlar"}, + {ModLanguage.Japanese, "剣"}, + {ModLanguage.Korean, "검"} + }; + + LocalizationTextTree[] Locs = new[] {new LocalizationTextTree("Swords", input, input)}; + + // Act + string res = Msl.CreateInjectionTextTreesLocalization(Locs)(inputTable.Split('\n')).Collect(); + + // Assert + Assert.Equal(outputTable.Replace("\r\n", "\n"), res.Replace("\r\n", "\n")); + } +} +public class LocalizationTextRarityTest +{ + [Theory] + [InlineData(LocalizationUtilsData.oneLanguageString, "testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;")] + [InlineData(LocalizationUtilsData.multipleLanguagesString, "testRu;testEn;testCh;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;")] + [InlineData(LocalizationUtilsData.allLanguagesString, "testRu;testEn;testCh;testGe;testSp;testFr;testIt;testPr;testPl;testTu;testJp;testKr;")] + public void CreateLine(string input, string output) + { + // Arrange + output = $"testItem;{output}"; + + // Act + string res = new LocalizationTextRarity("testItem", input) + .CreateLine(null) + .Collect(); + + // Assert + Assert.Equal(output, res); + } + [Fact] + public void CreateLineFromExistingData() + { + // Arrange + string output = "1;обычный / обычная / обычное / обычные;common;普通;gewöhnlicher / gewöhnliche / gewöhnliches / gewöhnliche;común / común / comunes / comunes;commun / commune / communs / communes;oggetto comune - ;comum;Zwyczajny / Zwyczajna / Zwyczajne / Zwyczajne;sıradan;コモン;평범한;"; + Dictionary input = new() + { + {ModLanguage.Russian, "обычный / обычная / обычное / обычные"}, + {ModLanguage.English, "common"}, + {ModLanguage.Chinese, "普通"}, + {ModLanguage.German, "gewöhnlicher / gewöhnliche / gewöhnliches / gewöhnliche"}, + {ModLanguage.Spanish, "común / común / comunes / comunes"}, + {ModLanguage.French, "commun / commune / communs / communes"}, + {ModLanguage.Italian, "oggetto comune - "}, + {ModLanguage.Portuguese, "comum"}, + {ModLanguage.Polish, "Zwyczajny / Zwyczajna / Zwyczajne / Zwyczajne"}, + {ModLanguage.Turkish, "sıradan"}, + {ModLanguage.Japanese, "コモン"}, + {ModLanguage.Korean, "평범한"} + }; + + // Act + string res = new LocalizationTextRarity("1", input) + .CreateLine(null) + .Collect(); + + // Assert + Assert.Equal(output, res); + } + [Fact] + public void CreateInjectionTextRaritysLocalization() + { + // Arrange + string inputTable = string.Format(LocalizationUtilsData.tableString, @"push.s ""rarity_end;"" +conv.s.v", 1); + + string outputTable = string.Format(LocalizationUtilsData.tableString, @"push.s ""rarity_end;"" +conv.s.v +push.s ""1;обычный / обычная / обычное / обычные;common;普通;gewöhnlicher / gewöhnliche / gewöhnliches / gewöhnliche;común / común / comunes / comunes;commun / commune / communs / communes;oggetto comune - ;comum;Zwyczajny / Zwyczajna / Zwyczajne / Zwyczajne;sıradan;コモン;평범한;"" +conv.s.v", 2); + + Dictionary input = new() + { + {ModLanguage.Russian, "обычный / обычная / обычное / обычные"}, + {ModLanguage.English, "common"}, + {ModLanguage.Chinese, "普通"}, + {ModLanguage.German, "gewöhnlicher / gewöhnliche / gewöhnliches / gewöhnliche"}, + {ModLanguage.Spanish, "común / común / comunes / comunes"}, + {ModLanguage.French, "commun / commune / communs / communes"}, + {ModLanguage.Italian, "oggetto comune - "}, + {ModLanguage.Portuguese, "comum"}, + {ModLanguage.Polish, "Zwyczajny / Zwyczajna / Zwyczajne / Zwyczajne"}, + {ModLanguage.Turkish, "sıradan"}, + {ModLanguage.Japanese, "コモン"}, + {ModLanguage.Korean, "평범한"} + }; + + LocalizationTextRarity[] Locs = new[] {new LocalizationTextRarity("1", input)}; + + // Act + string res = Msl.CreateInjectionTextRaritysLocalization(Locs)(inputTable.Split('\n')).Collect(); + + // Assert + Assert.Equal(outputTable.Replace("\r\n", "\n"), res.Replace("\r\n", "\n")); + } +} +public class LocalizationTextContextTest +{ + [Theory] + [InlineData(LocalizationUtilsData.oneLanguageString, "testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;")] + [InlineData(LocalizationUtilsData.multipleLanguagesString, "testRu;testEn;testCh;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;")] + [InlineData(LocalizationUtilsData.allLanguagesString, "testRu;testEn;testCh;testGe;testSp;testFr;testIt;testPr;testPl;testTu;testJp;testKr;")] + public void CreateLine(string input, string output) + { + // Arrange + output = $"testItem;{output}"; + + // Act + string res = new LocalizationTextContext("testItem", input) + .CreateLine(null) + .Collect(); + + // Assert + Assert.Equal(output, res); + } + [Fact] + public void CreateLineFromExistingData() + { + // Arrange + string output = "1;Открыть;Open;打开;Öffnen;Abrir;Ouvrir;Apri;Abrir;Otwórz;Aç;開く;열기;"; + Dictionary input = new() + { + {ModLanguage.Russian, "Открыть"}, + {ModLanguage.English, "Open"}, + {ModLanguage.Chinese, "打开"}, + {ModLanguage.German, "Öffnen"}, + {ModLanguage.Spanish, "Abrir"}, + {ModLanguage.French, "Ouvrir"}, + {ModLanguage.Italian, "Apri"}, + {ModLanguage.Portuguese, "Abrir"}, + {ModLanguage.Polish, "Otwórz"}, + {ModLanguage.Turkish, "Aç"}, + {ModLanguage.Japanese, "開く"}, + {ModLanguage.Korean, "열기"} + }; + + // Act + string res = new LocalizationTextContext("1", input) + .CreateLine(null) + .Collect(); + + // Assert + Assert.Equal(output, res); + } + [Fact] + public void CreateInjectionTextContextsLocalization() + { + // Arrange + string inputTable = string.Format(LocalizationUtilsData.tableString, @"push.s ""context_menu_end;"" +conv.s.v", 1); + + string outputTable = string.Format(LocalizationUtilsData.tableString, @"push.s ""context_menu_end;"" +conv.s.v +push.s ""1;Открыть;Open;打开;Öffnen;Abrir;Ouvrir;Apri;Abrir;Otwórz;Aç;開く;열기;"" +conv.s.v", 2); + + Dictionary input = new() + { + {ModLanguage.Russian, "Открыть"}, + {ModLanguage.English, "Open"}, + {ModLanguage.Chinese, "打开"}, + {ModLanguage.German, "Öffnen"}, + {ModLanguage.Spanish, "Abrir"}, + {ModLanguage.French, "Ouvrir"}, + {ModLanguage.Italian, "Apri"}, + {ModLanguage.Portuguese, "Abrir"}, + {ModLanguage.Polish, "Otwórz"}, + {ModLanguage.Turkish, "Aç"}, + {ModLanguage.Japanese, "開く"}, + {ModLanguage.Korean, "열기"} + }; + + LocalizationTextContext[] Locs = new[] {new LocalizationTextContext("1", input)}; + + // Act + string res = Msl.CreateInjectionTextContextsLocalization(Locs)(inputTable.Split('\n')).Collect(); + + // Assert + Assert.Equal(outputTable.Replace("\r\n", "\n"), res.Replace("\r\n", "\n")); + } +} \ No newline at end of file diff --git a/ModShardLauncherTest/TableTest/WeaponTextsTest.cs b/ModShardLauncherTest/TableTest/WeaponTextsTest.cs new file mode 100644 index 0000000..4c3359a --- /dev/null +++ b/ModShardLauncherTest/TableTest/WeaponTextsTest.cs @@ -0,0 +1,101 @@ +using ModShardLauncher.Mods; +using Serilog; +using System.Reflection; + +namespace ModShardLauncherTest; +public class LocalizationWeaponTextTest +{ + [Theory] + [InlineData(LocalizationUtilsData.oneLanguageString, "testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;", "name")] + [InlineData(LocalizationUtilsData.multipleLanguagesString, "testRu;testEn;testCh;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;", "name")] + [InlineData(LocalizationUtilsData.allLanguagesString, "testRu;testEn;testCh;testGe;testSp;testFr;testIt;testPr;testPl;testTu;testJp;testKr;", "name")] + [InlineData(LocalizationUtilsData.oneLanguageString, "testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;", "description")] + [InlineData(LocalizationUtilsData.multipleLanguagesString, "testRu;testEn;testCh;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;testEn;", "description")] + [InlineData(LocalizationUtilsData.allLanguagesString, "testRu;testEn;testCh;testGe;testSp;testFr;testIt;testPr;testPl;testTu;testJp;testKr;", "description")] + public void CreateLine(string input, string output, string selector) + { + // Arrange + output = $"testItem;{output}"; + + // Act + string res = new LocalizationWeaponText("testItem", input, input) + .CreateLine(selector) + .Collect(); + + // Assert + Assert.Equal(output, res); + } + [Theory] + [InlineData("name")] + [InlineData("description")] + public void CreateLineFromExistingData(string selector) + { + // Arrange + string output = "Wooden Sword;Деревянный меч;Wooden Sword;木剑;Holzschwert;Espada de madera;Épée en Bois;Spada di Legno;Espada de Madeira;Drewniany miecz;Tahta Kılıç;木製の剣;목검;"; + Dictionary input = new() + { + {ModLanguage.Russian, "Деревянный меч"}, + {ModLanguage.English, "Wooden Sword"}, + {ModLanguage.Chinese, "木剑"}, + {ModLanguage.German, "Holzschwert"}, + {ModLanguage.Spanish, "Espada de madera"}, + {ModLanguage.French, "Épée en Bois"}, + {ModLanguage.Italian, "Spada di Legno"}, + {ModLanguage.Portuguese, "Espada de Madeira"}, + {ModLanguage.Polish, "Drewniany miecz"}, + {ModLanguage.Turkish, "Tahta Kılıç"}, + {ModLanguage.Japanese, "木製の剣"}, + {ModLanguage.Korean, "목검"} + }; + + // Act + string res = new LocalizationWeaponText("Wooden Sword", input, input) + .CreateLine(selector) + .Collect(); + + // Assert + Assert.Equal(output, res); + } + [Fact] + public void CreateInjectionWeaponTextsLocalization() + { + // Arrange + string inputTable = string.Format(LocalizationUtilsData.tableString, @"push.s ""weapon_desc_end;"" +conv.s.v +push.s ""weapon_name_end;"" +conv.s.v", 2); + + string outputTable = string.Format(LocalizationUtilsData.tableString, @"push.s ""weapon_desc_end;"" +conv.s.v +push.s ""Wooden Sword;Деревянный меч;Wooden Sword;木剑;Holzschwert;Espada de madera;Épée en Bois;Spada di Legno;Espada de Madeira;Drewniany miecz;Tahta Kılıç;木製の剣;목검;"" +conv.s.v +push.s ""weapon_name_end;"" +conv.s.v +push.s ""Wooden Sword;Деревянный меч;Wooden Sword;木剑;Holzschwert;Espada de madera;Épée en Bois;Spada di Legno;Espada de Madeira;Drewniany miecz;Tahta Kılıç;木製の剣;목검;"" +conv.s.v", 4); + + Dictionary input = new() + { + {ModLanguage.Russian, "Деревянный меч"}, + {ModLanguage.English, "Wooden Sword"}, + {ModLanguage.Chinese, "木剑"}, + {ModLanguage.German, "Holzschwert"}, + {ModLanguage.Spanish, "Espada de madera"}, + {ModLanguage.French, "Épée en Bois"}, + {ModLanguage.Italian, "Spada di Legno"}, + {ModLanguage.Portuguese, "Espada de Madeira"}, + {ModLanguage.Polish, "Drewniany miecz"}, + {ModLanguage.Turkish, "Tahta Kılıç"}, + {ModLanguage.Japanese, "木製の剣"}, + {ModLanguage.Korean, "목검"} + }; + + LocalizationWeaponText[] Locs = new[] {new LocalizationWeaponText("Wooden Sword", input, input)}; + + // Act + string res = Msl.CreateInjectionWeaponTextsLocalization(Locs)(inputTable.Split('\n')).Collect(); + + // Assert + Assert.Equal(outputTable.Replace("\r\n", "\n"), res.Replace("\r\n", "\n")); + } +} \ No newline at end of file diff --git a/ModUtils/CodeUtils.cs b/ModUtils/CodeUtils.cs index 8af9f2c..a3fe281 100644 --- a/ModUtils/CodeUtils.cs +++ b/ModUtils/CodeUtils.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Text; -using System.Text.RegularExpressions; using ModShardLauncher.Resources.Codes; using Serilog; using UndertaleModLib; diff --git a/ModUtils/ContextMenuUtils.cs b/ModUtils/ContextMenuUtils.cs new file mode 100644 index 0000000..04d63a4 --- /dev/null +++ b/ModUtils/ContextMenuUtils.cs @@ -0,0 +1,230 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.Metrics; +using System.Linq; +using System.Text.RegularExpressions; +using ModShardLauncher.Mods; +using UndertaleModLib; +using UndertaleModLib.Decompiler; +using UndertaleModLib.Models; + +namespace ModShardLauncher; + +public class ContextMenu +{ + public string Name { get; set; } + public int Id; + public Dictionary LocName { get; set; } + public Dictionary LocDescription { get; set; } + public UndertaleFunction ScriptFunction { get; set; } + public UndertaleFunction? ConditionFunction { get; set; } + public ContextMenu(string name, Dictionary localisation, Dictionary description, string scriptFunction, string? conditionFunction = null) + { + Name = name; + LocName = Localization.SetDictionary(localisation); + LocDescription = Localization.SetDictionary(description); + if (conditionFunction != null) ConditionFunction = DataLoader.data.Functions.ByName(conditionFunction); + ScriptFunction = DataLoader.data.Functions.First(x => x.Name.Content.Contains(scriptFunction)); + } + public ContextMenu(string name, string localisation, string description, string scriptFunction, string? conditionFunction = null) + { + Name = name; + LocName = Localization.SetDictionary(localisation); + LocDescription = Localization.SetDictionary(description); + try + { + if (conditionFunction != null) ConditionFunction = DataLoader.data.Functions.ByName(conditionFunction); + ScriptFunction = DataLoader.data.Functions.First(x => x.Name.Content.Contains(scriptFunction)); + } + catch + { + throw; + } + } + public LocalizationTextContext ToLocalizationName(int id) + { + return new LocalizationTextContext(id.ToString(), LocName); + } + public LocalizationTextContext ToLocalizationDescription(int id) + { + return new LocalizationTextContext(id.ToString(), LocName); + } +} +internal static class ContextMenuUtils +{ + internal static int ReadLastContextIndex() + { + List code = Msl.GetUMTCodeFromFile("gml_GlobalScript_table_text").Instructions; + int count = -1; + foreach(UndertaleInstruction instruction in code.Where(x => x.Type1 == UndertaleInstruction.DataType.String)) + { + if (instruction.Value is UndertaleResourceById valById) + { + if (valById.Resource.Content.Contains("context_menu_end;")) + { + count = 0; + } + else if (valById.Resource.Content.Contains("context_menu;")) + { + return count; + } + else if (count >= 0) + { + count++; + } + } + } + return count; + } + public static Func, IEnumerable> CreateContextInjector(params ContextMenu[] res) + { + IEnumerable func(IEnumerable input) + { + bool fill_found = false; + bool fill_case_found = false; + bool jmptbl_injected = false; + string jmp_fill = ""; + bool only_once = false; + + int label = 998; + string block1 = string.Join('\n', + res.Select(x => @$"dup.v 0 +push.s ""{x.Name}"" +cmp.s.v EQ +bt [{label+=2}]") + ); + + label = 999; + string block2 = string.Join('\n', + res.Select(x => @$":[{++label}] +call.i {x.ConditionFunction?.Name.Content ?? "gml_Script_msl_always_true"}(argc=0) +conv.v.b +bf [{++label}] +pushi.e {x.Id} +conv.i.v +pushglb.v global.context_menu +call.i ds_list_find_value(argc=2) +push.s ""{x.Name}"" +conv.s.v +push.v self.context_name +call.i ds_list_add(argc=3) +popz.v +pushi.e 0 +conv.i.v +pushi.e 1 +conv.i.v +push.v self.context_desc +call.i ds_list_add(argc=3) +popz.v +:[{label}] +b {{0}}") + ); + + foreach(string item in input) + { + yield return item; + + if (!fill_found && item.Contains("Fill_Flask")) + { + fill_found = true; + } + else if (fill_found && !jmptbl_injected && item.Contains("bt")) + { + jmptbl_injected = true; + jmp_fill = new Regex(@"\[\d+\]").Match(item).Value; + + yield return block1; + } + else if (jmp_fill != "" && item.Contains(jmp_fill)) + { + fill_case_found = true; + } + else if (!only_once && fill_case_found && item.Contains("b [")) + { + only_once = true; + string jmp_end = new Regex(@"\[\d+\]").Match(item).Value; + yield return string.Format(block2, jmp_end); + } + } + } + return func; + } + public static Func, IEnumerable> CreateMouseInjector(params ContextMenu[] res) + { + IEnumerable func(IEnumerable input) + { + bool fill_found = false; + bool fill_case_found = false; + bool jmptbl_injected = false; + string jmp_fill = ""; + bool only_once = false; + + int label = 1000; + string block1 = string.Join('\n', + res.Select(x => @$"dup.v 0 +push.s ""{x.Name}"" +cmp.s.v EQ +bt [{label++}]") + ); + + label = 1000; + string block2 = string.Join('\n', + res.Select(x => @$":[{label++}] +call.i {x.ScriptFunction.Name.Content}(argc=0) +popz.v +b {{0}}") + ); + + foreach(string item in input) + { + yield return item; + + if (!fill_found && item.Contains("Eat")) + { + fill_found = true; + } + else if (fill_found && !jmptbl_injected && item.Contains("bt")) + { + jmptbl_injected = true; + jmp_fill = new Regex(@"\[\d+\]").Match(item).Value; + + yield return block1; + } + else if (jmp_fill != "" && item.Contains(jmp_fill)) + { + fill_case_found = true; + } + else if (!only_once && fill_case_found && item.Contains("b [")) + { + only_once = true; + string jmp_end = new Regex(@"\[\d+\]").Match(item).Value; + yield return string.Format(block2, jmp_end); + } + } + } + return func; + } +} +public static partial class Msl +{ + public static ContextMenu[] AddNewContext(params ContextMenu[] menus) + { + int id; + + InjectTableTextContextsLocalization(menus.Select(x => { + id = ++DataLoader.LastCountContext; + x.Id = id; + return x.ToLocalizationName(id); + }).ToArray()); + + LoadAssemblyAsString("gml_GlobalScript_scr_create_context_menu") + .Apply(ContextMenuUtils.CreateContextInjector(menus)) + .Save(); + + LoadAssemblyAsString("gml_Object_o_context_button_Mouse_4") + .Apply(ContextMenuUtils.CreateMouseInjector(menus)) + .Save(); + + return menus; + } +} \ No newline at end of file diff --git a/ModUtils/GeneralUtils.cs b/ModUtils/GeneralUtils.cs index 7a11ee9..58fa186 100644 --- a/ModUtils/GeneralUtils.cs +++ b/ModUtils/GeneralUtils.cs @@ -1,8 +1,7 @@ using System; using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Runtime.CompilerServices; +using ModShardLauncher.Mods; using Serilog; using UndertaleModLib; using UndertaleModLib.Decompiler; @@ -15,6 +14,13 @@ namespace ModShardLauncher /// public static partial class Msl { + public static void LogInformation(string message) { Log.Information(message); } + public static void LogWarning(string message) { Log.Warning(message); } + public static void LogDebug(string message) { Log.Debug(message); } + public static void LogError(string message) { Log.Error(message); } + public static void LogFatal(string message) { Log.Fatal(message); } + + public static readonly int ModLanguageSize = Enum.GetNames(typeof(ModLanguage)).Length; public static FileEnumerable LoadGML(string fileName) { try @@ -199,177 +205,5 @@ public static T ThrowIfNull( return (T)argument; } } - public static void GenerateNRandomLinesFromCode(IList code, GlobalDecompileContext context, int numberCode, int numberLinesByCode, ulong seed) - { - List s = new(); - RandomUtils.Seed = seed; - IEnumerable selector = code.SelectionSamplingTechnique(numberCode); - - foreach(UndertaleCode uc in selector) - { - try - { - s.AddRange(Decompiler.Decompile(uc, context).Split('\n').SelectionSamplingTechnique(numberLinesByCode)); - } - catch(InvalidOperationException invalid) - { - try - { - Log.Information(invalid.ToString()); - // we encounter an error since we can't decompile a nested function - // the error message indicates where to look instead - // but you need to parse the message to retrieve the needed code - // "This code block represents a function nested inside " + code.ParentEntry.Name + " - decompile that instead" - string name = invalid.Message.Split('\"')[1]; - Log.Information(string.Format("Looking for {{{0}}} instead", name)); - s.AddRange(Decompiler.Decompile(code.First(x => x.Name.Content == name), context).Split('\n').SelectionSamplingTechnique(numberLinesByCode)); - } - // not all code can be decompiled sadly - catch - { - string name = invalid.Message.Split('\"')[1]; - Log.Information(string.Format("Cannot decompile {{{0}}}, skipping that file", name)); - continue; - } - - } - } - s.FydkShuffling(); - string joinedS = string.Join('\n', s); - File.WriteAllText("_random_lines_for_test.txt", joinedS); - } - } - - public static class RandomUtils - { - // Use to generate seed for UINT64 PRNG - // SplitMix64, see https://prng.di.unimi.it/ - // this is an adaptation of https://prng.di.unimi.it/splitmix64.c - private static ulong seed = 0; - private static ulong[] s = { 0, 0, 0, 0}; - public static ulong NextSeed() { - ulong z = seed += 0x9e3779b97f4a7c15; - z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9; - z = (z ^ (z >> 27)) * 0x94d049bb133111eb; - return z ^ (z >> 31); - } - private static void SetSeed(ulong newSeed) { - seed = newSeed; - s[0] = NextSeed(); - s[1] = NextSeed(); - s[2] = NextSeed(); - s[3] = NextSeed(); - } - public static ulong Seed - { - get => seed; - set => SetSeed(value); - } - // see https://prng.di.unimi.it/xoshiro256starstar.c - private static ulong Rotl(ulong x, int k) - { - return (x << k) | (x >> (64 - k)); - } - - private static ulong NextUINT64() { - ulong result = Rotl(s[1] * 5, 7) * 9; - ulong t = s[1] << 17; - - s[2] ^= s[0]; - s[3] ^= s[1]; - s[1] ^= s[2]; - s[0] ^= s[3]; - - s[2] ^= t; - - s[3] = Rotl(s[3], 45); - - return result; - } - - // from Knuth Donald, The Art Of Computer Programming, Volume 2, Third Edition - // 3.4.2 Random Sampling and Shuffling, p142 - // Algorithm S - public static IEnumerable SelectionSamplingTechnique(this IList list, int n) - { - // number of elements dealt with - int tt = 0; - // number of elements selected by the algorithm - int m = 0; - int N = list.Count; - // firewall if we want more elements than the size of the list - int nn = Math.Min(n, N); - - ulong x; - double u; - - while(m < nn) - { - // they implement the xoshiro256** but only for non-negative int64 - // what some fucking donkeys - // so I've written a proper xoshiro256** return a UINT64 - x = NextUINT64(); - // conversion to a [0, 1] uniform double - // see https://prng.di.unimi.it/ - u = BitConverter.UInt64BitsToDouble(0x3FFL << 52 | x >> 12) - 1.0; - - if((N - tt)*u >= nn - m) - { - // element not selected - tt++; - } - else - { - // element selected - yield return list[tt]; - tt++; - m++; - } - } - } - public static IEnumerable SelectionSamplingTechnique(this IList list, int n, ulong seed) - { - SetSeed(seed); - return list.SelectionSamplingTechnique(n); - } - - // from Knuth Donald, The Art Of Computer Programming, Volume 2, Third Edition - // 3.4.2 Random Sampling and Shuffling, p145 - // Algorithm P - // Known as the Fisher-Yates-Durstenfeld-Knuth algorithm - public static void FydkShuffling(this IList list) - { - ulong tt = (ulong)list.Count; - int j = (int)tt - 1; - int k = 0; - T temp; - - ulong x; - // not an useless operation here since the division is an euclidian division - // for instance 5 / 2 * 2 = 4 with int division - ulong maxForMod = ulong.MaxValue / tt * tt; - - while(j > 0) - { - do - { - x = NextUINT64(); - // unbiased k in uniform [0, tt] - if (x < maxForMod) - k = (int)(x % tt); - } while(x >= maxForMod); - - temp = list[k]; - list[k] = list[j]; - list[j] = temp; - - j--; - } - } - public static void FydkShuffling(this IList list, ulong seed) - { - SetSeed(seed); - list.FydkShuffling(); - } } } \ No newline at end of file diff --git a/ModUtils/RandomUtils.cs b/ModUtils/RandomUtils.cs new file mode 100644 index 0000000..2a35a1e --- /dev/null +++ b/ModUtils/RandomUtils.cs @@ -0,0 +1,182 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Serilog; +using UndertaleModLib.Decompiler; +using UndertaleModLib.Models; + +namespace ModShardLauncher +{ + public static class RandomUtils + { + // Use to generate seed for UINT64 PRNG + // SplitMix64, see https://prng.di.unimi.it/ + // this is an adaptation of https://prng.di.unimi.it/splitmix64.c + private static ulong seed = 0; + private static ulong[] s = { 0, 0, 0, 0}; + public static ulong NextSeed() { + ulong z = seed += 0x9e3779b97f4a7c15; + z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9; + z = (z ^ (z >> 27)) * 0x94d049bb133111eb; + return z ^ (z >> 31); + } + private static void SetSeed(ulong newSeed) { + seed = newSeed; + s[0] = NextSeed(); + s[1] = NextSeed(); + s[2] = NextSeed(); + s[3] = NextSeed(); + } + public static ulong Seed + { + get => seed; + set => SetSeed(value); + } + // see https://prng.di.unimi.it/xoshiro256starstar.c + private static ulong Rotl(ulong x, int k) + { + return (x << k) | (x >> (64 - k)); + } + + private static ulong NextUINT64() { + ulong result = Rotl(s[1] * 5, 7) * 9; + ulong t = s[1] << 17; + + s[2] ^= s[0]; + s[3] ^= s[1]; + s[1] ^= s[2]; + s[0] ^= s[3]; + + s[2] ^= t; + + s[3] = Rotl(s[3], 45); + + return result; + } + + // from Knuth Donald, The Art Of Computer Programming, Volume 2, Third Edition + // 3.4.2 Random Sampling and Shuffling, p142 + // Algorithm S + public static IEnumerable SelectionSamplingTechnique(this IList list, int n) + { + // number of elements dealt with + int tt = 0; + // number of elements selected by the algorithm + int m = 0; + int N = list.Count; + // firewall if we want more elements than the size of the list + int nn = Math.Min(n, N); + + ulong x; + double u; + + while(m < nn) + { + // they implement the xoshiro256** but only for non-negative int64 + // what some fucking donkeys + // so I've written a proper xoshiro256** return a UINT64 + x = NextUINT64(); + // conversion to a [0, 1] uniform double + // see https://prng.di.unimi.it/ + u = BitConverter.UInt64BitsToDouble(0x3FFL << 52 | x >> 12) - 1.0; + + if((N - tt)*u >= nn - m) + { + // element not selected + tt++; + } + else + { + // element selected + yield return list[tt]; + tt++; + m++; + } + } + } + public static IEnumerable SelectionSamplingTechnique(this IList list, int n, ulong seed) + { + SetSeed(seed); + return list.SelectionSamplingTechnique(n); + } + + // from Knuth Donald, The Art Of Computer Programming, Volume 2, Third Edition + // 3.4.2 Random Sampling and Shuffling, p145 + // Algorithm P + // Known as the Fisher-Yates-Durstenfeld-Knuth algorithm + public static void FydkShuffling(this IList list) + { + ulong tt = (ulong)list.Count; + int j = (int)tt - 1; + int k = 0; + T temp; + + ulong x; + // not an useless operation here since the division is an euclidian division + // for instance 5 / 2 * 2 = 4 with int division + ulong maxForMod = ulong.MaxValue / tt * tt; + + while(j > 0) + { + do + { + x = NextUINT64(); + // unbiased k in uniform [0, tt] + if (x < maxForMod) + k = (int)(x % tt); + } while(x >= maxForMod); + + temp = list[k]; + list[k] = list[j]; + list[j] = temp; + + j--; + } + } + public static void FydkShuffling(this IList list, ulong seed) + { + SetSeed(seed); + list.FydkShuffling(); + } + public static void GenerateNRandomLinesFromCode(IList code, GlobalDecompileContext context, int numberCode, int numberLinesByCode, ulong seed) + { + List s = new(); + RandomUtils.Seed = seed; + IEnumerable selector = code.SelectionSamplingTechnique(numberCode); + + foreach(UndertaleCode uc in selector) + { + try + { + s.AddRange(Decompiler.Decompile(uc, context).Split('\n').SelectionSamplingTechnique(numberLinesByCode)); + } + catch(InvalidOperationException invalid) + { + try + { + Log.Information(invalid.ToString()); + // we encounter an error since we can't decompile a nested function + // the error message indicates where to look instead + // but you need to parse the message to retrieve the needed code + // "This code block represents a function nested inside " + code.ParentEntry.Name + " - decompile that instead" + string name = invalid.Message.Split('\"')[1]; + Log.Information(string.Format("Looking for {{{0}}} instead", name)); + s.AddRange(Decompiler.Decompile(code.First(x => x.Name.Content == name), context).Split('\n').SelectionSamplingTechnique(numberLinesByCode)); + } + // not all code can be decompiled sadly + catch + { + string name = invalid.Message.Split('\"')[1]; + Log.Information(string.Format("Cannot decompile {{{0}}}, skipping that file", name)); + continue; + } + + } + } + s.FydkShuffling(); + string joinedS = string.Join('\n', s); + File.WriteAllText("_random_lines_for_test.txt", joinedS); + } + } +} \ No newline at end of file diff --git a/ModUtils/SimulationUtils.cs b/ModUtils/SimulationUtils.cs new file mode 100644 index 0000000..b248675 --- /dev/null +++ b/ModUtils/SimulationUtils.cs @@ -0,0 +1,35 @@ +using System; +using System.Diagnostics; +using Serilog; +using Serilog.Events; + +namespace ModShardLauncher +{ + public class Simulation + { + public static void MonteCarloEstimation(int n, Action func, bool log = false) + { + long sum = 0; + long sum_sq = 0; + Stopwatch watch; + + if (!log) Main.lls.MinimumLevel = (LogEventLevel) 1 + (int) LogEventLevel.Fatal; // log off + for(int _ = 0; _ < n; _++) + { + watch = Stopwatch.StartNew(); + func(); + watch.Stop(); + long elapsedMs = watch.ElapsedMilliseconds; + sum += elapsedMs; + sum_sq += elapsedMs * elapsedMs; + } + if (!log) Main.lls.MinimumLevel = LogEventLevel.Information; // log in + + double mean = sum / (double)n; + double var = sum_sq / (double)(n - 1) - sum * sum / (double)((n - 1) * n); + + Log.Information("MonteCarlo parameter estimated at {{{0}}} +/- {{{1}}} ms", mean, Math.Sqrt(var / n) * 1.96); + } + } +} + \ No newline at end of file diff --git a/ModUtils/TableUtils/AnimalsAI.cs b/ModUtils/TableUtils/AnimalsAI.cs new file mode 100644 index 0000000..9780f7e --- /dev/null +++ b/ModUtils/TableUtils/AnimalsAI.cs @@ -0,0 +1,245 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using Serilog; +using UndertaleModLib; +using UndertaleModLib.Decompiler; +using UndertaleModLib.Models; + +namespace ModShardLauncher; + +public static class DataAI +{ + public enum Behaviour + { + None, + Attack, + Territorial, + Fleeing, + } + public static string StrBehaviour(Behaviour behaviour) + { + return behaviour switch + { + Behaviour.Attack => "1", + Behaviour.Territorial => "2", + Behaviour.Fleeing => "3", + _ => "", + }; + } + public static Behaviour ParseBehaviour(string s) + { + return s switch + { + "1" => Behaviour.Attack, + "2" => Behaviour.Territorial, + "3" => Behaviour.Fleeing, + _ => Behaviour.None, + }; + } + public static int ConvertSquaredCoordinates(int i, int j) + { + // i is row + // j is column + return i <= j ? j*j + i: i*i + 2*i - j; + } + public static readonly string TableName = "gml_GlobalScript_table_animals_ai"; + public static List ActingFactions = new(); // each row represent how a faction acts to the presence of another one + public static List RespondingFactions = new(); // each columns represents how others factions respond to the present of another one + // Behaviours matrix is represented by layers instead of a class row, column representation. + // see https://cs.stackexchange.com/questions/27627/algorithm-dimension-increase-in-1d-representation-of-square-matrix for more information. + public static List Behaviours = new(); + internal static void LoadAITable() + { + if (ActingFactions.Any() || RespondingFactions.Any() || Behaviours.Any()) + { + // already Imported + Log.Warning("The AITable was already loaded."); + return; + } + + try + { + UndertaleCode code = Msl.GetUMTCodeFromFile(TableName); // can fail InvalidOperationException + + string table = code.Disassemble(ModLoader.Data.Variables, ModLoader.Data.CodeLocals.For(code)); + IEnumerable matches = Regex.Matches(table, @"push.s ""(.+)""@\d+").Reverse(); + + foreach((int i, System.Text.RegularExpressions.Match match) in matches.Enumerate()) + { + string line = match.Groups[1].Value; + if (line.Contains("1;- Combat")) break; + if (line.Contains("x;")) + { + foreach((int j, string s) in line.Split(';').Enumerate()) + { + if (j == 0) continue; + RespondingFactions.Add(s); + Log.Information("found responding faction {0}", s); + } + continue; + } + + foreach((int j, string s) in line.Split(';').Enumerate()) + { + if (j == 0) + { + ActingFactions.Add(s); + Log.Information("found acting faction {0}", s); + continue; + } + + int index = ConvertSquaredCoordinates(i - 1, j - 1); + if (Behaviours.Count <= index) + { + int old_size = Behaviours.Count; + int increasing_size = (int)(2 *Math.Sqrt(Behaviours.Count) + 1); + Log.Information("By adding {2}, need to increase behaviours size from {0} to {1}.", old_size, old_size + increasing_size, index); + Behaviours.AddRange(Enumerable.Repeat(Behaviour.None, increasing_size)); + } + Behaviours[index] = ParseBehaviour(s); + } + } + } + catch (Exception ex) + { + Log.Error(ex, ex.Message); + } + + Log.Information("found {0} acting factions", ActingFactions.Count); + Log.Information("found {0} responding factions", RespondingFactions.Count); + Log.Information("found {0} behaviours", Behaviours.Count); + } + internal static void PrintAITable() + { + int N = ActingFactions.Count; + int M = RespondingFactions.Count; + + string l = "x\t\t"; + for(int j = 0; j < M; j++) + { + l += $" {RespondingFactions[j]}"; + } + + for(int i = 0; i < N; i++) + { + l = $"{ActingFactions[i]}\t\t"; + for(int j = 0; j < M; j++) + { + // l += $"\t{Behaviours[ConvertSquaredCoordinates(i, j)]}"; + l += $" {StrBehaviour(Behaviours[ConvertSquaredCoordinates(i, j)])}"; + } + Log.Information(l); + } + } + internal static IEnumerable CreateIATable() + { + yield return $"x;{string.Concat(RespondingFactions.Select(x => $"{x};"))}"; + + int N = ActingFactions.Count; + int M = RespondingFactions.Count; + + for(int i = 0; i < N; i++) + { + string l = $"{ActingFactions[i]};"; + for(int j = 0; j < M; j++) + { + l += $"{StrBehaviour(Behaviours[ConvertSquaredCoordinates(i, j)])};"; + } + yield return l; + } + + yield return $"1;- Combat-переход;{string.Concat(Enumerable.Repeat(';', M))}"; + yield return $"1;- Threat-переход;{string.Concat(Enumerable.Repeat(';', M))}"; + yield return $"1;- Flee-переход;{string.Concat(Enumerable.Repeat(';', M))}"; + } +} +public class StatAI +{ + private static void SetElements(string faction, List factionsList, Func conv, params (string, DataAI.Behaviour)[] actions) + { + if (!factionsList.Contains(faction)) + { + DataAI.Behaviours.AddRange(Enumerable.Repeat(DataAI.Behaviour.None, (int)(2 *Math.Sqrt(DataAI.Behaviours.Count) + 1))); + factionsList.Add(faction); + } + + (int i, string _) = factionsList.Enumerate().First(x => x.Item2 == faction); + + foreach ((string f, DataAI.Behaviour b) in actions) + { + (int j, string? factionFound) = factionsList.Enumerate().FirstOrDefault(x => x.Item2 == f); + if (factionFound != null) + { + int index = conv(i, j); + DataAI.Behaviours[index] = b; + } + } + } + public static void SetAction(string faction, params (string, DataAI.Behaviour)[] responses) + { + // add or change a line + SetElements(faction, DataAI.RespondingFactions, DataAI.ConvertSquaredCoordinates, responses); + } + public static void SetResponse(string faction, params (string, DataAI.Behaviour)[] responses) + { + // add or change a column + SetElements(faction, DataAI.ActingFactions, (i, j) => DataAI.ConvertSquaredCoordinates(j, i), responses); + } +} +public partial class Msl +{ + public static void LoadAITable() + { + DataAI.LoadAITable(); + DataAI.PrintAITable(); + } + public static void SaveAITable() + { + static IEnumerable func(IEnumerable input) + { + int sizeTable = 0; + bool ignore_lines = false; + foreach (string item in input) + { + if (item.Contains("setowner")) + { + yield return item; + IEnumerable table = DataAI.CreateIATable().Reverse(); + foreach(string line in table) + { + sizeTable++; + yield return $"push.s \"{line}\""; + yield return "conv.s.v"; + } + ignore_lines = true; + } + else if (item.Contains("NewGMLArray")) + { + yield return $"call.i @@NewGMLArray@@(argc={sizeTable})"; + ignore_lines = false; + continue; + } + + if (!ignore_lines) + { + yield return item; + } + } + } + + Msl.LoadAssemblyAsString(DataAI.TableName) + .Apply(func) + .Save(); + } + public static void InjectTableAction(string faction, params (string, DataAI.Behaviour)[] responses) + { + StatAI.SetAction(faction, responses); + } + public static void InjectTableResponse(string faction, params (string, DataAI.Behaviour)[] responses) + { + StatAI.SetResponse(faction, responses); + } +} + \ No newline at end of file diff --git a/ModUtils/TableUtils/Backers.cs b/ModUtils/TableUtils/LocalizationTables/Backers.cs similarity index 100% rename from ModUtils/TableUtils/Backers.cs rename to ModUtils/TableUtils/LocalizationTables/Backers.cs diff --git a/ModUtils/TableUtils/LocalizationTables/Books.cs b/ModUtils/TableUtils/LocalizationTables/Books.cs new file mode 100644 index 0000000..08a4776 --- /dev/null +++ b/ModUtils/TableUtils/LocalizationTables/Books.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ModShardLauncher.Mods; + +namespace ModShardLauncher; + +public class LocalizationBook : ILocalizationElement +{ + /// + /// Id of the modifier + /// + public string Id { get; set; } + /// + /// Dictionary that contains a translation of the modifier as displayed in the log for each available languages. + /// + public Dictionary Name { get; set; } = new(); + public Dictionary Content { get; set; } = new(); + public Dictionary MidText { get; set; } = new(); + public Dictionary Description { get; set; } = new(); + public Dictionary Type { get; set; } = new(); + /// + /// Return an instance of with filled by an input dictionary. + /// It is expected to have at least an English key. It does not need to follow the convention order of the localization table. + /// + /// For example: + /// + /// LocalizationBook("mySpeechId", + /// new Dictionary < ModLanguage, string > () { {Russian, "speechRu"}, {English, "speechEn"}, {Italian, "speechIt"} }); + /// + /// + /// + /// + /// + public LocalizationBook(string id, + Dictionary name, + Dictionary content, + Dictionary midText, + Dictionary description, + Dictionary type) + { + Id = id; + Name = Localization.SetDictionary(name); + Content = Localization.SetDictionary(content); + MidText = Localization.SetDictionary(midText); + Description = Localization.SetDictionary(description); + Type = Localization.SetDictionary(type); + } + /// + /// Return an instance of with filled by an input string delimited by semi-colon. + /// It is expected to follow the convention order of the localization table. + /// + /// For example: + /// + /// LocalizationBook("mySpeechId", + /// "speechRu;speechEn;speechCh"); + /// + /// + /// + /// + /// + public LocalizationBook(string id, + string name, + string content, + string midText, + string description, + string type) + { + Id = id; + Name = Localization.SetDictionary(name); + Content = Localization.SetDictionary(content); + MidText = Localization.SetDictionary(midText); + Description = Localization.SetDictionary(description); + Type = Localization.SetDictionary(type); + } + /// + /// Create a string delimited by semi-colon that follows the in-game convention order for localization of speechs. + /// + /// For example: + /// + /// LocalizationBook("mySpeechId", "speechRu;speechEn;speechCh").CreateLine(); + /// + /// returns the string "mySpeechId;speechRu;speechEn;speechCh;speechEn;speechEn;speechEn;speechEn;speechEn;speechEn;speechEn;speechEn;speechEn;". + /// + /// + /// + public IEnumerable CreateLine(string? selector) + { + switch(selector) + { + case "name": + yield return $"{Id};{string.Concat(Name.Values.Select(x => @$"{x};"))}"; + break; + case "content": + yield return $"{Id};{string.Concat(Content.Values.Select(x => @$"{x};"))}"; + break; + case "mid_text": + yield return $"{Id};{string.Concat(MidText.Values.Select(x => @$"{x};"))}"; + break; + case "desc": + yield return $"{Id};{string.Concat(Description.Values.Select(x => @$"{x};"))}"; + break; + case "type": + yield return $"{Id};{string.Concat(Type.Values.Select(x => @$"{x};"))}"; + break; + } + } +} +public static partial class Msl +{ + /// + /// Wrapper for the LocalizationBooks class + /// + /// + public static Func, IEnumerable> CreateInjectionBooksLocalization(params LocalizationBook[] books) + { + LocalizationBaseTable localizationBaseTable = new( + ("book_name_end;", "name"), + ("book_content_end;", "content"), + ("book_mid_text_end;", "mid_text"), + ("book_desc_end;", "desc"), + ("book_type_end;", "type") + ); + return localizationBaseTable.CreateInjectionTable(books.Select(x => x as ILocalizationElement).ToList()); + } + public static void InjectTableBooksLocalization(params LocalizationBook[] books) + { + Localization.InjectTable("gml_GlobalScript_table_books", CreateInjectionBooksLocalization(books)); + } +} \ No newline at end of file diff --git a/ModUtils/TableUtils/LocalizationTables/ItemText.cs b/ModUtils/TableUtils/LocalizationTables/ItemText.cs new file mode 100644 index 0000000..b95197c --- /dev/null +++ b/ModUtils/TableUtils/LocalizationTables/ItemText.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ModShardLauncher.Mods; + +namespace ModShardLauncher; + +/// +/// Abstraction the localization of items found in gml_GlobalScript_table_items. +/// +public class LocalizationItem : ILocalizationElement +{ + /// + /// Name of the object in the localization table. + /// + public string Id { get; set; } + /// + /// Dictionary that contains a translation of the item name as displayed in-game for each available languages. + /// + public Dictionary Name { get; set; } = new(); + /// + /// Dictionary that contains a translation of the item effect as displayed in-game for each available languages. + /// + public Dictionary Effect { get; set; } = new(); + /// + /// Dictionary that contains a translation of the item description as displayed in-game for each available languages. + /// + public Dictionary Description { get; set; } = new(); + /// + /// Return an instance of with , and filled by input dictionaries. + /// It is expected to have at least an English key for each dictionary. It does not need to follow the convention order of the localization table. + /// + /// For example: + /// + /// LocalizationItem("myTestItem", + /// new Dictionary < ModLanguage, string > () { {Russian, "testRu"}, {English, "testEn"}, {Italian, "testIt"} }, + /// new Dictionary < ModLanguage, string > () { {Russian, "effectRu"}, {English, "effectEn"}, {Italian, "effectIt"} }, + /// new Dictionary < ModLanguage, string > () { {Russian, "descRu"}, {English, "descEn"}, {Italian, "descIt"} } ); + /// + /// + /// + /// + /// + /// + /// + public LocalizationItem(string id, Dictionary name, Dictionary effect, Dictionary description) + { + Id = id; + Name = Localization.SetDictionary(name); + Effect = Localization.SetDictionary(effect); + Description = Localization.SetDictionary(description); + } + /// + /// Return an instance of with , and filled by input strings delimited by semi-colon. + /// It is expected to follow the convention order of the localization table. + /// + /// For example: + /// + /// LocalizationItem("myTestItem", + /// "testRu;testEn;testCh", + /// "effectRu;effectEn;effectCh", + /// "descRu;descEn;descIt"); + /// + /// + /// + /// + /// + /// + /// + public LocalizationItem(string id, string name, string effect, string description) + { + Id = id; + Name = Localization.SetDictionary(name); + Effect = Localization.SetDictionary(effect); + Description = Localization.SetDictionary(description); + } + /// + /// Create a string delimited by semi-colon that follows the in-game convention order for localization of items. + /// + /// For example: + /// + /// CreateLine("testItem", new Dictionary < ModLanguage, string > () {{Russian, "testRu"}, {English, "testEn"}, {Chinese, "testCh"}, {German, "testGe"}, {Spanish, "testSp"}, + /// {French, "testFr"}, {Italian, "testIt"}, {Portuguese, "testPr"}, {Polish, "testPl"}, {Turkish, "testTu"}, {Japanese, "testJp"}, {Korean, "testKr"}} ); + /// + /// returns the string "testItem;testRu;testEn;testCh;testGe;testSp;testFr;testIt;testPr;testPl;testTu;testJp;testKr;". + /// + /// + /// + /// + public IEnumerable CreateLine(string? selector) + { + switch(selector) + { + case "name": + yield return $"{Id};{string.Concat(Name.Values.Select(x => @$"{x};"))}"; + break; + case "effect": + yield return $"{Id};{string.Concat(Effect.Values.Select(x => @$"{x};"))}"; + break; + case "description": + yield return $"{Id};{string.Concat(Description.Values.Select(x => @$"{x};"))}"; + break; + } + } + // do not use + // it's for legacy purpose, TODO remove them for 1.0 + public void InjectTable() + { + Localization.InjectTable("gml_GlobalScript_table_items", Msl.CreateInjectionItemsLocalization(this)); + } +} +public partial class Msl +{ + /// + /// Wrapper for the LocalizationItem class using dictionnaries + /// + /// + /// + /// + /// + public static Func, IEnumerable> CreateInjectionItemsLocalization(params LocalizationItem[] items) + { + LocalizationBaseTable localizationBaseTable = new( + ("consum_name_end;", "name"), ("consum_mid_end;", "effect"), ("consum_desc_end;", "description") + ); + return localizationBaseTable.CreateInjectionTable(items.Select(x => x as ILocalizationElement).ToList()); + } + public static void InjectTableItemsLocalization(params LocalizationItem[] items) + { + Localization.InjectTable("gml_GlobalScript_table_items", CreateInjectionItemsLocalization(items)); + } + // do not use for legacy usages only, TODO remove them for 1.0 + public static void InjectTableItemLocalization(string id, Dictionary name, Dictionary effect, Dictionary description) + { + LocalizationItem item = new(id, name, effect, description); + Localization.InjectTable("gml_GlobalScript_table_items", CreateInjectionItemsLocalization(item)); + } + // do not use for legacy usages only, TODO remove them for 1.0 + public static void InjectTableItemLocalization(string id, string name, string effect, string description) + { + LocalizationItem item = new(id, name, effect, description); + Localization.InjectTable("gml_GlobalScript_table_items", CreateInjectionItemsLocalization(item)); + } +} \ No newline at end of file diff --git a/ModUtils/TableUtils/LocalizationTables/Logs.cs b/ModUtils/TableUtils/LocalizationTables/Logs.cs new file mode 100644 index 0000000..566624f --- /dev/null +++ b/ModUtils/TableUtils/LocalizationTables/Logs.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ModShardLauncher; +using ModShardLauncher.Mods; + +namespace ModShardLauncher; + +public class LocalizationLogText : ILocalizationElement +{ + public string Id { get; set; } + public Dictionary Name { get; set; } = new(); + public LocalizationLogText(string id, Dictionary name) + { + Id = id; + Name = Localization.SetDictionary(name); + } + public LocalizationLogText(string id, string name) + { + Id = id; + Name = Localization.SetDictionary(name); + } + public IEnumerable CreateLine(string? selector) + { + yield return $"{Id};{string.Concat(Name.Values.Select(x => @$"{x};"))}"; + } +} + +public static partial class Msl +{ + public static Func, IEnumerable> CreateInjectionLogTextLocalization(params LocalizationLogText[] texts) + { + LocalizationBaseTable localizationBaseTable = new( + ("text_end;", null) + ); + return localizationBaseTable.CreateInjectionTable(texts.Select(x => x as ILocalizationElement).ToList()); + } + public static void InjectTableLogTextLocalization(params LocalizationLogText[] texts) + { + Localization.InjectTable("gml_GlobalScript_table_log", CreateInjectionLogTextLocalization(texts)); + } + + public static Func, IEnumerable> CreateInjectionLogWordsLocalization(params LocalizationLogText[] texts) + { + LocalizationBaseTable localizationBaseTable = new( + ("words_end;", null) + ); + return localizationBaseTable.CreateInjectionTable(texts.Select(x => x as ILocalizationElement).ToList()); + } + public static void InjectTableLogWordsLocalization(params LocalizationLogText[] texts) + { + Localization.InjectTable("gml_GlobalScript_table_log", CreateInjectionLogWordsLocalization(texts)); + } + + public static Func, IEnumerable> CreateInjectionLogActionsLocalization(params LocalizationLogText[] texts) + { + LocalizationBaseTable localizationBaseTable = new( + ("actions_end;", null) + ); + return localizationBaseTable.CreateInjectionTable(texts.Select(x => x as ILocalizationElement).ToList()); + } + public static void InjectTableLogActionsLocalization(params LocalizationLogText[] texts) + { + Localization.InjectTable("gml_GlobalScript_table_log", CreateInjectionLogActionsLocalization(texts)); + } + + public static Func, IEnumerable> CreateInjectionLogDamagesLocalization(params LocalizationLogText[] texts) + { + LocalizationBaseTable localizationBaseTable = new( + ("damages_end;", null) + ); + return localizationBaseTable.CreateInjectionTable(texts.Select(x => x as ILocalizationElement).ToList()); + } + public static void InjectTableLogDamagesLocalization(params LocalizationLogText[] texts) + { + Localization.InjectTable("gml_GlobalScript_table_log", CreateInjectionLogDamagesLocalization(texts)); + } + + public static Func, IEnumerable> CreateInjectionLogSymbolsLocalization(params LocalizationLogText[] texts) + { + LocalizationBaseTable localizationBaseTable = new( + ("symbols_end;", null) + ); + return localizationBaseTable.CreateInjectionTable(texts.Select(x => x as ILocalizationElement).ToList()); + } + public static void InjectTableLogSymbolsLocalization(params LocalizationLogText[] texts) + { + Localization.InjectTable("gml_GlobalScript_table_log", CreateInjectionLogSymbolsLocalization(texts)); + } +} \ No newline at end of file diff --git a/ModUtils/TableUtils/LocalizationTables/Modifier.cs b/ModUtils/TableUtils/LocalizationTables/Modifier.cs new file mode 100644 index 0000000..0f522d2 --- /dev/null +++ b/ModUtils/TableUtils/LocalizationTables/Modifier.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ModShardLauncher.Mods; + +namespace ModShardLauncher; + +public class LocalizationModifier : ILocalizationElement +{ + /// + /// Id of the modifier + /// + public string Id { get; set; } + /// + /// Dictionary that contains a translation of the modifier as displayed in the log for each available languages. + /// + public Dictionary Name { get; set; } = new(); + public Dictionary? Description { get; set; } = null; + /// + /// Return an instance of with filled by an input dictionary. + /// It is expected to have at least an English key. It does not need to follow the convention order of the localization table. + /// + /// For example: + /// + /// LocalizationModifier("mySpeechId", + /// new Dictionary < ModLanguage, string > () { {Russian, "speechRu"}, {English, "speechEn"}, {Italian, "speechIt"} }); + /// + /// + /// + /// + /// + public LocalizationModifier(string id, Dictionary name, Dictionary? description) + { + Id = id; + Name = Localization.SetDictionary(name); + if (description != null) Description = Localization.SetDictionary(description); + } + /// + /// Return an instance of with filled by an input string delimited by semi-colon. + /// It is expected to follow the convention order of the localization table. + /// + /// For example: + /// + /// LocalizationModifier("mySpeechId", + /// "speechRu;speechEn;speechCh"); + /// + /// + /// + /// + /// + public LocalizationModifier(string id, string name, string? description) + { + Id = id; + Name = Localization.SetDictionary(name); + if (description != null) Description = Localization.SetDictionary(description); + } + /// + /// Create a string delimited by semi-colon that follows the in-game convention order for localization of speechs. + /// + /// For example: + /// + /// LocalizationModifier("mySpeechId", "speechRu;speechEn;speechCh").CreateLine(); + /// + /// returns the string "mySpeechId;speechRu;speechEn;speechCh;speechEn;speechEn;speechEn;speechEn;speechEn;speechEn;speechEn;speechEn;speechEn;". + /// + /// + /// + public IEnumerable CreateLine(string? selector) + { + switch(selector) + { + case "name": + yield return $"{Id};{string.Concat(Name.Values.Select(x => @$"{x};"))}"; + break; + case "description": + if (Description == null) yield return $"{Id};{string.Concat(Enumerable.Repeat("None;", Msl.ModLanguageSize))}"; + else yield return $"{Id};{string.Concat(Description.Values.Select(x => @$"{x};"))}"; + break; + } + } +} +public static partial class Msl +{ + /// + /// Wrapper for the LocalizationModifiers class + /// + /// + public static Func, IEnumerable> CreateInjectionModifiersLocalization(params LocalizationModifier[] modifiers) + { + LocalizationBaseTable localizationBaseTable = new( + ("buff_name_end;", "name"), ("buff_desc_end;", "description") + ); + return localizationBaseTable.CreateInjectionTable(modifiers.Select(x => x as ILocalizationElement).ToList()); + } + public static void InjectTableModifiersLocalization(params LocalizationModifier[] modifiers) + { + Localization.InjectTable("gml_GlobalScript_table_effects", CreateInjectionModifiersLocalization(modifiers)); + } +} \ No newline at end of file diff --git a/ModUtils/TableUtils/LocalizationTables/NPCLines.cs b/ModUtils/TableUtils/LocalizationTables/NPCLines.cs new file mode 100644 index 0000000..b3223bc --- /dev/null +++ b/ModUtils/TableUtils/LocalizationTables/NPCLines.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ModShardLauncher.Mods; + +namespace ModShardLauncher; + +/// +/// Abstraction for the localization of sentences found in gml_GlobalScript_table_NPC_Lines. +/// +public class LocalizationSentence : ILocalizationElement +{ + /// + /// Id of the sentence + /// + public string Id { get; set; } + public string Tags { get; set; } = "any"; + public string Role { get; set; } = "any"; + public string Type { get; set; } = "any"; + public string Faction { get; set; } = "any"; + public string Settlement { get; set; } = "any"; + /// + /// Dictionary that contains a translation of the sentence as displayed in dialog for each available languages. + /// + public Dictionary Sentence { get; set; } = new(); + /// + /// Return an instance of with filled by an input dictionary. + /// It is expected to have at least an English key. It does not need to follow the convention order of the localization table. + /// + /// For example: + /// + /// LocalizationSentence("mySentenceId", + /// new Dictionary < ModLanguage, string > () { {Russian, "sentenceRu"}, {English, "sentenceEn"}, {Italian, "sentenceIt"} }); + /// + /// + /// + /// + /// + public LocalizationSentence(string id, Dictionary sentence) + { + Id = id; + Sentence = Localization.SetDictionary(sentence); + + } + /// + /// Return an instance of with filled by an input string delimited by semi-colon. + /// It is expected to follow the convention order of the localization table. + /// + /// For example: + /// + /// LocalizationSentence("mySentenceId", + /// "sentenceRu;sentenceEn;sentenceCh"); + /// + /// + /// + /// + /// + public LocalizationSentence(string id, string sentence) + { + Id = id; + Sentence = Localization.SetDictionary(sentence); + } + /// + /// Create a string delimited by semi-colon that follows the in-game convention order for localization of sentences. + /// + /// For example: + /// + /// LocalizationSentence("mySentenceId", "sentenceRu;sentenceEn;sentenceCh").CreateLine(); + /// + /// returns the string "mySentenceId;any;any;any;any;any;sentenceRu;sentenceEn;sentenceCh;sentenceEn;sentenceEn;sentenceEn;sentenceEn;sentenceEn;sentenceEn;sentenceEn;sentenceEn;sentenceEn;". + /// + /// + /// + public IEnumerable CreateLine(string? _) + { + string line = string.Format("{0};{1};{2};{3};{4};{5};", Id, Tags, Role, Type, Faction, Settlement); + line += string.Concat(Sentence.Values.Select(x => @$"{x};")); + + yield return line; + } +} +public static partial class Msl +{ + /// + /// Wrapper for the LocalizationDialog class + /// + /// + public static Func, IEnumerable> CreateInjectionDialogLocalization(params LocalizationSentence[] sentences) + { + LocalizationBaseTable localizationBaseTable = new( + ("[NPC] GREETINGS;", null) + ); + return localizationBaseTable.CreateInjectionTable(sentences.Select(x => x as ILocalizationElement).ToList()); + } + public static void InjectTableDialogLocalization(params LocalizationSentence[] sentences) + { + Localization.InjectTable("gml_GlobalScript_table_lines", CreateInjectionDialogLocalization(sentences)); + } +} \ No newline at end of file diff --git a/ModUtils/TableUtils/LocalizationTables/NPCNames.cs b/ModUtils/TableUtils/LocalizationTables/NPCNames.cs new file mode 100644 index 0000000..3a64d75 --- /dev/null +++ b/ModUtils/TableUtils/LocalizationTables/NPCNames.cs @@ -0,0 +1,263 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ModShardLauncher.Mods; + +namespace ModShardLauncher; + +/// +/// Abstraction the localization of items found in gml_GlobalScript_table_consumables. +/// +public class LocalizationName : ILocalizationElement +{ + /// + /// Dictionary that contains a translation of the item name as displayed in-game for each available languages. + /// + public Dictionary Name { get; set; } = new(); + /// + /// Return an instance of with , and filled by input dictionaries. + /// It is expected to have at least an English key for each dictionary. It does not need to follow the convention order of the localization table. + /// + /// For example: + /// + /// LocalizationItem("myTestItem", + /// new Dictionary < ModLanguage, string > () { {Russian, "testRu"}, {English, "testEn"}, {Italian, "testIt"} }, + /// new Dictionary < ModLanguage, string > () { {Russian, "effectRu"}, {English, "effectEn"}, {Italian, "effectIt"} }, + /// new Dictionary < ModLanguage, string > () { {Russian, "descRu"}, {English, "descEn"}, {Italian, "descIt"} } ); + /// + /// + /// + /// + /// + /// + /// + public LocalizationName(Dictionary name) + { + Name = Localization.SetDictionary(name); + } + /// + /// Return an instance of with , and filled by input strings delimited by semi-colon. + /// It is expected to follow the convention order of the localization table. + /// + /// For example: + /// + /// LocalizationItem("myTestItem", + /// "testRu;testEn;testCh", + /// "effectRu;effectEn;effectCh", + /// "descRu;descEn;descIt"); + /// + /// + /// + /// + /// + /// + /// + public LocalizationName(string name) + { + Name = Localization.SetDictionary(name); + } + /// + /// Create a string delimited by semi-colon that follows the in-game convention order for localization of items. + /// + /// For example: + /// + /// CreateLine("testItem", new Dictionary < ModLanguage, string > () {{Russian, "testRu"}, {English, "testEn"}, {Chinese, "testCh"}, {German, "testGe"}, {Spanish, "testSp"}, + /// {French, "testFr"}, {Italian, "testIt"}, {Portuguese, "testPr"}, {Polish, "testPl"}, {Turkish, "testTu"}, {Japanese, "testJp"}, {Korean, "testKr"}} ); + /// + /// returns the string "testItem;testRu;testEn;testCh;testGe;testSp;testFr;testIt;testPr;testPl;testTu;testJp;testKr;//;". + /// + /// + /// + /// + public IEnumerable CreateLine(string? selector) + { + yield return $";{string.Concat(Name.Values.Select(x => @$"{x};"))}"; + } +} +public class LocalizationQuestName : ILocalizationElement +{ + /// + /// Name of the object in the localization table. + /// + public string Id { get; set; } + /// + /// Dictionary that contains a translation of the item name as displayed in-game for each available languages. + /// + public Dictionary Name { get; set; } = new(); + /// + /// Return an instance of with , and filled by input dictionaries. + /// It is expected to have at least an English key for each dictionary. It does not need to follow the convention order of the localization table. + /// + /// For example: + /// + /// LocalizationItem("myTestItem", + /// new Dictionary < ModLanguage, string > () { {Russian, "testRu"}, {English, "testEn"}, {Italian, "testIt"} }, + /// new Dictionary < ModLanguage, string > () { {Russian, "effectRu"}, {English, "effectEn"}, {Italian, "effectIt"} }, + /// new Dictionary < ModLanguage, string > () { {Russian, "descRu"}, {English, "descEn"}, {Italian, "descIt"} } ); + /// + /// + /// + /// + /// + /// + /// + public LocalizationQuestName(string id, Dictionary name) + { + Id = id; + Name = Localization.SetDictionary(name); + } + /// + /// Return an instance of with , and filled by input strings delimited by semi-colon. + /// It is expected to follow the convention order of the localization table. + /// + /// For example: + /// + /// LocalizationItem("myTestItem", + /// "testRu;testEn;testCh", + /// "effectRu;effectEn;effectCh", + /// "descRu;descEn;descIt"); + /// + /// + /// + /// + /// + /// + /// + public LocalizationQuestName(string id, string name) + { + Id = id; + Name = Localization.SetDictionary(name); + } + /// + /// Create a string delimited by semi-colon that follows the in-game convention order for localization of items. + /// + /// For example: + /// + /// CreateLine("testItem", new Dictionary < ModLanguage, string > () {{Russian, "testRu"}, {English, "testEn"}, {Chinese, "testCh"}, {German, "testGe"}, {Spanish, "testSp"}, + /// {French, "testFr"}, {Italian, "testIt"}, {Portuguese, "testPr"}, {Polish, "testPl"}, {Turkish, "testTu"}, {Japanese, "testJp"}, {Korean, "testKr"}} ); + /// + /// returns the string "testItem;testRu;testEn;testCh;testGe;testSp;testFr;testIt;testPr;testPl;testTu;testJp;testKr;//;". + /// + /// + /// + /// + public IEnumerable CreateLine(string? selector) + { + yield return $"{Id};{string.Concat(Name.Values.Select(x => @$"{x};"))}"; + } +} +public class LocalizationOccupationName : ILocalizationElement +{ + /// + /// Name of the object in the localization table. + /// + public string Id { get; set; } + /// + /// Dictionary that contains a translation of the item name as displayed in-game for each available languages. + /// + public Dictionary Name { get; set; } = new(); + /// + /// Return an instance of with , and filled by input dictionaries. + /// It is expected to have at least an English key for each dictionary. It does not need to follow the convention order of the localization table. + /// + /// For example: + /// + /// LocalizationItem("myTestItem", + /// new Dictionary < ModLanguage, string > () { {Russian, "testRu"}, {English, "testEn"}, {Italian, "testIt"} }, + /// new Dictionary < ModLanguage, string > () { {Russian, "effectRu"}, {English, "effectEn"}, {Italian, "effectIt"} }, + /// new Dictionary < ModLanguage, string > () { {Russian, "descRu"}, {English, "descEn"}, {Italian, "descIt"} } ); + /// + /// + /// + /// + /// + /// + /// + public LocalizationOccupationName(string id, Dictionary name) + { + Id = id; + Name = Localization.SetDictionary(name); + } + /// + /// Return an instance of with , and filled by input strings delimited by semi-colon. + /// It is expected to follow the convention order of the localization table. + /// + /// For example: + /// + /// LocalizationItem("myTestItem", + /// "testRu;testEn;testCh", + /// "effectRu;effectEn;effectCh", + /// "descRu;descEn;descIt"); + /// + /// + /// + /// + /// + /// + /// + public LocalizationOccupationName(string id, string name) + { + Id = id; + Name = Localization.SetDictionary(name); + } + /// + /// Create a string delimited by semi-colon that follows the in-game convention order for localization of items. + /// + /// For example: + /// + /// CreateLine("testItem", new Dictionary < ModLanguage, string > () {{Russian, "testRu"}, {English, "testEn"}, {Chinese, "testCh"}, {German, "testGe"}, {Spanish, "testSp"}, + /// {French, "testFr"}, {Italian, "testIt"}, {Portuguese, "testPr"}, {Polish, "testPl"}, {Turkish, "testTu"}, {Japanese, "testJp"}, {Korean, "testKr"}} ); + /// + /// returns the string "testItem;testRu;testEn;testCh;testGe;testSp;testFr;testIt;testPr;testPl;testTu;testJp;testKr;//;". + /// + /// + /// + /// + public IEnumerable CreateLine(string? selector) + { + yield return $"{Id};{string.Concat(Name.Values.Select(x => @$"{x};"))}"; + } +} +public partial class Msl +{ + /// + /// Wrapper for the LocalizationItem class using dictionnaries + /// + /// + /// + /// + /// + public static Func, IEnumerable> CreateInjectionNamesLocalization(params LocalizationName[] names) + { + LocalizationBaseTable localizationBaseTable = new( + ("Names_end;", "name") + ); + return localizationBaseTable.CreateInjectionTable(names.Select(x => x as ILocalizationElement).ToList()); + } + public static void InjectTableNamesLocalization(params LocalizationName[] names) + { + Localization.InjectTable("gml_GlobalScript_table_names", CreateInjectionNamesLocalization(names)); + } + public static Func, IEnumerable> CreateInjectionQuestNamesLocalization(params LocalizationQuestName[] questNames) + { + LocalizationBaseTable localizationBaseTable = new( + ("Constant_Name_end;", "name") + ); + return localizationBaseTable.CreateInjectionTable(questNames.Select(x => x as ILocalizationElement).ToList()); + } + public static void InjectTableQuestNamesLocalization(params LocalizationQuestName[] questNames) + { + Localization.InjectTable("gml_GlobalScript_table_names", CreateInjectionQuestNamesLocalization(questNames)); + } + public static Func, IEnumerable> CreateInjectionOccupationNamesLocalization(params LocalizationOccupationName[] occupationNames) + { + LocalizationBaseTable localizationBaseTable = new( + ("NPC_info_end;", "name") + ); + return localizationBaseTable.CreateInjectionTable(occupationNames.Select(x => x as ILocalizationElement).ToList()); + } + public static void InjectTableOccupationNamesLocalization(params LocalizationOccupationName[] occupationNames) + { + Localization.InjectTable("gml_GlobalScript_table_names", CreateInjectionOccupationNamesLocalization(occupationNames)); + } +} \ No newline at end of file diff --git a/ModUtils/TableUtils/LocalizationTables/Skills.cs b/ModUtils/TableUtils/LocalizationTables/Skills.cs new file mode 100644 index 0000000..559bc4c --- /dev/null +++ b/ModUtils/TableUtils/LocalizationTables/Skills.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ModShardLauncher.Mods; + +namespace ModShardLauncher; + +public class LocalizationSkill : ILocalizationElement +{ + /// + /// Id of the modifier + /// + public string Id { get; set; } + /// + /// Dictionary that contains a translation of the modifier as displayed in the log for each available languages. + /// + public Dictionary Name { get; set; } = new(); + public Dictionary Description { get; set; } = new(); + /// + /// Return an instance of with filled by an input dictionary. + /// It is expected to have at least an English key. It does not need to follow the convention order of the localization table. + /// + /// For example: + /// + /// LocalizationSkill("mySpeechId", + /// new Dictionary < ModLanguage, string > () { {Russian, "speechRu"}, {English, "speechEn"}, {Italian, "speechIt"} }); + /// + /// + /// + /// + /// + public LocalizationSkill(string id, Dictionary name, Dictionary description) + { + Id = id; + Name = Localization.SetDictionary(name); + Description = Localization.SetDictionary(description); + } + /// + /// Return an instance of with filled by an input string delimited by semi-colon. + /// It is expected to follow the convention order of the localization table. + /// + /// For example: + /// + /// LocalizationSkill("mySpeechId", + /// "speechRu;speechEn;speechCh"); + /// + /// + /// + /// + /// + public LocalizationSkill(string id, string name, string description) + { + Id = id; + Name = Localization.SetDictionary(name); + Description = Localization.SetDictionary(description); + } + /// + /// Create a string delimited by semi-colon that follows the in-game convention order for localization of speechs. + /// + /// For example: + /// + /// LocalizationSkill("mySpeechId", "speechRu;speechEn;speechCh").CreateLine(); + /// + /// returns the string "mySpeechId;speechRu;speechEn;speechCh;speechEn;speechEn;speechEn;speechEn;speechEn;speechEn;speechEn;speechEn;speechEn;". + /// + /// + /// + public IEnumerable CreateLine(string? selector) + { + switch(selector) + { + case "name": + yield return $"{Id};{string.Concat(Name.Values.Select(x => @$"{x};"))}"; + break; + case "description": + yield return $"{Id};{string.Concat(Description.Values.Select(x => @$"{x};"))}"; + break; + } + } +} +public static partial class Msl +{ + /// + /// Wrapper for the LocalizationSkills class + /// + /// + public static Func, IEnumerable> CreateInjectionSkillsLocalization(params LocalizationSkill[] skills) + { + LocalizationBaseTable localizationBaseTable = new( + ("skill_name_end;", "name"), ("skill_desc_end;", "description") + ); + return localizationBaseTable.CreateInjectionTable(skills.Select(x => x as ILocalizationElement).ToList()); + } + public static void InjectTableSkillsLocalization(params LocalizationSkill[] skills) + { + Localization.InjectTable("gml_GlobalScript_table_skills", CreateInjectionSkillsLocalization(skills)); + } +} \ No newline at end of file diff --git a/ModUtils/TableUtils/LocalizationTables/Speech.cs b/ModUtils/TableUtils/LocalizationTables/Speech.cs new file mode 100644 index 0000000..0332f69 --- /dev/null +++ b/ModUtils/TableUtils/LocalizationTables/Speech.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ModShardLauncher.Mods; + +namespace ModShardLauncher; + +public class LocalizationSpeech : ILocalizationElement +{ + /// + /// Id of the speech + /// + public string Id { get; set; } + /// + /// Dictionary that contains a translation of the speech as displayed in the log for each available languages. + /// + public List> Speeches { get; set; } = new(); + /// + /// Return an instance of with filled by an input dictionary. + /// It is expected to have at least an English key. It does not need to follow the convention order of the localization table. + /// + /// For example: + /// + /// LocalizationSpeech("mySpeechId", + /// new Dictionary < ModLanguage, string > () { {Russian, "speechRu"}, {English, "speechEn"}, {Italian, "speechIt"} }); + /// + /// + /// + /// + /// + public LocalizationSpeech(string id, params Dictionary[] speeches) + { + Id = id; + Speeches = speeches.Select(x => Localization.SetDictionary(x)).ToList(); + } + /// + /// Return an instance of with filled by an input string delimited by semi-colon. + /// It is expected to follow the convention order of the localization table. + /// + /// For example: + /// + /// LocalizationSpeech("mySpeechId", + /// "speechRu;speechEn;speechCh"); + /// + /// + /// + /// + /// + public LocalizationSpeech(string id, params string[] speeches) + { + Id = id; + Speeches = speeches.Select(x => Localization.SetDictionary(x)).ToList(); + } + /// + /// Create a string delimited by semi-colon that follows the in-game convention order for localization of speechs. + /// + /// For example: + /// + /// LocalizationSpeech("mySpeechId", "speechRu;speechEn;speechCh").CreateLine(); + /// + /// returns the string "mySpeechId;speechRu;speechEn;speechCh;speechEn;speechEn;speechEn;speechEn;speechEn;speechEn;speechEn;speechEn;speechEn;". + /// + /// + /// + public IEnumerable CreateLine(string? _) + { + yield return $"{Id};{string.Concat(Enumerable.Repeat(@$"{Id};", Msl.ModLanguageSize))}"; + foreach(Dictionary speech in Speeches) + { + yield return $";{string.Concat(speech.Values.Select(x => @$"{x};"))}"; + } + yield return $"{Id}_end;{string.Concat(Enumerable.Repeat(@$"{Id}_end;", Msl.ModLanguageSize))}"; + } +} +public static partial class Msl +{ + /// + /// Wrapper for the LocalizationSpeeches class + /// + /// + public static Func, IEnumerable> CreateInjectionSpeechesLocalization(params LocalizationSpeech[] speeches) + { + LocalizationBaseTable localizationBaseTable = new( + ("FORBIDDEN MAGIC;", null) + ); + return localizationBaseTable.CreateInjectionTable(speeches.Select(x => x as ILocalizationElement).ToList()); + } + public static void InjectTableSpeechesLocalization(params LocalizationSpeech[] speeches) + { + Localization.InjectTable("gml_GlobalScript_table_speech", CreateInjectionSpeechesLocalization(speeches)); + } +} \ No newline at end of file diff --git a/ModUtils/TableUtils/LocalizationTables/Text.cs b/ModUtils/TableUtils/LocalizationTables/Text.cs new file mode 100644 index 0000000..e4bf7b7 --- /dev/null +++ b/ModUtils/TableUtils/LocalizationTables/Text.cs @@ -0,0 +1,279 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ModShardLauncher; +using ModShardLauncher.Mods; + +namespace ModShardLauncher; + +public class LocalizationTextTree : ILocalizationElement +{ + /// + /// Id of the modifier + /// + public string Id { get; set; } + /// + /// Dictionary that contains a translation of the modifier as displayed in the log for each available languages. + /// + public Dictionary Tier { get; set; } = new(); + public Dictionary Hover { get; set; } = new(); + /// + /// Return an instance of with filled by an input dictionary. + /// It is expected to have at least an English key. It does not need to follow the convention order of the localization table. + /// + /// For example: + /// + /// LocalizationTextTree("mySpeechId", + /// new Dictionary < ModLanguage, string > () { {Russian, "speechRu"}, {English, "speechEn"}, {Italian, "speechIt"} }); + /// + /// + /// + /// + /// + public LocalizationTextTree(string id, Dictionary tier, Dictionary hover) + { + Id = id; + Tier = Localization.SetDictionary(tier); + Hover = Localization.SetDictionary(hover); + } + /// + /// Return an instance of with filled by an input string delimited by semi-colon. + /// It is expected to follow the convention order of the localization table. + /// + /// For example: + /// + /// LocalizationTextTree("mySpeechId", + /// "speechRu;speechEn;speechCh"); + /// + /// + /// + /// + /// + public LocalizationTextTree(string id, string tier, string hover) + { + Id = id; + Tier = Localization.SetDictionary(tier); + Hover = Localization.SetDictionary(hover); + } + /// + /// Create a string delimited by semi-colon that follows the in-game convention order for localization of speechs. + /// + /// For example: + /// + /// LocalizationTextTree("mySpeechId", "speechRu;speechEn;speechCh").CreateLine(); + /// + /// returns the string "mySpeechId;speechRu;speechEn;speechCh;speechEn;speechEn;speechEn;speechEn;speechEn;speechEn;speechEn;speechEn;speechEn;". + /// + /// + /// + public IEnumerable CreateLine(string? selector) + { + switch(selector) + { + case "tier": + yield return $"{Id};{string.Concat(Tier.Values.Select(x => @$"{x};"))}"; + break; + case "hover": + yield return $"{Id};{string.Concat(Hover.Values.Select(x => @$"{x};"))}"; + break; + } + } +} +public class LocalizationTextRarity : ILocalizationElement +{ + /// + /// Id of the modifier + /// + public string Id { get; set; } + /// + /// Dictionary that contains a translation of the modifier as displayed in the log for each available languages. + /// + public Dictionary Name { get; set; } = new(); + /// + /// Return an instance of with filled by an input dictionary. + /// It is expected to have at least an English key. It does not need to follow the convention order of the localization table. + /// + /// For example: + /// + /// LocalizationTextRarity("mySpeechId", + /// new Dictionary < ModLanguage, string > () { {Russian, "speechRu"}, {English, "speechEn"}, {Italian, "speechIt"} }); + /// + /// + /// + /// + /// + public LocalizationTextRarity(string id, Dictionary name) + { + Id = id; + Name = Localization.SetDictionary(name); + } + /// + /// Return an instance of with filled by an input string delimited by semi-colon. + /// It is expected to follow the convention order of the localization table. + /// + /// For example: + /// + /// LocalizationTextRarity("mySpeechId", + /// "speechRu;speechEn;speechCh"); + /// + /// + /// + /// + /// + public LocalizationTextRarity(string id, string name) + { + Id = id; + Name = Localization.SetDictionary(name); + } + /// + /// Create a string delimited by semi-colon that follows the in-game convention order for localization of speechs. + /// + /// For example: + /// + /// LocalizationTextTree("mySpeechId", "speechRu;speechEn;speechCh").CreateLine(); + /// + /// returns the string "mySpeechId;speechRu;speechEn;speechCh;speechEn;speechEn;speechEn;speechEn;speechEn;speechEn;speechEn;speechEn;speechEn;". + /// + /// + /// + public IEnumerable CreateLine(string? selector) + { + yield return $"{Id};{string.Concat(Name.Values.Select(x => @$"{x};"))}"; + } +} +public class LocalizationTextContext : ILocalizationElement +{ + /// + /// Id of the modifier + /// + public string Id { get; set; } + /// + /// Dictionary that contains a translation of the modifier as displayed in the log for each available languages. + /// + public Dictionary Name { get; set; } = new(); + /// + /// Return an instance of with filled by an input dictionary. + /// It is expected to have at least an English key. It does not need to follow the convention order of the localization table. + /// + /// For example: + /// + /// LocalizationTextContext("mySpeechId", + /// new Dictionary < ModLanguage, string > () { {Russian, "speechRu"}, {English, "speechEn"}, {Italian, "speechIt"} }); + /// + /// + /// + /// + /// + public LocalizationTextContext(string id, Dictionary name) + { + Id = id; + Name = Localization.SetDictionary(name); + } + /// + /// Return an instance of with filled by an input string delimited by semi-colon. + /// It is expected to follow the convention order of the localization table. + /// + /// For example: + /// + /// LocalizationTextContext("mySpeechId", + /// "speechRu;speechEn;speechCh"); + /// + /// + /// + /// + /// + public LocalizationTextContext(string id, string name) + { + Id = id; + Name = Localization.SetDictionary(name); + } + /// + /// Create a string delimited by semi-colon that follows the in-game convention order for localization of speechs. + /// + /// For example: + /// + /// LocalizationTextTree("mySpeechId", "speechRu;speechEn;speechCh").CreateLine(); + /// + /// returns the string "mySpeechId;speechRu;speechEn;speechCh;speechEn;speechEn;speechEn;speechEn;speechEn;speechEn;speechEn;speechEn;speechEn;". + /// + /// + /// + public IEnumerable CreateLine(string? selector) + { + yield return $"{Id};{string.Concat(Name.Values.Select(x => @$"{x};"))}"; + } +} +public class LocalizationCraftingCategory : ILocalizationElement +{ + public string Id { get; set; } + public Dictionary Name { get; set; } = new(); + public LocalizationCraftingCategory(string id, Dictionary name) + { + Id = id; + Name = Localization.SetDictionary(name); + } + public LocalizationCraftingCategory(string id, string name) + { + Id = id; + Name = Localization.SetDictionary(name); + } + public IEnumerable CreateLine(string? selector) + { + yield return $"{Id};{string.Concat(Name.Values.Select(x => @$"{x};"))}"; + } +} +// TODO : psychic injection +public static partial class Msl +{ + /// + /// Wrapper for the LocalizationTextTrees class + /// + /// + public static Func, IEnumerable> CreateInjectionTextTreesLocalization(params LocalizationTextTree[] trees) + { + LocalizationBaseTable localizationBaseTable = new( + ("Tier_name_end;", "tier"), ("skilltree_hover_end;", "hover") + ); + return localizationBaseTable.CreateInjectionTable(trees.Select(x => x as ILocalizationElement).ToList()); + } + public static void InjectTableTextTreesLocalization(params LocalizationTextTree[] trees) + { + Localization.InjectTable("gml_GlobalScript_table_text", CreateInjectionTextTreesLocalization(trees)); + } + + public static Func, IEnumerable> CreateInjectionTextRaritysLocalization(params LocalizationTextRarity[] rarity) + { + LocalizationBaseTable localizationBaseTable = new( + ("rarity_end;", null) + ); + return localizationBaseTable.CreateInjectionTable(rarity.Select(x => x as ILocalizationElement).ToList()); + } + public static void InjectTableTextRaritysLocalization(params LocalizationTextRarity[] rarity) + { + Localization.InjectTable("gml_GlobalScript_table_text", CreateInjectionTextRaritysLocalization(rarity)); + } + + public static Func, IEnumerable> CreateInjectionTextContextsLocalization(params LocalizationTextContext[] modifiers) + { + LocalizationBaseTable localizationBaseTable = new( + ("context_menu_end;", null) + ); + return localizationBaseTable.CreateInjectionTable(modifiers.Select(x => x as ILocalizationElement).ToList()); + } + public static void InjectTableTextContextsLocalization(params LocalizationTextContext[] contexts) + { + Localization.InjectTable("gml_GlobalScript_table_text", CreateInjectionTextContextsLocalization(contexts)); + } + + public static Func, IEnumerable> CreateInjectionTextCraftingCategoryLocalization(params LocalizationCraftingCategory[] categories) + { + LocalizationBaseTable localizationBaseTable = new( + ("crafting_category_end;", null) + ); + return localizationBaseTable.CreateInjectionTable(categories.Select(x => x as ILocalizationElement).ToList()); + } + public static void InjectTableTextCraftingCategoryLocalization(params LocalizationCraftingCategory[] categories) + { + Localization.InjectTable("gml_GlobalScript_table_text", CreateInjectionTextCraftingCategoryLocalization(categories)); + } +} diff --git a/ModUtils/TableUtils/LocalizationTables/WeaponsText.cs b/ModUtils/TableUtils/LocalizationTables/WeaponsText.cs new file mode 100644 index 0000000..2b879ea --- /dev/null +++ b/ModUtils/TableUtils/LocalizationTables/WeaponsText.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ModShardLauncher.Mods; + +namespace ModShardLauncher; + +public class LocalizationWeaponText : ILocalizationElement +{ + /// + /// Id of the modifier + /// + public string Id { get; set; } + /// + /// Dictionary that contains a translation of the modifier as displayed in the log for each available languages. + /// + public Dictionary Name { get; set; } = new(); + public Dictionary Description { get; set; } = new(); + /// + /// Return an instance of with filled by an input dictionary. + /// It is expected to have at least an English key. It does not need to follow the convention order of the localization table. + /// + /// For example: + /// + /// LocalizationWeaponText("mySpeechId", + /// new Dictionary < ModLanguage, string > () { {Russian, "speechRu"}, {English, "speechEn"}, {Italian, "speechIt"} }); + /// + /// + /// + /// + /// + public LocalizationWeaponText(string id, Dictionary name, Dictionary description) + { + Id = id; + Name = Localization.SetDictionary(name); + Description = Localization.SetDictionary(description); + } + /// + /// Return an instance of with filled by an input string delimited by semi-colon. + /// It is expected to follow the convention order of the localization table. + /// + /// For example: + /// + /// LocalizationWeaponText("mySpeechId", + /// "speechRu;speechEn;speechCh"); + /// + /// + /// + /// + /// + public LocalizationWeaponText(string id, string name, string description) + { + Id = id; + Name = Localization.SetDictionary(name); + Description = Localization.SetDictionary(description); + } + /// + /// Create a string delimited by semi-colon that follows the in-game convention order for localization of speechs. + /// + /// For example: + /// + /// LocalizationWeaponText("mySpeechId", "speechRu;speechEn;speechCh").CreateLine(); + /// + /// returns the string "mySpeechId;speechRu;speechEn;speechCh;speechEn;speechEn;speechEn;speechEn;speechEn;speechEn;speechEn;speechEn;speechEn;". + /// + /// + /// + public IEnumerable CreateLine(string? selector) + { + switch(selector) + { + case "name": + yield return $"{Id};{string.Concat(Name.Values.Select(x => @$"{x};"))}"; + break; + case "description": + yield return $"{Id};{string.Concat(Description.Values.Select(x => @$"{x};"))}"; + break; + } + } +} +public static partial class Msl +{ + /// + /// Wrapper for the LocalizationWeaponTexts class + /// + /// + public static Func, IEnumerable> CreateInjectionWeaponTextsLocalization(params LocalizationWeaponText[] weaponTexts) + { + LocalizationBaseTable localizationBaseTable = new( + ("weapon_name_end;", "name"), ("weapon_desc_end;", "description") + ); + return localizationBaseTable.CreateInjectionTable(weaponTexts.Select(x => x as ILocalizationElement).ToList()); + } + public static void InjectTableWeaponTextsLocalization(params LocalizationWeaponText[] weaponTexts) + { + Localization.InjectTable("gml_GlobalScript_table_equipment", CreateInjectionWeaponTextsLocalization(weaponTexts)); + } +} \ No newline at end of file diff --git a/ModUtils/TableUtils/LocalizationUtils.cs b/ModUtils/TableUtils/LocalizationUtils.cs index 785d94d..ea5b49e 100644 --- a/ModUtils/TableUtils/LocalizationUtils.cs +++ b/ModUtils/TableUtils/LocalizationUtils.cs @@ -1,8 +1,14 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.IO; using System.Linq; +using System.Text.RegularExpressions; using ModShardLauncher.Mods; +using Serilog; +using UndertaleModLib; +using UndertaleModLib.Decompiler; +using UndertaleModLib.Models; namespace ModShardLauncher; @@ -111,337 +117,90 @@ static public Dictionary SetDictionary(string source) } return dest; } -} -/// -/// Abstraction the localization of items found in gml_GlobalScript_table_items. -/// -public class LocalizationItem -{ - /// - /// Name of the object in the localization table. - /// - public string OName { get; set; } - /// - /// Dictionary that contains a translation of the item name as displayed in-game for each available languages. - /// - public Dictionary ConsumableName { get; set; } = new(); - /// - /// Dictionary that contains a translation of the item effect as displayed in-game for each available languages. - /// - public Dictionary ConsumableID { get; set; } = new(); - /// - /// Dictionary that contains a translation of the item description as displayed in-game for each available languages. - /// - public Dictionary ConsumableDescription { get; set; } = new(); - /// - /// Return an instance of with empty , and . - /// - /// For example: - /// - /// LocalizationItem("myTestItem"); - /// - /// - /// - /// - public LocalizationItem(string oName) - { - OName = oName; - } - /// - /// Return an instance of with , and filled by input dictionaries. - /// It is expected to have at least an English key for each dictionary. It does not need to follow the convention order of the localization table. - /// - /// For example: - /// - /// LocalizationItem("myTestItem", - /// new Dictionary < ModLanguage, string > () { {Russian, "testRu"}, {English, "testEn"}, {Italian, "testIt"} }, - /// new Dictionary < ModLanguage, string > () { {Russian, "effectRu"}, {English, "effectEn"}, {Italian, "effectIt"} }, - /// new Dictionary < ModLanguage, string > () { {Russian, "descRu"}, {English, "descEn"}, {Italian, "descIt"} } ); - /// - /// - /// - /// - /// - /// - /// - public LocalizationItem(string oName, Dictionary dictName, Dictionary dictID, Dictionary dictDescription) - { - OName = oName; - ConsumableName = Localization.SetDictionary(dictName); - ConsumableID = Localization.SetDictionary(dictID); - ConsumableDescription = Localization.SetDictionary(dictDescription); - } - /// - /// Return an instance of with , and filled by input strings delimited by semi-colon. - /// It is expected to follow the convention order of the localization table. - /// - /// For example: - /// - /// LocalizationItem("myTestItem", - /// "testRu;testEn;testCh", - /// "effectRu;effectEn;effectCh", - /// "descRu;descEn;descIt"); - /// - /// - /// - /// - /// - /// - /// - public LocalizationItem(string oName, string valuesName, string valuesID, string valuesDescription) + static public Func, IEnumerable> CreateInjectionTable(params (string anchor, IEnumerable elements)[] datas) { - OName = oName; - ConsumableName = Localization.SetDictionary(valuesName); - ConsumableID = Localization.SetDictionary(valuesID); - ConsumableDescription = Localization.SetDictionary(valuesDescription); - } - /// - /// Create a string delimited by semi-colon that follows the in-game convention order for localization of items. - /// - /// For example: - /// - /// CreateLine("testItem", new Dictionary < ModLanguage, string > () {{Russian, "testRu"}, {English, "testEn"}, {Chinese, "testCh"}, {German, "testGe"}, {Spanish, "testSp"}, - /// {French, "testFr"}, {Italian, "testIt"}, {Portuguese, "testPr"}, {Polish, "testPl"}, {Turkish, "testTu"}, {Japanese, "testJp"}, {Korean, "testKr"}} ); - /// - /// returns the string "testItem;testRu;testEn;testCh;testGe;testSp;testFr;testIt;testPr;testPl;testTu;testJp;testKr;". - /// - /// - /// - /// - /// - static private string CreateLine(string oName, Dictionary dict) - { - string line = oName; - foreach (KeyValuePair kp in dict) - { - line += ";"; - line += kp.Value; - } - return line + ";"; - } - /// - /// Browse a table with an iterator, and at special lines, yield a new line constructed by the dictionaries , and . - /// - /// - /// - private IEnumerable EditTable(IEnumerable table) - { - foreach (string line in table) + IEnumerable func(IEnumerable input) { - if (line.Contains("consum_name_end")) - { - yield return CreateLine(OName, ConsumableName); - } - else if (line.Contains("consum_mid_end")) - { - yield return CreateLine(OName, ConsumableID); - } - else if (line.Contains("consum_desc_end")) + int extraLines = 0; + foreach (string item in input) { - yield return CreateLine(OName, ConsumableDescription); + if (item.Contains("NewGMLArray")) + { + int nLines = int.Parse(Regex.Match(item, @"argc=(\d+)").Groups[1].Value); + yield return $"call.i @@NewGMLArray@@(argc={nLines + extraLines})"; + } + else + { + yield return item; + } + + foreach(string element in datas.Where(_ => item.Contains(_.anchor)).SelectMany(_ => _.elements).Reverse()) + { + extraLines++; + yield return "conv.s.v"; + yield return $"push.s \"{element}\""; + } } - yield return line; } + + return func; } - /// - /// Browse a table with an iterator, and at special lines, - /// insert a new line constructed by the dictionaries , and in the gml_GlobalScript_table_consumables table. - /// - /// - /// - public void InjectTable() + static public void InjectTable(string tableName, Func, IEnumerable> CreateInjectionTable) { - List table = Msl.ThrowIfNull(ModLoader.GetTable("gml_GlobalScript_table_items")); - ModLoader.SetTable(EditTable(table).ToList(), "gml_GlobalScript_table_items"); + Msl.LoadAssemblyAsString(tableName) + .Apply(CreateInjectionTable) + .Save(); } } -/// -/// Abstraction for the localization of sentences found in gml_GlobalScript_table_lines. -/// -public class LocalizationSentence +public interface ILocalizationElement { - /// - /// Id of the sentence - /// - public string Id { get; set; } - public string Tags { get; set; } = "any"; - public string Role { get; set; } = "any"; - public string Type { get; set; } = "any"; - public string Faction { get; set; } = "any"; - public string Settlement { get; set; } = "any"; - /// - /// Dictionary that contains a translation of the sentence as displayed in dialog for each available languages. - /// - public Dictionary Sentence { get; set; } = new(); - /// - /// Return an instance of with an empty . - /// - /// For example: - /// - /// LocalizationSentence("mySentenceId"); - /// - /// - /// - /// - public LocalizationSentence(string id) + IEnumerable CreateLine(string? selector); +} +public class LocalizationBaseTable +{ + public List<(string anchor, string? selector)> Anchors = new(); + public LocalizationBaseTable(params (string, string?)[] anchors) { - Id = id; + foreach ((string, string?) anchor in anchors) + { + Anchors.Add(anchor); + } } - /// - /// Return an instance of with filled by an input dictionary. - /// It is expected to have at least an English key. It does not need to follow the convention order of the localization table. - /// - /// For example: - /// - /// LocalizationItem("mySentenceId", - /// new Dictionary < ModLanguage, string > () { {Russian, "sentenceRu"}, {English, "sentenceEn"}, {Italian, "sentenceIt"} }); - /// - /// - /// - /// - /// - public LocalizationSentence(string id, Dictionary sentence) + public static IEnumerable CreateLines(List Locs, string? selector) { - Id = id; - Sentence = Localization.SetDictionary(sentence); - + return Locs.SelectMany(x => x.CreateLine(selector)); } - /// - /// Return an instance of with filled by an input string delimited by semi-colon. - /// It is expected to follow the convention order of the localization table. - /// - /// For example: - /// - /// LocalizationItem("mySentenceId", - /// "sentenceRu;sentenceEn;sentenceCh"); - /// - /// - /// - /// - /// - public LocalizationSentence(string id, string sentence) + public Func, IEnumerable> CreateInjectionTable(List Locs) { - Id = id; - Sentence = Localization.SetDictionary(sentence); - } - /// - /// Create a string delimited by semi-colon that follows the in-game convention order for localization of sentences. - /// - /// For example: - /// - /// LocalizationItem("mySentenceId", "sentenceRu;sentenceEn;sentenceCh").CreateLine(); - /// - /// returns the string "mySentenceId;any;any;any;any;any;sentenceRu;sentenceEn;sentenceCh;sentenceEn;sentenceEn;sentenceEn;sentenceEn;sentenceEn;sentenceEn;sentenceEn;sentenceEn;sentenceEn;". - /// - /// - /// - public string CreateLine() - { - string line = string.Format("{0};{1};{2};{3};{4};{5}", Id, Tags, Role, Type, Faction, Settlement); - foreach (KeyValuePair kp in Sentence) - { - line += ";"; - line += kp.Value; - } - return line + ";"; + return Localization.CreateInjectionTable( + Anchors.Select(x => (x.anchor, CreateLines(Locs, x.selector))).ToArray() + ); } } -/// -/// Abstraction for carrying a list of sentences. -/// -public class LocalizationDialog -{ - /// - /// List of - /// - public List Sentences { get; set; } = new(); - /// - /// Return an instance of with an arbitrary number of . - /// - /// For example: - /// - /// LocalizationDialog( - /// new LocalizationSentence("mySentenceId1"), - /// new LocalizationSentence("mySentenceId2")); - /// - /// - /// - /// - public LocalizationDialog(params LocalizationSentence[] sentences) - { - foreach (LocalizationSentence sentence in sentences) - { - Sentences.Add(sentence); - } - - } - /// - /// Browse a table with an iterator, and at a special line, for each , - /// yield a new line constructed by the dictionary . - /// - /// - /// - private IEnumerable EditTable(IEnumerable table) +public partial class Msl +{ + static public void ExportTable(string tableName, string outputName) { - foreach (string line in table) + DirectoryInfo dir = new (DataLoader.exportPath); + if (!dir.Exists) dir.Create(); + + try { - yield return line; + UndertaleCode code = GetUMTCodeFromFile(tableName); // can fail InvalidOperationException + + string table = code.Disassemble(ModLoader.Data.Variables, ModLoader.Data.CodeLocals.For(code)); + IEnumerable matches = Regex.Matches(table, @"push.s ""(.+)""@\d+").Reverse(); - if (line.Contains("[NPC] GREETINGS;")) + using var stream = File.OpenWrite(Path.Join(dir.FullName, outputName)); + using StreamWriter writer = new(stream); + foreach(System.Text.RegularExpressions.Match match in matches) { - foreach (LocalizationSentence sentence in Sentences) - { - yield return sentence.CreateLine(); - } + writer.WriteLine(match.Groups[1].Value); } } - } - /// - /// Browse a table with an iterator, and at a special line, for each , - /// insert a new line constructed by the dictionary in the gml_GlobalScript_table_lines table. - /// - /// - /// - public void InjectTable() - { - List table = Msl.ThrowIfNull(ModLoader.GetTable("gml_GlobalScript_table_lines")); - ModLoader.SetTable(EditTable(table).ToList(), "gml_GlobalScript_table_lines"); - } -} - -public static partial class Msl -{ - /// - /// Wrapper for the LocalizationItem class using dictionnaries - /// - /// - /// - /// - /// - public static void InjectTableItemLocalization(string oName, Dictionary dictName, Dictionary dictID, Dictionary dictDescription) - { - LocalizationItem localizationItem = new(oName, dictName, dictID, dictDescription); - localizationItem.InjectTable(); - } - /// - /// Wrapper for the LocalizationItem class using strings - /// - /// - /// - /// - /// - public static void InjectTableItemLocalization(string oName, string valuesName, string valuesID, string valuesDescription) - { - LocalizationItem localizationItem = new(oName, valuesName, valuesID, valuesDescription); - localizationItem.InjectTable(); - } - /// - /// Wrapper for the LocalizationDialog class - /// - /// - public static void InjectTableDialogLocalization(params LocalizationSentence[] sentences) - { - LocalizationDialog localizationDialog = new(sentences); - localizationDialog.InjectTable(); + catch (InvalidOperationException) + { + Log.Warning($"{tableName} is not a valid table."); + } } } \ No newline at end of file diff --git a/ModUtils/TableUtils/TableArmor.cs b/ModUtils/TableUtils/StatsTables/Armor.cs similarity index 83% rename from ModUtils/TableUtils/TableArmor.cs rename to ModUtils/TableUtils/StatsTables/Armor.cs index c36ab27..bbd77e7 100644 --- a/ModUtils/TableUtils/TableArmor.cs +++ b/ModUtils/TableUtils/StatsTables/Armor.cs @@ -120,6 +120,7 @@ public static void InjectTableArmor( short? Fortitude = null, short? MP = null, short? MP_Restoration = null, + short? Abilities_Energy_Cost = null, short? Skills_Energy_Cost = null, short? Spells_Energy_Cost = null, short? Magic_Power = null, @@ -180,8 +181,7 @@ public static void InjectTableArmor( byte? fragment_metal02 = null, byte? fragment_metal03 = null, byte? fragment_metal04 = null, - byte? fragment_gold = null, - short? dur_per_frag = null + byte? fragment_gold = null ) { // Table filename @@ -191,7 +191,7 @@ public static void InjectTableArmor( List table = ThrowIfNull(ModLoader.GetTable(tableName)); // Prepare line - string newline = $"{name};{GetEnumMemberValue(Tier)};{id};{Slot};{Class};{rarity};{Mat};{Price};{Markup};{MaxDuration};{DEF};;{PRR};{Block_Power};{Block_Recovery};{EVS};{Crit_Avoid};{FMB};{Hit_Chance};{Weapon_Damage};{Armor_Piercing};{Armor_Damage};{CRT};{CRTD};{CTA};{Damage_Received};{Fortitude};;{MP};{MP_Restoration};{Skills_Energy_Cost};{Spells_Energy_Cost};{Magic_Power};{Miscast_Chance};{Miracle_Chance};{Miracle_Power};{Cooldown_Reduction};;{VSN};{max_hp};{Health_Restoration};{Healing_Received};{Lifesteal};{Manasteal};{Bonus_Range};{Received_XP};{Damage_Returned};;{Bleeding_Resistance};{Knockback_Resistance};{Stun_Resistance};{Pain_Resistance};{Fatigue_Gain};;{Physical_Resistance};{Nature_Resistance};{Magic_Resistance};;{Slashing_Resistance};{Piercing_Resistance};{Blunt_Resistance};{Rending_Resistance};{Fire_Resistance};{Shock_Resistance};{Poison_Resistance};{Caustic_Resistance};{Frost_Resistance};{Arcane_Resistance};{Unholy_Resistance};{Sacred_Resistance};{Psionic_Resistance};;{Pyromantic_Power};{Geomantic_Power};{Venomantic_Power};{Electromantic_Power};{Cryomantic_Power};{Arcanistic_Power};{Astromantic_Power};{Psimantic_Power};;{GetEnumMemberValue(tags)};{(fireproof ? "1" : "")};{(IsOpen ? "1" : "")};{(NoDrop ? "1" : "")};{fragment_cloth01};{fragment_cloth02};{fragment_cloth03};{fragment_cloth04};{fragment_leather01};{fragment_leather02};{fragment_leather03};{fragment_leather04};{fragment_metal01};{fragment_metal02};{fragment_metal03};{fragment_metal04};{fragment_gold};{dur_per_frag};"; + string newline = $"{name};{GetEnumMemberValue(Tier)};{id};{Slot};{Class};{rarity};{Mat};{Price};{Markup};{MaxDuration};{DEF};;{PRR};{Block_Power};{Block_Recovery};{EVS};{Crit_Avoid};{FMB};{Hit_Chance};{Weapon_Damage};{Armor_Piercing};{Armor_Damage};{CRT};{CRTD};{CTA};{Damage_Received};{Fortitude};;{MP};{MP_Restoration};{Abilities_Energy_Cost};{Skills_Energy_Cost};{Spells_Energy_Cost};{Magic_Power};{Miscast_Chance};{Miracle_Chance};{Miracle_Power};{Cooldown_Reduction};;{VSN};{max_hp};{Health_Restoration};{Healing_Received};{Lifesteal};{Manasteal};{Bonus_Range};{Received_XP};{Damage_Returned};;{Bleeding_Resistance};{Knockback_Resistance};{Stun_Resistance};{Pain_Resistance};{Fatigue_Gain};;{Physical_Resistance};{Nature_Resistance};{Magic_Resistance};;{Slashing_Resistance};{Piercing_Resistance};{Blunt_Resistance};{Rending_Resistance};{Fire_Resistance};{Shock_Resistance};{Poison_Resistance};{Caustic_Resistance};{Frost_Resistance};{Arcane_Resistance};{Unholy_Resistance};{Sacred_Resistance};{Psionic_Resistance};;{Pyromantic_Power};{Geomantic_Power};{Venomantic_Power};{Electromantic_Power};{Cryomantic_Power};{Arcanistic_Power};{Astromantic_Power};{Psimantic_Power};;{GetEnumMemberValue(tags)};{(fireproof ? "1" : "")};{(IsOpen ? "1" : "")};{(NoDrop ? "1" : "")};{fragment_cloth01};{fragment_cloth02};{fragment_cloth03};{fragment_cloth04};{fragment_leather01};{fragment_leather02};{fragment_leather03};{fragment_leather04};{fragment_metal01};{fragment_metal02};{fragment_metal03};{fragment_metal04};{fragment_gold};"; // Find Meta Category in table string hookStr = ""; diff --git a/ModUtils/TableUtils/ContractsStats.cs b/ModUtils/TableUtils/StatsTables/ContractsStats.cs similarity index 100% rename from ModUtils/TableUtils/ContractsStats.cs rename to ModUtils/TableUtils/StatsTables/ContractsStats.cs diff --git a/ModUtils/TableUtils/Drops.cs b/ModUtils/TableUtils/StatsTables/Drops.cs similarity index 100% rename from ModUtils/TableUtils/Drops.cs rename to ModUtils/TableUtils/StatsTables/Drops.cs diff --git a/ModUtils/TableUtils/DungeonsSpawn.cs b/ModUtils/TableUtils/StatsTables/DungeonsSpawn.cs similarity index 100% rename from ModUtils/TableUtils/DungeonsSpawn.cs rename to ModUtils/TableUtils/StatsTables/DungeonsSpawn.cs diff --git a/ModUtils/TableUtils/ItemStats.cs b/ModUtils/TableUtils/StatsTables/ItemStats.cs similarity index 83% rename from ModUtils/TableUtils/ItemStats.cs rename to ModUtils/TableUtils/StatsTables/ItemStats.cs index 95d0027..d14f920 100644 --- a/ModUtils/TableUtils/ItemStats.cs +++ b/ModUtils/TableUtils/StatsTables/ItemStats.cs @@ -177,6 +177,7 @@ public static void InjectTableItemStats( ushort? Fresh = null, ushort? Duration = null, ushort? Stacks = null, + bool Diet = false, short? Hunger = null, float? Hunger_Change = null, short? Hunger_Resistance = null, @@ -191,13 +192,16 @@ public static void InjectTableItemStats( float? Pain_Change = null, short? Pain_Resistance = null, // Could be ushort ? short? Pain_Limit = null, - short? Morale = null, + short? MoraleSituational = null, float? Morale_Change = null, - short? Sanity = null, + short? MoraleTemporary = null, + float? MoraleDiet = null, + short? SanitySituational = null, float? Sanity_Change = null, short? Condition = null, short? max_hp = null, // Could be ushort ? short? max_hp_res = null, // Could be ushort ? + short? HP_Turn = null, short? Health_Restoration = null, // Could be float ? short? Healing_Received = null, short? max_mp = null, // Could be ushort ? @@ -210,6 +214,7 @@ public static void InjectTableItemStats( short? Received_XP = null, short? Cooldown_Reduction = null, short? Weapon_Damage = null, + short? Magic_Power = null, short? Hit_Chance = null, // type unknown, assuming short short? FMB = null, short? CRTD = null, // Could be ushort ? @@ -234,7 +239,6 @@ public static void InjectTableItemStats( short? Unholy_Resistance = null, short? Sacred_Resistance = null, short? Psionic_Resistance = null, - short? Bleeding_Resistance_2 = null, short? Nausea_Chance = null, // Could be ushort ? short? Poisoning_Chance = null, // Could be ushort ? short? Poisoning_Duration = null, @@ -255,7 +259,7 @@ public static void InjectTableItemStats( List table = ThrowIfNull(ModLoader.GetTable(tableName)); // Prepare line - string newline = $"{id};;{Price};{EffPrice};{GetEnumMemberValue(tier)};{GetEnumMemberValue(Cat)};{GetEnumMemberValue(Subcat)};{Material};{GetEnumMemberValue(Weight)};;{Fresh};{Duration};{Stacks};;{Hunger};{Hunger_Change};{Hunger_Resistance};;{Thirsty};{Thirst_Change};;{Immunity};{Immunity_Change};;{Intoxication};{Toxicity_Change};{Toxicity_Resistance};;{Pain};{Pain_Change};{Pain_Resistance};{Pain_Limit};;{Morale};{Morale_Change};{Sanity};{Sanity_Change};;{Condition};{max_hp};{max_hp_res};{Health_Restoration};{Healing_Received};;{max_mp};{max_mp_res};{MP_Restoration};{MP_turn};;{Fatigue};{Fatigue_Change};{Fatigue_Gain};;{Received_XP};{Cooldown_Reduction};{Weapon_Damage};{Hit_Chance};{FMB};{CRTD};{Fortitude};{VSN};;{Bleeding_Resistance};{Knockback_Resistance};{Stun_Resistance};;{Physical_Resistance};{Nature_Resistance};{Magic_Resistance};{Slashing_Resistance};{Piercing_Resistance};{Blunt_Resistance};{Rending_Resistance};{Fire_Resistance};{Shock_Resistance};{Poison_Resistance};{Caustic_Resistance};{Frost_Resistance};{Arcane_Resistance};{Unholy_Resistance};{Sacred_Resistance};{Psionic_Resistance};{Bleeding_Resistance_2};;{Nausea_Chance};{Poisoning_Chance};{Poisoning_Duration};;{(purse ? "1" : "")};{(bottle ? "1" : "")};{upgrade};{fodder};{stack};{(fireproof ? "1" : "")};{(dropsOnce ? "1" : "")};{GetEnumMemberValue(tags)};"; + string newline = $"{id};;{Price};{EffPrice};{GetEnumMemberValue(tier)};{GetEnumMemberValue(Cat)};{GetEnumMemberValue(Subcat)};{Material};{GetEnumMemberValue(Weight)};;{Fresh};{Duration};{Stacks};{(Diet ? "1" : "")};;{Hunger};{Hunger_Change};{Hunger_Resistance};;{Thirsty};{Thirst_Change};;{Immunity};{Immunity_Change};;{Intoxication};{Toxicity_Change};{Toxicity_Resistance};;{Pain};{Pain_Change};{Pain_Resistance};{Pain_Limit};;{MoraleSituational};{Morale_Change};{MoraleTemporary};{MoraleDiet};{SanitySituational};{Sanity_Change};;{Condition};{max_hp};{max_hp_res};{HP_Turn};{Health_Restoration};{Healing_Received};;{max_mp};{max_mp_res};{MP_Restoration};{MP_turn};;{Fatigue};{Fatigue_Change};{Fatigue_Gain};;{Received_XP};{Cooldown_Reduction};{Weapon_Damage};{Magic_Power};{Hit_Chance};{FMB};{CRTD};{Fortitude};{VSN};;{Bleeding_Resistance};{Knockback_Resistance};{Stun_Resistance};;{Physical_Resistance};{Nature_Resistance};{Magic_Resistance};{Slashing_Resistance};{Piercing_Resistance};{Blunt_Resistance};{Rending_Resistance};{Fire_Resistance};{Shock_Resistance};{Poison_Resistance};{Caustic_Resistance};{Frost_Resistance};{Arcane_Resistance};{Unholy_Resistance};{Sacred_Resistance};{Psionic_Resistance};;{Nausea_Chance};{Poisoning_Chance};{Poisoning_Duration};;{(purse ? "1" : "")};{(bottle ? "1" : "")};{upgrade};{fodder};{stack};{(fireproof ? "1" : "")};{(dropsOnce ? "1" : "")};{GetEnumMemberValue(tags)};"; // Add line to table table.Add(newline); diff --git a/ModUtils/TableUtils/MobsStats.cs b/ModUtils/TableUtils/StatsTables/MobsStats.cs similarity index 78% rename from ModUtils/TableUtils/MobsStats.cs rename to ModUtils/TableUtils/StatsTables/MobsStats.cs index 725a6be..e39f2f9 100644 --- a/ModUtils/TableUtils/MobsStats.cs +++ b/ModUtils/TableUtils/StatsTables/MobsStats.cs @@ -176,6 +176,10 @@ public static void InjectTableMobsStats( short? Knockback_Resistance = null, short? Stun_Resistance = null, short? Pain_Resistance = null, + short? Bleeding_Immunity = null, + short? Move_Immunity = null, + short? Control_Immunity = null, + short? Effect_Immunity = null, short? Bleeding_Chance = null, short? Daze_Chance = null, short? Stun_Chance = null, @@ -232,11 +236,20 @@ public static void InjectTableMobsStats( short? Unholy_Resistance = null, short? Sacred_Resistance = null, short? Psionic_Resistance = null, + short? Pyromantic_Power = null, + short? Geomantic_Power = null, + short? Venomantic_Power = null, + short? Electromantic_Power = null, + short? Cryomantic_Power = null, + short? Arcanistic_Power = null, + short? Astromantic_Power = null, + short? Psimantic_Power = null, bool canBlock = false, bool canDisarm = false, bool canSwim = false, byte Swimming_Cost = 1, - byte? achievement = null + byte? achievement = null, + string? trophy = null ) { // Table filename @@ -246,7 +259,7 @@ public static void InjectTableMobsStats( List table = ThrowIfNull(ModLoader.GetTable(tableName)); // Prepare line - string newline = $"{name};{GetEnumMemberValue(tier)};{ID};{type};{faction};{pattern};;{GetEnumMemberValue(category1)};{GetEnumMemberValue(category2)};{GetEnumMemberValue(weapon)};{armor};{size};{matter};{VIS};;{XP};{HP};{MP};{Head_DEF};{Body_DEF};{Arms_DEF};{Legs_DEF};;{Hit_Chance};{EVS};{PRR};{Block_Power};{Block_Recovery};{Crit_Avoid};{CRT};{CRTD};{CTA};{FMB};;{Magic_Power};{Miscast_Chance};{Miracle_Chance};{Miracle_Power};;{MP_Restoration};{Cooldown_Reduction};{Fortitude};{Health_Restoration};{Healing_Received};{Lifesteal};{Manasteal};;{Bleeding_Resistance};{Knockback_Resistance};{Stun_Resistance};{Pain_Resistance};;{Bleeding_Chance};{Daze_Chance};{Stun_Chance};{Knockback_Chance};{Immob_Chance};{Stagger_Chance};;{STRk};{AGLk};{Vitalityk};{PRCk};{WILk};{Checksum};;{STR};{AGL};{Vitality};{PRC};{WIL};;{Bonus_Range};{Avoiding_Chance};{Damage_Returned};{Damage_Received};;{Head};{Torso};{Left_Leg};{Right_Leg};{Left_Hand};{Right_Hand};;{IP};{Morale};{Threat_Time};;{Bodypart_Damage};{Armor_Piercing};{DMG_Sum};{Slashing_Damage};{Piercing_Damage};{Blunt_Damage};{Rending_Damage};{Fire_Damage};{Shock_Damage};{Poison_Damage};{Caustic_Damage};{Frost_Damage};{Arcane_Damage};{Unholy_Damage};{Sacred_Damage};{Psionic_Damage};;{Physical_Resistance};{Natural_Resistance};{Magical_Resistance};;{Slashing_Resistance};{Piercing_Resistance};{Blunt_Resistance};{Rending_Resistance};{Fire_Resistance};{Shock_Resistance};{Poison_Resistance};{Frost_Resistance};{Caustic_Resistance};{Arcane_Resistance};{Unholy_Resistance};{Sacred_Resistance};{Psionic_Resistance};;{(canBlock ? "1": "")};{(canDisarm ? "1": "")};{(canSwim ? "1": "")};{Swimming_Cost};{achievement};"; + string newline = $"{name};{GetEnumMemberValue(tier)};{ID};{type};{faction};{pattern};;{GetEnumMemberValue(category1)};{GetEnumMemberValue(category2)};{GetEnumMemberValue(weapon)};{armor};{size};{matter};{VIS};;{XP};{HP};{MP};{Head_DEF};{Body_DEF};{Arms_DEF};{Legs_DEF};;{Hit_Chance};{EVS};{PRR};{Block_Power};{Block_Recovery};{Crit_Avoid};{CRT};{CRTD};{CTA};{FMB};;{Magic_Power};{Miscast_Chance};{Miracle_Chance};{Miracle_Power};;{MP_Restoration};{Cooldown_Reduction};{Fortitude};{Health_Restoration};{Healing_Received};{Lifesteal};{Manasteal};;{Bleeding_Resistance};{Knockback_Resistance};{Stun_Resistance};{Pain_Resistance};{Bleeding_Immunity};{Move_Immunity};{Control_Immunity};{Effect_Immunity};;{Bleeding_Chance};{Daze_Chance};{Stun_Chance};{Knockback_Chance};{Immob_Chance};{Stagger_Chance};;{STRk};{AGLk};{Vitalityk};{PRCk};{WILk};{Checksum};;{STR};{AGL};{Vitality};{PRC};{WIL};;{Bonus_Range};{Avoiding_Chance};{Damage_Returned};{Damage_Received};;{Head};{Torso};{Left_Leg};{Right_Leg};{Left_Hand};{Right_Hand};;{IP};{Morale};{Threat_Time};;{Bodypart_Damage};{Armor_Piercing};{DMG_Sum};{Slashing_Damage};{Piercing_Damage};{Blunt_Damage};{Rending_Damage};{Fire_Damage};{Shock_Damage};{Poison_Damage};{Caustic_Damage};{Frost_Damage};{Arcane_Damage};{Unholy_Damage};{Sacred_Damage};{Psionic_Damage};;{Physical_Resistance};{Natural_Resistance};{Magical_Resistance};;{Slashing_Resistance};{Piercing_Resistance};{Blunt_Resistance};{Rending_Resistance};{Fire_Resistance};{Shock_Resistance};{Poison_Resistance};{Frost_Resistance};{Caustic_Resistance};{Arcane_Resistance};{Unholy_Resistance};{Sacred_Resistance};{Psionic_Resistance};;{Pyromantic_Power};{Geomantic_Power};{Venomantic_Power};{Electromantic_Power};{Cryomantic_Power};{Arcanistic_Power};{Astromantic_Power};{Psimantic_Power};;{(canBlock ? "1": "")};{(canDisarm ? "1": "")};{noTP};{(canSwim ? "1": "")};{Swimming_Cost};{achievement};{trophy};"; // Add line to table table.Add(newline); diff --git a/ModUtils/TableUtils/PotionsStats.cs b/ModUtils/TableUtils/StatsTables/PotionsStats.cs similarity index 100% rename from ModUtils/TableUtils/PotionsStats.cs rename to ModUtils/TableUtils/StatsTables/PotionsStats.cs diff --git a/ModUtils/TableUtils/RecipesCook.cs b/ModUtils/TableUtils/StatsTables/RecipesCook.cs similarity index 100% rename from ModUtils/TableUtils/RecipesCook.cs rename to ModUtils/TableUtils/StatsTables/RecipesCook.cs diff --git a/ModUtils/TableUtils/RecipesCraft.cs b/ModUtils/TableUtils/StatsTables/RecipesCraft.cs similarity index 100% rename from ModUtils/TableUtils/RecipesCraft.cs rename to ModUtils/TableUtils/StatsTables/RecipesCraft.cs diff --git a/ModUtils/TableUtils/SkillsStats.cs b/ModUtils/TableUtils/StatsTables/SkillsStats.cs similarity index 75% rename from ModUtils/TableUtils/SkillsStats.cs rename to ModUtils/TableUtils/StatsTables/SkillsStats.cs index c64e83f..5d1a96b 100644 --- a/ModUtils/TableUtils/SkillsStats.cs +++ b/ModUtils/TableUtils/StatsTables/SkillsStats.cs @@ -129,34 +129,34 @@ public enum SkillsStatsMetacategory } public static void InjectTableSkillsStats( - SkillsStatsHook hook, - string id, - string? Object = null, - SkillsStatsTarget Target = SkillsStatsTarget.NoTarget, - string Range = "0", - ushort KD = 0, - ushort MP = 0, - ushort Reserv = 0, - ushort Duration = 0, - byte AOE_Lenght = 0, - byte AOE_Width = 0, - bool is_movement = false, - SkillsStatsPattern Pattern = SkillsStatsPattern.normal, - SkillsStatsValidator Validators = SkillsStatsValidator.none, - SkillsStatsClass Class = SkillsStatsClass.skill, - bool Bonus_Range = false, // could be byte ? Not sure as only values are 0 and 1 - string? Starcast = null, - SkillsStatsBranch Branch = SkillsStatsBranch.none, - bool is_knockback = false, - bool Crime = false, - SkillsStatsMetacategory metacategory = SkillsStatsMetacategory.none, - short FMB = 0, - string AP = "x", - bool Attack = false, - bool Stance = false, - bool Charge = false, - bool Maneuver = false, - bool Spell = false + SkillsStatsHook hook, + string id, + string? Object = null, + SkillsStatsTarget Target = SkillsStatsTarget.NoTarget, + string Range = "0", + ushort KD = 0, + ushort MP = 0, + ushort Reserv = 0, + ushort Duration = 0, + byte AOE_Lenght = 0, + byte AOE_Width = 0, + bool is_movement = false, + SkillsStatsPattern Pattern = SkillsStatsPattern.normal, + SkillsStatsValidator Validators = SkillsStatsValidator.none, + SkillsStatsClass Class = SkillsStatsClass.skill, + bool Bonus_Range = false, // could be byte ? Not sure as only values are 0 and 1 + string? Starcast = null, + string Branch = "none", + bool is_knockback = false, + bool Crime = false, + SkillsStatsMetacategory metacategory = SkillsStatsMetacategory.none, + short FMB = 0, + string AP = "x", + bool Attack = false, + bool Stance = false, + bool Charge = false, + bool Maneuver = false, + bool Spell = false ) { // Table filename @@ -166,7 +166,7 @@ public static void InjectTableSkillsStats( List table = ThrowIfNull(ModLoader.GetTable(tableName)); // Prepare line - string newline = $"{id};{Object};{GetEnumMemberValue(Target)};{Range};{KD};{MP};{Reserv};{Duration};{AOE_Lenght};{AOE_Width};{(is_movement ? "1" : "0")};{Pattern};{GetEnumMemberValue(Validators)};{Class};{(Bonus_Range ? "1" : "0")};{Starcast};{GetEnumMemberValue(Branch)};{(is_knockback ? "1" : "0")};{(Crime ? "1" : "")};{GetEnumMemberValue(metacategory)};{FMB};{AP};{(Attack ? "1" : "")};{(Stance ? "1" : "")};{(Charge ? "1" : "")};{(Maneuver ? "1" : "")};{(Spell ? "1" : "")};"; + string newline = $"{id};{Object};{GetEnumMemberValue(Target)};{Range};{KD};{MP};{Reserv};{Duration};{AOE_Lenght};{AOE_Width};{(is_movement ? "1" : "0")};{Pattern};{GetEnumMemberValue(Validators)};{Class};{(Bonus_Range ? "1" : "0")};{Starcast};{Branch};{(is_knockback ? "1" : "0")};{(Crime ? "1" : "")};{GetEnumMemberValue(metacategory)};{FMB};{AP};{(Attack ? "1" : "")};{(Stance ? "1" : "")};{(Charge ? "1" : "")};{(Maneuver ? "1" : "")};{(Spell ? "1" : "")};"; // Find Hook string hookStr = "// " + GetEnumMemberValue(hook); diff --git a/ModUtils/TableUtils/SurfaceSpawn.cs b/ModUtils/TableUtils/StatsTables/SurfaceSpawn.cs similarity index 100% rename from ModUtils/TableUtils/SurfaceSpawn.cs rename to ModUtils/TableUtils/StatsTables/SurfaceSpawn.cs diff --git a/ModUtils/TableUtils/TableWeapons.cs b/ModUtils/TableUtils/StatsTables/Weapons.cs similarity index 90% rename from ModUtils/TableUtils/TableWeapons.cs rename to ModUtils/TableUtils/StatsTables/Weapons.cs index 17eeba7..b2fa461 100644 --- a/ModUtils/TableUtils/TableWeapons.cs +++ b/ModUtils/TableUtils/StatsTables/Weapons.cs @@ -151,6 +151,7 @@ public static void InjectTableWeapons( short? MP = null, short? MP_Restoration = null, short? Cooldown_Reduction = null, + short? Abilities_Energy_Cost = null, short? Skills_Energy_Cost = null, short? Spells_Energy_Cost = null, short? Magic_Power = null, @@ -187,7 +188,7 @@ public static void InjectTableWeapons( List table = ThrowIfNull(ModLoader.GetTable(tableName)); // Prepare line - string newline = $"{name};{GetEnumMemberValue(Tier)};{id};{GetEnumMemberValue(Slot)};{rarity};{Mat};{Price};{Markup};{MaxDuration};{Rng};;{Armor_Piercing};{Armor_Damage};{Bodypart_Damage};;{Slashing_Damage};{Piercing_Damage};{Blunt_Damage};{Rending_Damage};{Fire_Damage};{Shock_Damage};{Poison_Damage};{Caustic_Damage};{Frost_Damage};{Arcane_Damage};{Unholy_Damage};{Sacred_Damage};{Psionic_Damage};;{FMB};{Hit_Chance};{CRT};{CRTD};{CTA};{PRR};{Block_Power};{Block_Recovery};;{Bleeding_Chance};{Daze_Chance};{Stun_Chance};{Knockback_Chance};{Immob_Chance};{Stagger_Chance};;{MP};{MP_Restoration};{Cooldown_Reduction};{Skills_Energy_Cost};{Spells_Energy_Cost};{Magic_Power};{Miscast_Chance};{Miracle_Chance};{Miracle_Power};{Bonus_Range};;{max_hp};{Health_Restoration};{Healing_Received};{Crit_Avoid};{Fatigue_Gain};{Lifesteal};{Manasteal};{Damage_Received};;{Pyromantic_Power};{Geomantic_Power};{Venomantic_Power};{Electroantic_Power};{Cryomantic_Power};{Arcanistic_Power};{Astromantic_Power};{Psimantic_Power};;{Balance};{GetEnumMemberValue(tags)};{upgrade};{(fireproof ? 1 : "")};{(NoDrop ? 1 : "")};"; + string newline = $"{name};{GetEnumMemberValue(Tier)};{id};{GetEnumMemberValue(Slot)};{rarity};{Mat};{Price};{Markup};{MaxDuration};{Rng};;{Armor_Piercing};{Armor_Damage};{Bodypart_Damage};;{Slashing_Damage};{Piercing_Damage};{Blunt_Damage};{Rending_Damage};{Fire_Damage};{Shock_Damage};{Poison_Damage};{Caustic_Damage};{Frost_Damage};{Arcane_Damage};{Unholy_Damage};{Sacred_Damage};{Psionic_Damage};;{FMB};{Hit_Chance};{CRT};{CRTD};{CTA};{PRR};{Block_Power};{Block_Recovery};;{Bleeding_Chance};{Daze_Chance};{Stun_Chance};{Knockback_Chance};{Immob_Chance};{Stagger_Chance};;{MP};{MP_Restoration};{Cooldown_Reduction};{Abilities_Energy_Cost};{Skills_Energy_Cost};{Spells_Energy_Cost};{Magic_Power};{Miscast_Chance};{Miracle_Chance};{Miracle_Power};{Bonus_Range};;{max_hp};{Health_Restoration};{Healing_Received};{Crit_Avoid};{Fatigue_Gain};{Lifesteal};{Manasteal};{Damage_Received};;{Pyromantic_Power};{Geomantic_Power};{Venomantic_Power};{Electroantic_Power};{Cryomantic_Power};{Arcanistic_Power};{Astromantic_Power};{Psimantic_Power};;{Balance};{GetEnumMemberValue(tags)};{upgrade};{(fireproof ? 1 : "")};{(NoDrop ? 1 : "")};"; // Add line to table table.Add(newline); diff --git a/ModUtils/TableUtils/TableUtils.cs b/ModUtils/TableUtils/TableUtils.cs index 06059e9..e365cf6 100644 --- a/ModUtils/TableUtils/TableUtils.cs +++ b/ModUtils/TableUtils/TableUtils.cs @@ -17,9 +17,5 @@ public partial class Msl .GetCustomAttribute(false)? .Value ?? value.ToString(); } - - // Tables left to do : - // - ai - // - supply / demand if necessary ? } } diff --git a/Reference/README.md b/Reference/README.md deleted file mode 100644 index 8b13789..0000000 --- a/Reference/README.md +++ /dev/null @@ -1 +0,0 @@ -