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