diff --git a/Readme.md b/Readme.md index 24d3f857..c568a368 100644 --- a/Readme.md +++ b/Readme.md @@ -5,8 +5,7 @@ Check the [Wiki](https://github.com/bcssov/IronyModManager/wiki) All instructions are for Windows. 1. Install Visual Studio 2022 (required for .NET6) 1. Clone the repo to your local machine -1. Open the LocalizationResourceGenerator solution file located in \Tools\LocalizationResourceGenerator\src - * Build the solution and copy the binaries to the Tools\LocalizationResourceGenerator folder +1. Run cmd\build-tools.bat to build the LocalizationResourceGenerator 1. If you don't already have one, create a folder for local NuGet packages and unzip the [CWTools.Irony-Private.0.4.0-alpha8](https://github.com/bcssov/IronyModManager/files/7798143/CWTools.Irony-Private.0.4.0-alpha8.zip) package to it * This is just an up to date version of CWTools, the one on the public NuGet is older * Example path: C:\Users\username\code\LocalNuGet diff --git a/References/CopyAll/Maps/HeartsofIronIVParserMap.json b/References/CopyAll/Maps/HeartsofIronIVParserMap.json index e0682218..eebbd5e6 100644 --- a/References/CopyAll/Maps/HeartsofIronIVParserMap.json +++ b/References/CopyAll/Maps/HeartsofIronIVParserMap.json @@ -325,9 +325,6 @@ }, { "DirectoryPath": "history\\units", "PreferredParser": "HOI4WholeTextParser" - }, { - "DirectoryPath": "interface", - "PreferredParser": "GenericGraphicsParser" }, { "DirectoryPath": "integrated_dlc\\dlc018_together_for_victory\\gfx\\entities", "PreferredParser": "GenericGraphicsParser" @@ -363,9 +360,9 @@ "PreferredParser": "DefaultParser" }, { "DirectoryPath": "interface", - "PreferredParser": "DefaultParser" + "PreferredParser": "GenericGraphicsParser" }, { - "DirectoryPath": "launcher-assets", + "DirectoryPath": "interface", "PreferredParser": "DefaultParser" }, { "DirectoryPath": "localisation", diff --git a/References/CopyAll/Maps/StellarisParserMap.json b/References/CopyAll/Maps/StellarisParserMap.json index bdd113b0..3b8e340c 100644 --- a/References/CopyAll/Maps/StellarisParserMap.json +++ b/References/CopyAll/Maps/StellarisParserMap.json @@ -271,6 +271,24 @@ }, { "DirectoryPath": "common\\inline_scripts\\events", "PreferredParser": "StellarisWholeTextParser" + }, { + "DirectoryPath": "common\\inline_scripts\\grand_archive", + "PreferredParser": "StellarisWholeTextParser" + }, { + "DirectoryPath": "common\\inline_scripts\\grand_archive\\collection", + "PreferredParser": "StellarisWholeTextParser" + }, { + "DirectoryPath": "common\\inline_scripts\\grand_archive\\mutations", + "PreferredParser": "StellarisWholeTextParser" + }, { + "DirectoryPath": "common\\inline_scripts\\grand_archive\\mutations\\camouflage", + "PreferredParser": "StellarisWholeTextParser" + }, { + "DirectoryPath": "common\\inline_scripts\\grand_archive\\mutations\\core_components", + "PreferredParser": "StellarisWholeTextParser" + }, { + "DirectoryPath": "common\\inline_scripts\\grand_archive\\specimen_upkeeps", + "PreferredParser": "StellarisWholeTextParser" }, { "DirectoryPath": "common\\inline_scripts\\jobs", "PreferredParser": "StellarisWholeTextParser" @@ -292,6 +310,9 @@ }, { "DirectoryPath": "common\\inline_scripts\\ship_components\\weights", "PreferredParser": "StellarisWholeTextParser" + }, { + "DirectoryPath": "common\\inline_scripts\\starbase_modules", + "PreferredParser": "StellarisWholeTextParser" }, { "DirectoryPath": "common\\inline_scripts\\technologies", "PreferredParser": "StellarisWholeTextParser" @@ -337,6 +358,9 @@ }, { "DirectoryPath": "common\\missions", "PreferredParser": "DefaultParser" + }, { + "DirectoryPath": "common\\mutations", + "PreferredParser": "DefaultParser" }, { "DirectoryPath": "common\\named_colors", "PreferredParser": "DefaultParser" @@ -433,6 +457,9 @@ }, { "DirectoryPath": "common\\ship_behaviors", "PreferredParser": "GenericKeyParser" + }, { + "DirectoryPath": "common\\ship_categories", + "PreferredParser": "DefaultParser" }, { "DirectoryPath": "common\\ship_sizes", "PreferredParser": "StellarisOverwrittenObjectSingleFileParser" @@ -484,6 +511,9 @@ }, { "DirectoryPath": "common\\species_rights\\slavery_types", "PreferredParser": "StellarisOverwrittenParser" + }, { + "DirectoryPath": "common\\specimens", + "PreferredParser": "DefaultParser" }, { "DirectoryPath": "common\\starbase_buildings", "PreferredParser": "DefaultParser" @@ -649,6 +679,9 @@ }, { "DirectoryPath": "gfx\\shipview", "PreferredParser": "DefaultParser" + }, { + "DirectoryPath": "gfx\\vivariumview", + "PreferredParser": "DefaultParser" }, { "DirectoryPath": "gfx\\worldgfx", "PreferredParser": "GenericKeyParser" @@ -808,6 +841,21 @@ }, { "DirectoryPath": "sound\\firstcontact\\ui", "PreferredParser": "GenericWholeTextParser" + }, { + "DirectoryPath": "sound\\grand_archive\\events", + "PreferredParser": "GenericWholeTextParser" + }, { + "DirectoryPath": "sound\\grand_archive\\relics", + "PreferredParser": "GenericWholeTextParser" + }, { + "DirectoryPath": "sound\\grand_archive\\ships", + "PreferredParser": "GenericWholeTextParser" + }, { + "DirectoryPath": "sound\\grand_archive\\ui", + "PreferredParser": "GenericWholeTextParser" + }, { + "DirectoryPath": "sound\\grand_archive\\weapons", + "PreferredParser": "GenericWholeTextParser" }, { "DirectoryPath": "sound\\guardians", "PreferredParser": "GenericWholeTextParser" diff --git a/src/IronyModManager.DI/MessageBus/MessageBusDependencyResolver.cs b/src/IronyModManager.DI/MessageBus/MessageBusDependencyResolver.cs index 3e36d566..5f5cf01a 100644 --- a/src/IronyModManager.DI/MessageBus/MessageBusDependencyResolver.cs +++ b/src/IronyModManager.DI/MessageBus/MessageBusDependencyResolver.cs @@ -1,26 +1,26 @@ - -// *********************************************************************** +// *********************************************************************** // Assembly : IronyModManager.DI // Author : Mario // Created : 06-10-2020 // // Last Modified By : Mario -// Last Modified On : 06-26-2023 +// Last Modified On : 10-29-2024 // *********************************************************************** // // Mario // // // *********************************************************************** + using System; using System.Collections.Generic; +using System.Linq; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using SlimMessageBus.Host; namespace IronyModManager.DI.MessageBus { - /// /// Class MessageBusDependencyResolver. /// Implements the @@ -34,6 +34,7 @@ internal class MessageBusDependencyResolver : IServiceProvider /// The message type resolver /// private readonly MessageTypeResolver messageTypeResolver; + /// /// The resolved collections /// @@ -75,6 +76,11 @@ public object GetService(Type serviceType) { return messageTypeResolver; } + else if (typeof(System.Collections.IEnumerable).IsAssignableFrom(serviceType) && typeof(SlimMessageBus.Host.Interceptor.IMessageBusLifecycleInterceptor).IsAssignableFrom(serviceType.GetGenericArguments()[0])) + { + return null; + } + if (DIContainer.Container.IsLocked) { var obj = DIResolver.Get(serviceType); @@ -82,11 +88,7 @@ public object GetService(Type serviceType) } else { - if (resolvedCollections.TryGetValue(serviceType, out var instance)) - { - return instance(); - } - return null; + return resolvedCollections.TryGetValue(serviceType, out var instance) ? instance() : null; } } diff --git a/src/IronyModManager.Parser.Common/Parsers/BaseParser.cs b/src/IronyModManager.Parser.Common/Parsers/BaseParser.cs index b0e9b397..890279a8 100644 --- a/src/IronyModManager.Parser.Common/Parsers/BaseParser.cs +++ b/src/IronyModManager.Parser.Common/Parsers/BaseParser.cs @@ -4,7 +4,7 @@ // Created : 02-17-2020 // // Last Modified By : Mario -// Last Modified On : 10-17-2024 +// Last Modified On : 10-29-2024 // *********************************************************************** // // Mario @@ -309,6 +309,15 @@ protected virtual IEnumerable ParseComplexTypes(IEnumerable // Mario // // // *********************************************************************** + using System; using System.Collections.Generic; using System.Linq; using System.Text; -using System.Threading.Tasks; using FluentAssertions; using IronyModManager.DI; using IronyModManager.Parser.Common; @@ -39,7 +39,7 @@ public void GetObjectId_should_yield_results() { DISetup.SetupContainer(); - var sb = new System.Text.StringBuilder(3175); + var sb = new StringBuilder(3175); sb.AppendLine(@"building_giga_megaworkshop_hub_acot_$tier$ = {"); sb.AppendLine(@" base_buildtime = @giga_amb_hub_time_$tier$"); sb.AppendLine(@" category = manufacturing"); @@ -191,7 +191,7 @@ public void GetObjectId_should_yield_results() sb.AppendLine(@"}"); - var sb2 = new System.Text.StringBuilder(257); + var sb2 = new StringBuilder(257); sb2.AppendLine(@"inline_script = {"); sb2.AppendLine(@" script = ""buildings/building_giga_megaworkshop_acot"""); sb2.AppendLine(@" tier = ""delta"""); @@ -207,13 +207,7 @@ public void GetObjectId_should_yield_results() var result = parser.Process(sb.ToString(), sb2.ToString()); result.Should().NotBeNullOrEmpty(); var m = DIResolver.Get(); - var parserResult = m.Parse(new ParserManagerArgs() - { - File = "common\\buildings\\dummy.txt", - GameType = "Stellaris", - IsBinary = false, - Lines = result.SplitOnNewLine() - }); + var parserResult = m.Parse(new ParserManagerArgs { File = "common\\buildings\\dummy.txt", GameType = "Stellaris", IsBinary = false, Lines = result.SplitOnNewLine() }); parserResult.Count().Should().Be(1); parserResult.FirstOrDefault().Id.Should().Be("building_giga_megaworkshop_hub_acot_delta"); } @@ -226,7 +220,7 @@ public void GetObjectId_should_yield_results_without_using_parameters() { DISetup.SetupContainer(); - var sb = new System.Text.StringBuilder(3175); + var sb = new StringBuilder(3175); sb.AppendLine(@"building_giga_megaworkshop_hub_acot_no_parameters = {"); sb.AppendLine(@" base_buildtime = @giga_amb_hub_time_$tier$"); sb.AppendLine(@" category = manufacturing"); @@ -378,7 +372,7 @@ public void GetObjectId_should_yield_results_without_using_parameters() sb.AppendLine(@"}"); - var sb2 = new System.Text.StringBuilder(257); + var sb2 = new StringBuilder(257); sb2.AppendLine(@"inline_script = {"); sb2.AppendLine(@" script = ""buildings/building_giga_megaworkshop_acot"""); sb2.AppendLine(@" tier = ""delta"""); @@ -394,13 +388,7 @@ public void GetObjectId_should_yield_results_without_using_parameters() var result = parser.Process(sb.ToString(), sb2.ToString()); result.Should().NotBeNullOrEmpty(); var m = DIResolver.Get(); - var parserResult = m.Parse(new ParserManagerArgs() - { - File = "common\\buildings\\dummy.txt", - GameType = "Stellaris", - IsBinary = false, - Lines = result.SplitOnNewLine() - }); + var parserResult = m.Parse(new ParserManagerArgs { File = "common\\buildings\\dummy.txt", GameType = "Stellaris", IsBinary = false, Lines = result.SplitOnNewLine() }); parserResult.Count().Should().Be(1); parserResult.FirstOrDefault().Id.Should().Be("building_giga_megaworkshop_hub_acot_no_parameters"); } @@ -413,7 +401,7 @@ public void GetObjectId_should_yield_results_using_parameters_with_extra_dolar_s { DISetup.SetupContainer(); - var sb = new System.Text.StringBuilder(3175); + var sb = new StringBuilder(3175); sb.AppendLine(@"building_giga_megaworkshop_hub_acot_$tier$$ = {"); sb.AppendLine(@" base_buildtime = @giga_amb_hub_time_$tier$"); sb.AppendLine(@" category = manufacturing"); @@ -565,7 +553,7 @@ public void GetObjectId_should_yield_results_using_parameters_with_extra_dolar_s sb.AppendLine(@"}"); - var sb2 = new System.Text.StringBuilder(257); + var sb2 = new StringBuilder(257); sb2.AppendLine(@"inline_script = {"); sb2.AppendLine(@" script = ""buildings/building_giga_megaworkshop_acot"""); sb2.AppendLine(@" tier = ""delta"""); @@ -582,13 +570,7 @@ public void GetObjectId_should_yield_results_using_parameters_with_extra_dolar_s result.Should().NotBeNullOrEmpty(); var m = DIResolver.Get(); - var parserResult = m.Parse(new ParserManagerArgs() - { - File = "common\\buildings\\dummy.txt", - GameType = "Stellaris", - IsBinary = false, - Lines = result.SplitOnNewLine() - }); + var parserResult = m.Parse(new ParserManagerArgs { File = "common\\buildings\\dummy.txt", GameType = "Stellaris", IsBinary = false, Lines = result.SplitOnNewLine() }); parserResult.Count().Should().Be(1); parserResult.FirstOrDefault().Id.Should().Be("building_giga_megaworkshop_hub_acot_delta$"); } @@ -601,7 +583,7 @@ public void GetObjectId_should_yield_results_using_parameters_while_including_du { DISetup.SetupContainer(); - var sb = new System.Text.StringBuilder(3175); + var sb = new StringBuilder(3175); sb.AppendLine(@"building_giga_megaworkshop_hub_acot_$tier$ = {"); sb.AppendLine(@" base_buildtime = @giga_amb_hub_time_$tier$"); sb.AppendLine(@" category = manufacturing"); @@ -755,7 +737,7 @@ public void GetObjectId_should_yield_results_using_parameters_while_including_du sb.AppendLine(@"}"); - var sb2 = new System.Text.StringBuilder(257); + var sb2 = new StringBuilder(257); sb2.AppendLine(@"inline_script = {"); sb2.AppendLine(@" script = ""buildings/building_giga_megaworkshop_acot"""); sb2.AppendLine(@" tier = ""delta"""); @@ -772,13 +754,7 @@ public void GetObjectId_should_yield_results_using_parameters_while_including_du result.Should().NotBeNullOrEmpty(); var m = DIResolver.Get(); - var parserResult = m.Parse(new ParserManagerArgs() - { - File = "common\\buildings\\dummy.txt", - GameType = "Stellaris", - IsBinary = false, - Lines = result.SplitOnNewLine() - }); + var parserResult = m.Parse(new ParserManagerArgs { File = "common\\buildings\\dummy.txt", GameType = "Stellaris", IsBinary = false, Lines = result.SplitOnNewLine() }); parserResult.Count().Should().Be(2); parserResult.All(p => p.Id == "building_giga_megaworkshop_hub_acot_delta").Should().BeTrue(); } @@ -791,7 +767,7 @@ public void GetObjectId_should_yield_multiple_results_using_parameters() { DISetup.SetupContainer(); - var sb = new System.Text.StringBuilder(3175); + var sb = new StringBuilder(3175); sb.AppendLine(@"building_giga_megaworkshop_hub_acot_$tier$ = {"); sb.AppendLine(@" base_buildtime = @giga_amb_hub_time_$tier$"); sb.AppendLine(@" category = manufacturing"); @@ -945,7 +921,7 @@ public void GetObjectId_should_yield_multiple_results_using_parameters() sb.AppendLine(@"}"); - var sb2 = new System.Text.StringBuilder(257); + var sb2 = new StringBuilder(257); sb2.AppendLine(@"inline_script = {"); sb2.AppendLine(@" script = ""buildings/building_giga_megaworkshop_acot"""); sb2.AppendLine(@" tier = ""delta"""); @@ -962,18 +938,82 @@ public void GetObjectId_should_yield_multiple_results_using_parameters() result.Should().NotBeNullOrEmpty(); var m = DIResolver.Get(); - var parserResult = m.Parse(new ParserManagerArgs() - { - File = "common\\buildings\\dummy.txt", - GameType = "Stellaris", - IsBinary = false, - Lines = result.SplitOnNewLine() - }); + var parserResult = m.Parse(new ParserManagerArgs { File = "common\\buildings\\dummy.txt", GameType = "Stellaris", IsBinary = false, Lines = result.SplitOnNewLine() }); parserResult.Count().Should().Be(2); parserResult.Any(p => p.Id == "building_giga_megaworkshop_hub_acot_delta").Should().BeTrue(); parserResult.Any(p => p.Id == "building_giga_megaworkshop_hub_acot_2_delta").Should().BeTrue(); } + /// + /// Defines the test method GetObjectId_on_first_levelshould_yield_results. + /// + [Fact] + public void GetObjectId_on_first_level_should_yield_results() + { + DISetup.SetupContainer(); + + var sb = new StringBuilder(700); + sb.AppendLine(@"key = ""BIO_PROPULSION_$LEVEL$_$CORRESPONDING_SIZE$"""); + sb.AppendLine(@"size = small"); + sb.AppendLine(@"icon = ""GFX_ship_part_bio_thruster_$LEVEL$"""); + sb.AppendLine(@"icon_frame = 1"); + sb.AppendLine(@"power = 0"); + sb.AppendLine(@""); + sb.AppendLine(@"resources = {"); + sb.AppendLine(@" category = ship_components"); + sb.AppendLine(@" inline_script = {"); + sb.AppendLine(@" script = ""grand_archive/mutations/component_dynamic_cost"""); + sb.AppendLine(@" COST = $COST$"); + sb.AppendLine(@" }"); + sb.AppendLine(@" cost = {"); + sb.AppendLine(@" sr_dark_matter = $DARK_MATTER$"); + sb.AppendLine(@" }"); + sb.AppendLine(@"}"); + sb.AppendLine(@""); + sb.AppendLine(@"modifier = {"); + sb.AppendLine(@" ship_base_speed_mult = $SPEED$"); + sb.AppendLine(@" ship_evasion_add = $EVASION$"); + sb.AppendLine(@"}"); + sb.AppendLine(@""); + sb.AppendLine(@"prerequisites = { $PREREQUISITE$ }"); + sb.AppendLine(@"component_set = ""thruster_components_bio"""); + sb.AppendLine(@"inline_script = {"); + sb.AppendLine(@" script = grand_archive/mutations/core_components/upgrade_thrusters_bio_$LEVEL$"); + sb.AppendLine(@" CORRESPONDING_SIZE = $CORRESPONDING_SIZE$"); + sb.AppendLine(@"}"); + sb.AppendLine(@""); + sb.AppendLine(@"size_restriction = { $SIZE_RESTRICTION$ }"); + sb.AppendLine(@""); + sb.AppendLine(@"ai_weight = {"); + sb.AppendLine(@" weight = $LEVEL$"); + sb.AppendLine(@"}"); + + + var sb2 = new StringBuilder(374); + sb2.AppendLine(@"utility_component_template = {"); + sb2.AppendLine(@" inline_script = {"); + sb2.AppendLine(@" script = grand_archive/mutations/core_components/component_thrusters_bio"); + sb2.AppendLine(@" LEVEL = 5"); + sb2.AppendLine(@" CORRESPONDING_SIZE = BATTLESHIP"); + sb2.AppendLine(@" PREREQUISITE = ""tech_dark_matter_propulsion tech_thrusters_bio_integration"""); + sb2.AppendLine(@" COST = 384"); + sb2.AppendLine(@" DARK_MATTER = 4"); + sb2.AppendLine(@" SPEED = 1"); + sb2.AppendLine(@" EVASION = 8"); + sb2.AppendLine(@" SIZE_RESTRICTION = ""space_whale_5 voidworms_large cutholoids"""); + sb2.AppendLine(@" }"); + sb2.AppendLine(@"}"); + + var parser = new ParametrizedParser(new CodeParser(new Logger())); + var result = parser.Process(sb.ToString(), sb2.ToString()); + result.Should().NotBeNullOrEmpty(); + var m = DIResolver.Get(); + var parserResult = m.Parse(new ParserManagerArgs { File = "common\\component_templates\\dummy.txt", GameType = "Stellaris", IsBinary = false, Lines = result.SplitOnNewLine() }); + parserResult.Count().Should().Be(1); + parserResult.Any(p => p.Id.Equals("BIO_PROPULSION_5_BATTLESHIP")).Should().BeTrue(); + } + + /// /// Defines the test method GetScriptPath_should_yield_results. /// @@ -982,7 +1022,7 @@ public void GetScriptPath_should_yield_results() { DISetup.SetupContainer(); - var sb = new System.Text.StringBuilder(257); + var sb = new StringBuilder(257); sb.AppendLine(@"inline_script = {"); sb.AppendLine(@" script = ""buildings/building_giga_megaworkshop_acot"""); sb.AppendLine(@" tier = ""delta"""); @@ -1007,7 +1047,7 @@ public void GetScriptPath_should_not_yield_results() { DISetup.SetupContainer(); - var sb = new System.Text.StringBuilder(257); + var sb = new StringBuilder(257); sb.AppendLine(@"inline_script = {"); sb.AppendLine(@" tier = ""delta"""); sb.AppendLine(@""); @@ -1022,5 +1062,33 @@ public void GetScriptPath_should_not_yield_results() var result = parser.GetScriptPath(sb.ToString()); result.Should().BeNullOrEmpty(); } + + /// + /// Defines the test method GetScriptPath_as_sub_element_should_yield_results. + /// + [Fact] + public void GetScriptPath_as_sub_element_should_yield_results() + { + DISetup.SetupContainer(); + + var sb = new StringBuilder(374); + sb.AppendLine(@"utility_component_template = {"); + sb.AppendLine(@" inline_script = {"); + sb.AppendLine(@" script = grand_archive/mutations/core_components/component_thrusters_bio"); + sb.AppendLine(@" LEVEL = 5"); + sb.AppendLine(@" CORRESPONDING_SIZE = BATTLESHIP"); + sb.AppendLine(@" PREREQUISITE = ""tech_dark_matter_propulsion tech_thrusters_bio_integration"""); + sb.AppendLine(@" COST = 384"); + sb.AppendLine(@" DARK_MATTER = 4"); + sb.AppendLine(@" SPEED = 1"); + sb.AppendLine(@" EVASION = 8"); + sb.AppendLine(@" SIZE_RESTRICTION = ""space_whale_5 voidworms_large cutholoids"""); + sb.AppendLine(@" }"); + sb.AppendLine(@"}"); + + var parser = new ParametrizedParser(new CodeParser(new Logger())); + var result = parser.GetScriptPath(sb.ToString()); + result.Should().Be("grand_archive\\mutations\\core_components\\component_thrusters_bio"); + } } } diff --git a/src/IronyModManager.Parser.Tests/StellarisKeyParserTests.cs b/src/IronyModManager.Parser.Tests/StellarisKeyParserTests.cs index d27df2af..d712113b 100644 --- a/src/IronyModManager.Parser.Tests/StellarisKeyParserTests.cs +++ b/src/IronyModManager.Parser.Tests/StellarisKeyParserTests.cs @@ -117,54 +117,5 @@ public void Parse_should_yield_results() result[i].Type.Should().Be("common\\special_projects\\txt"); } } - - /// - /// Defines the test method Parse_should_yield_results_with_inline. - /// - [Fact] - public void Parse_should_yield_results_with_inline() - { - DISetup.SetupContainer(); - - var sb = new System.Text.StringBuilder(); - sb.AppendLine(@"special_project = {"); - sb.AppendLine(@" inline_script = {"); - sb.AppendLine(@" script = cosmic_storms/EyeOfTheStormSpecialProjects"); - sb.AppendLine(@" TYPE = nexus_storm"); - sb.AppendLine(@" }"); - sb.AppendLine(@"}"); - - - - var args = new ParserArgs() - { - ContentSHA = "sha", - ModDependencies = new List { "1" }, - File = "common\\special_projects\\fake.txt", - Lines = sb.ToString().Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries), - ModName = "fake" - }; - var parser = new Games.Stellaris.KeyParser(new CodeParser(new Logger()), null); - var result = parser.Parse(args).ToList(); - result.Should().NotBeNullOrEmpty(); - result.Count.Should().Be(1); - for (int i = 0; i < 1; i++) - { - result[i].ContentSHA.Should().Be("sha"); - result[i].Dependencies.First().Should().Be("1"); - result[i].File.Should().Be("common\\special_projects\\fake.txt"); - switch (i) - { - case 0: - result[i].Id.Should().Be("nexus_storm"); - result[i].ValueType.Should().Be(ValueType.Object); - break; - default: - break; - } - result[i].ModName.Should().Be("fake"); - result[i].Type.Should().Be("common\\special_projects\\txt"); - } - } } } diff --git a/src/IronyModManager.Parser/Definitions/Definition.cs b/src/IronyModManager.Parser/Definitions/Definition.cs index 3d0b39bc..20ef4dff 100644 --- a/src/IronyModManager.Parser/Definitions/Definition.cs +++ b/src/IronyModManager.Parser/Definitions/Definition.cs @@ -4,7 +4,7 @@ // Created : 02-16-2020 // // Last Modified By : Mario -// Last Modified On : 10-17-2024 +// Last Modified On : 10-29-2024 // *********************************************************************** // // Mario @@ -247,6 +247,12 @@ public string CodeTag } } + /// + /// Gets or sets a value indicating whether [contains inline identifier]. + /// + /// true if [contains inline identifier]; otherwise, false. + public bool ContainsInlineIdentifier { get; set; } + /// /// Gets or sets the content sha. /// @@ -786,6 +792,7 @@ public object GetValue(string propName, bool unwrap) nameof(UseSimpleValidation) => UseSimpleValidation, nameof(IsSpecialFolder) => IsSpecialFolder, nameof(MergeType) => MergeType, + nameof(ContainsInlineIdentifier) => ContainsInlineIdentifier, _ => Id }; } diff --git a/src/IronyModManager.Parser/Games/Stellaris/KeyParser.cs b/src/IronyModManager.Parser/Games/Stellaris/KeyParser.cs index 469f04a5..256891cc 100644 --- a/src/IronyModManager.Parser/Games/Stellaris/KeyParser.cs +++ b/src/IronyModManager.Parser/Games/Stellaris/KeyParser.cs @@ -4,7 +4,7 @@ // Created : 09-10-2024 // // Last Modified By : Mario -// Last Modified On : 09-10-2024 +// Last Modified On : 10-29-2024 // *********************************************************************** // // Mario @@ -66,26 +66,7 @@ public override bool CanParse(CanParseArgs args) /// System.String. protected override string EvalElementForId(IScriptElement value) { - if (value.Key.Equals("key", StringComparison.OrdinalIgnoreCase)) - { - return value.Value; - } - else if (value.Key.Equals("inline_script")) - { - foreach (var val in value.Values) - { - if (!string.IsNullOrWhiteSpace(val.Key) && !val.Key.Equals("script", StringComparison.OrdinalIgnoreCase)) - { - // TODO: Fix in later versions for now use best guess - if (val.Value.CountLetters() >= 3) - { - return val.Value; - } - } - } - } - - return base.EvalElementForId(value); + return value.Key.Equals("key", StringComparison.OrdinalIgnoreCase) ? value.Value : base.EvalElementForId(value); } #endregion Methods diff --git a/src/IronyModManager.Parser/Generic/KeyParser.cs b/src/IronyModManager.Parser/Generic/KeyParser.cs index 7a1f1b1c..1521a811 100644 --- a/src/IronyModManager.Parser/Generic/KeyParser.cs +++ b/src/IronyModManager.Parser/Generic/KeyParser.cs @@ -4,7 +4,7 @@ // Created : 02-16-2020 // // Last Modified By : Mario -// Last Modified On : 10-17-2024 +// Last Modified On : 10-29-2024 // *********************************************************************** // // Mario @@ -148,7 +148,7 @@ protected virtual bool IsKeyType(CanParseArgs args) closeBrackets += line.Count(s => s == Constants.Scripts.CloseObject); if (openBrackets - closeBrackets <= 1 && Constants.Scripts.GenericKeyIds.Any(s => !string.IsNullOrWhiteSpace(GetValue(line, s)))) { - var bracketLocation = cleaned.IndexOf(Constants.Scripts.OpenObject.ToString()); + var bracketLocation = cleaned.IndexOf(Constants.Scripts.OpenObject.ToString(), StringComparison.OrdinalIgnoreCase); var idLoc = -1; foreach (var item in Constants.Scripts.GenericKeyIds) { diff --git a/src/IronyModManager.Parser/ParametrizedParser.cs b/src/IronyModManager.Parser/ParametrizedParser.cs index 3808df67..a8fcfb60 100644 --- a/src/IronyModManager.Parser/ParametrizedParser.cs +++ b/src/IronyModManager.Parser/ParametrizedParser.cs @@ -1,5 +1,4 @@ - -// *********************************************************************** +// *********************************************************************** // Assembly : IronyModManager.Parser // Author : Mario // Created : 10-03-2023 @@ -12,6 +11,7 @@ // // // *********************************************************************** + using System; using System.Collections.Generic; using System.Linq; @@ -20,7 +20,6 @@ namespace IronyModManager.Parser { - /// /// Class ParametrizedParser. /// Implements the @@ -44,6 +43,7 @@ public class ParametrizedParser : IParametrizedParser /// The terminator /// private const char Terminator = '$'; // I'll be back + /// /// The code parser /// @@ -74,18 +74,28 @@ public ParametrizedParser(ICodeParser codeParser) public string GetScriptPath(string parameters) { var elParams = codeParser.ParseScriptWithoutValidation(parameters.SplitOnNewLine(), string.Empty); - if (elParams != null && elParams.Values != null && elParams.Error == null && elParams.Values.Count(p => p.Key.Equals(Common.Constants.Stellaris.InlineScriptId, StringComparison.OrdinalIgnoreCase)) == 1) + if (elParams is { Values: not null, Error: null }) { - var elObj = elParams.Values.FirstOrDefault(p => p.Values != null); - if (elObj != null) + if (elParams.Values.Count(p => p.Key.Equals(Common.Constants.Stellaris.InlineScriptId, StringComparison.OrdinalIgnoreCase)) == 1) { - var match = elObj.Values.FirstOrDefault(p => p.Key.Equals(Script, StringComparison.OrdinalIgnoreCase)); + var elObj = elParams.Values.FirstOrDefault(p => p.Values != null); + var match = elObj?.Values.FirstOrDefault(p => p.Key.Equals(Script, StringComparison.OrdinalIgnoreCase)); if (match != null) { - return ((match.Value ?? string.Empty).Trim(Quotes)).StandardizeDirectorySeparator(); + return (match.Value ?? string.Empty).Trim(Quotes).StandardizeDirectorySeparator(); + } + } + else if (elParams.Values.Count() == 1 && elParams.Values.FirstOrDefault()!.Values.Count(p => p.Key.Equals(Common.Constants.Stellaris.InlineScriptId, StringComparison.OrdinalIgnoreCase)) == 1) + { + var elObj = elParams.Values.FirstOrDefault(p => p.Values != null)?.Values.FirstOrDefault(); + var match = elObj?.Values.FirstOrDefault(p => p.Key.Equals(Script, StringComparison.OrdinalIgnoreCase)); + if (match != null) + { + return (match.Value ?? string.Empty).Trim(Quotes).StandardizeDirectorySeparator(); } } } + return string.Empty; } @@ -98,25 +108,60 @@ public string GetScriptPath(string parameters) public string Process(string code, string parameters) { var elParams = codeParser.ParseScriptWithoutValidation(parameters.SplitOnNewLine(), string.Empty); - if (elParams != null && elParams.Values != null && elParams.Error == null && elParams.Values.Count(p => p.Key.Equals(Common.Constants.Stellaris.InlineScriptId, StringComparison.OrdinalIgnoreCase)) == 1) + if (elParams is { Values: not null, Error: null }) { - var processed = code; - var elObj = elParams.Values.FirstOrDefault(p => p.Values != null); - if (elObj != null) + if (elParams.Values.Count(p => p.Key.Equals(Common.Constants.Stellaris.InlineScriptId, StringComparison.OrdinalIgnoreCase)) == 1) { - foreach (var value in elObj.Values) + var processed = code; + var elObj = elParams.Values.FirstOrDefault(p => p.Values != null); + if (elObj != null) { - var id = (value.Key ?? string.Empty).Trim(Quotes); - var replacement = (value.Value ?? string.Empty).Trim(Quotes); - if (!id.Equals(Script, StringComparison.OrdinalIgnoreCase)) + foreach (var value in elObj.Values) { - var key = $"{Terminator}{id}{Terminator}"; - processed = processed.Replace(key, replacement, StringComparison.OrdinalIgnoreCase); + var id = (value.Key ?? string.Empty).Trim(Quotes); + var replacement = (value.Value ?? string.Empty).Trim(Quotes); + if (!id.Equals(Script, StringComparison.OrdinalIgnoreCase)) + { + var key = $"{Terminator}{id}{Terminator}"; + processed = processed.Replace(key, replacement, StringComparison.OrdinalIgnoreCase); + } + } + } + + return processed; + } + else if (elParams.Values.Count() == 1 && elParams.Values.FirstOrDefault()!.Values.Count(p => p.Key.Equals(Common.Constants.Stellaris.InlineScriptId, StringComparison.OrdinalIgnoreCase)) == 1) + { + var processed = code; + var elObj = elParams.Values.FirstOrDefault(p => p.Values != null)?.Values.FirstOrDefault(); + if (elObj != null) + { + foreach (var value in elObj.Values) + { + var id = (value.Key ?? string.Empty).Trim(Quotes); + var replacement = (value.Value ?? string.Empty).Trim(Quotes); + if (!id.Equals(Script, StringComparison.OrdinalIgnoreCase)) + { + var key = $"{Terminator}{id}{Terminator}"; + processed = processed.Replace(key, replacement, StringComparison.OrdinalIgnoreCase); + } + } + + var replacementCode = codeParser.ParseScriptWithoutValidation(processed.SplitOnNewLine(), string.Empty); + if (replacementCode is { Values: not null, Error: null }) + { + var newCode = elParams.Values.FirstOrDefault(p => p.Values != null); + if (newCode != null) + { + newCode.Values = replacementCode.Values; + processed = codeParser.FormatCode(newCode); + return processed; + } } } } - return processed; } + return string.Empty; } diff --git a/src/IronyModManager.Services.Tests/GameIndexServiceTests.cs b/src/IronyModManager.Services.Tests/GameIndexServiceTests.cs index 0ce5f891..246357b5 100644 --- a/src/IronyModManager.Services.Tests/GameIndexServiceTests.cs +++ b/src/IronyModManager.Services.Tests/GameIndexServiceTests.cs @@ -4,7 +4,7 @@ // Created : 05-27-2021 // // Last Modified By : Mario -// Last Modified On : 05-27-2021 +// Last Modified On : 10-30-2024 // *********************************************************************** // // Mario @@ -28,6 +28,7 @@ using IronyModManager.Parser.Common; using IronyModManager.Parser.Common.Args; using IronyModManager.Parser.Common.Mod; +using IronyModManager.Parser.Common.Parsers; using IronyModManager.Parser.Definitions; using IronyModManager.Services.Common; using IronyModManager.Shared.Cache; @@ -61,12 +62,15 @@ public class GameIndexServiceTests /// GameIndexService. private static GameIndexService GetService(Mock gameIndexer, Mock storageProvider, Mock modParser, Mock parserManager, Mock reader, Mock mapper, Mock modWriter, - Mock gameService, IEnumerable definitionInfoProviders = null) + Mock gameService) { + var providers = new Mock(); + providers.Setup(p => p.CanProcess(It.IsAny())).Returns(true); + var messageBus = new Mock(); messageBus.Setup(p => p.PublishAsync(It.IsAny())); messageBus.Setup(p => p.Publish(It.IsAny())); - return new GameIndexService(messageBus.Object, parserManager.Object, gameIndexer.Object, new Cache(), definitionInfoProviders, reader.Object, modWriter.Object, modParser.Object, gameService.Object, storageProvider.Object, + return new GameIndexService(null, messageBus.Object, parserManager.Object, gameIndexer.Object, new Cache(), new List() { providers.Object }, reader.Object, modWriter.Object, modParser.Object, gameService.Object, storageProvider.Object, mapper.Object); } diff --git a/src/IronyModManager.Services/GameIndexService.cs b/src/IronyModManager.Services/GameIndexService.cs index e8797218..18d522af 100644 --- a/src/IronyModManager.Services/GameIndexService.cs +++ b/src/IronyModManager.Services/GameIndexService.cs @@ -4,7 +4,7 @@ // Created : 05-27-2021 // // Last Modified By : Mario -// Last Modified On : 02-25-2024 +// Last Modified On : 10-30-2024 // *********************************************************************** // // Mario @@ -20,6 +20,7 @@ using System.Threading; using System.Threading.Tasks; using AutoMapper; +using IronyModManager.DI; using IronyModManager.IO.Common.Game; using IronyModManager.IO.Common.Mods; using IronyModManager.IO.Common.Readers; @@ -27,6 +28,7 @@ using IronyModManager.Parser.Common; using IronyModManager.Parser.Common.Args; using IronyModManager.Parser.Common.Mod; +using IronyModManager.Parser.Common.Parsers; using IronyModManager.Services.Common; using IronyModManager.Services.Common.MessageBus; using IronyModManager.Shared; @@ -35,6 +37,7 @@ using IronyModManager.Shared.Models; using IronyModManager.Storage.Common; using Nito.AsyncEx; +using ValueType = IronyModManager.Shared.Models.ValueType; namespace IronyModManager.Services { @@ -46,6 +49,7 @@ namespace IronyModManager.Services /// /// public class GameIndexService( + IParametrizedParser parametrizedParser, IMessageBus messageBus, IParserManager parserManager, IGameIndexer gameIndexer, @@ -85,6 +89,16 @@ public class GameIndexService( /// private readonly IParserManager parserManager = parserManager; + /// + /// The parametrized parser + /// + private readonly IParametrizedParser parametrizedParser = parametrizedParser; + + /// + /// The inline scripts folder + /// + private readonly string inlineScriptsFolder = "common\\inline_scripts".StandardizeDirectorySeparator(); + #endregion Fields #region Methods @@ -113,7 +127,10 @@ public virtual async Task IndexDefinitionsAsync(IGame game, IEnumerable p.CanProcess(game.Type)); files = files.Where(p => game.GameFolders.Any(p.StartsWith)); + var gameInlineScriptFiles = files.Where(p => p.StartsWith(inlineScriptsFolder, StringComparison.OrdinalIgnoreCase)); + var gameInlineFolders = gameInlineScriptFiles.Select(Path.GetDirectoryName).GroupBy(p => p).Select(p => p.FirstOrDefault()).ToList(); var indexedFolders = (await indexedDefinitions.GetAllDirectoryKeysAsync()).Select(p => p.ToLowerInvariant()); var validFolders = files.Select(Path.GetDirectoryName).GroupBy(p => p).Select(p => p.FirstOrDefault()).Where(p => indexedFolders.Any(a => a.ToLowerInvariant().Equals(p!.ToLowerInvariant()))); var folders = new List(); @@ -125,12 +142,98 @@ public virtual async Task IndexDefinitionsAsync(IGame game, IEnumerable(); + + // First index inline scripts folder + if (provider is { SupportsInlineScripts: true }) + { + total += gameInlineFolders.Count; + var inlineFolders = folders.Where(p => p.StartsWith(inlineScriptsFolder, StringComparison.OrdinalIgnoreCase)).ToList(); + var inlineTasks = inlineFolders.AsParallel().Select(async folder => + { + await semaphore.WaitAsync(); + try + { + await Task.Run(async () => + { + var result = await ParseGameFiles(game, Reader.Read(Path.Combine(gamePath, folder), searchSubFolders: false), folder, null); + if ((result?.Any()).GetValueOrDefault()) + { + await gameIndexer.SaveDefinitionsAsync(GetStoragePath(), game, result); + } + }); + using var mutex = await asyncServiceLock.LockAsync(); + processed++; + var perc = GetProgressPercentage(total, processed, 100); + if (perc.IsNotNearlyEqual(previousProgress)) + { + await messageBus.PublishAsync(new GameIndexProgressEvent(perc)); + previousProgress = perc; + } + + GCRunner.RunGC(GCCollectionMode.Optimized); + + // ReSharper disable once DisposeOnUsingVariable + mutex.Dispose(); + } + finally + { + semaphore.Release(); + } + }); + await Task.WhenAll(inlineTasks); + + folders = folders.Where(p => !p.StartsWith(inlineScriptsFolder, StringComparison.OrdinalIgnoreCase)).ToList(); + + var loadTasks = gameInlineFolders.Select(async directory => + { + await semaphore.WaitAsync(); + try + { + inlineDefinitions = await LoadDefinitionsInternalAsync(inlineDefinitions, game, versions, null, directory); + using var mutex = await asyncServiceLock.LockAsync(); + processed++; + var perc = GetProgressPercentage(total, processed, 100); + if (perc.IsNotNearlyEqual(previousProgress)) + { + await messageBus.PublishAsync(new GameIndexProgressEvent(perc)); + previousProgress = perc; + } + + GCRunner.RunGC(GCCollectionMode.Optimized); + + // ReSharper disable once DisposeOnUsingVariable + mutex.Dispose(); + } + finally + { + semaphore.Release(); + } + }).ToList(); + await Task.WhenAll(loadTasks); + } + var tasks = folders.AsParallel().Select(async folder => { await semaphore.WaitAsync(); @@ -138,7 +241,7 @@ public virtual async Task IndexDefinitionsAsync(IGame game, IEnumerable { - var result = ParseGameFiles(game, Reader.Read(Path.Combine(gamePath, folder), searchSubFolders: false), folder); + var result = await ParseGameFiles(game, Reader.Read(Path.Combine(gamePath, folder), searchSubFolders: false), folder, inlineDefinitions); if ((result?.Any()).GetValueOrDefault()) { await gameIndexer.SaveDefinitionsAsync(GetStoragePath(), game, result); @@ -155,6 +258,7 @@ await Task.Run(async () => GCRunner.RunGC(GCCollectionMode.Optimized); + // ReSharper disable once DisposeOnUsingVariable mutex.Dispose(); } finally @@ -162,7 +266,10 @@ await Task.Run(async () => semaphore.Release(); } }); + await Task.WhenAll(tasks); + inlineDefinitions.Dispose(); + inlineDefinitions = null; } return true; @@ -183,11 +290,31 @@ await Task.Run(async () => /// The game languages. /// IIndexedDefinitions. public virtual async Task LoadDefinitionsAsync(IIndexedDefinitions modDefinitions, IGame game, IEnumerable versions, IReadOnlyCollection gameLanguages) + { + return await LoadDefinitionsInternalAsync(modDefinitions, game, versions, gameLanguages); + } + + /// + /// Load definitions internal as an asynchronous operation. + /// + /// The mod definitions. + /// The game. + /// The versions. + /// The game languages. + /// The directory override. + /// A Task<IIndexedDefinitions> representing the asynchronous operation. + protected virtual async Task LoadDefinitionsInternalAsync(IIndexedDefinitions modDefinitions, IGame game, IEnumerable versions, IReadOnlyCollection gameLanguages, + string directoryOverride = Shared.Constants.EmptyParam) { if (game != null && versions != null && versions.Any() && await gameIndexer.GameVersionsSameAsync(GetStoragePath(), game, versions)) { var gameDefinitions = new ConcurrentBag(); - var directories = await modDefinitions.GetAllDirectoryKeysAsync(); + var directories = (await modDefinitions.GetAllDirectoryKeysAsync()).ToList(); + if (!string.IsNullOrWhiteSpace(directoryOverride)) + { + directories = [directoryOverride]; + } + // Kinda need to force insert localisation directory itself if (directories.Any(p => p.StartsWith(Shared.Constants.LocalizationDirectory, StringComparison.OrdinalIgnoreCase)) && !directories.Any(p => p.Equals(Shared.Constants.LocalizationDirectory, StringComparison.OrdinalIgnoreCase))) @@ -196,6 +323,18 @@ public virtual async Task LoadDefinitionsAsync(IIndexedDefi directories = newDirs; } + // No directory override this means we want to ensure inline directories are loaded even if not requested + if (string.IsNullOrWhiteSpace(directoryOverride)) + { + var gamePath = PathResolver.GetPath(game); + var files = Reader.GetFiles(gamePath); + files = files.Where(p => game.GameFolders.Any(p.StartsWith)); + var gameInlineScriptFiles = files.Where(p => p.StartsWith(inlineScriptsFolder, StringComparison.OrdinalIgnoreCase)); + var gameInlineFolders = gameInlineScriptFiles.Select(Path.GetDirectoryName).GroupBy(p => p).Select(p => p.FirstOrDefault()).ToList(); + directories.AddRange(gameInlineFolders); + directories = directories.Distinct().ToList(); + } + if (gameLanguages != null && gameLanguages.Count != 0) { var folders = gameLanguages.Select(p => p.Type[2..]); @@ -227,7 +366,7 @@ public virtual async Task LoadDefinitionsAsync(IIndexedDefi } double processed = 0; - double total = directories.Count(); + double total = directories.Count; double previousProgress = 0; using var semaphore = new SemaphoreSlim(MaxFoldersToProcess); var tasks = directories.Select(async directory => @@ -268,7 +407,7 @@ public virtual async Task LoadDefinitionsAsync(IIndexedDefi processed++; var perc = GetProgressPercentage(total, processed, 100); - if (perc.IsNotNearlyEqual(previousProgress)) + if (perc.IsNotNearlyEqual(previousProgress) && string.IsNullOrWhiteSpace(directoryOverride)) // Directory override present, probably an internal call { await messageBus.PublishAsync(new GameDefinitionLoadProgressEvent(perc)); previousProgress = perc; @@ -324,13 +463,15 @@ protected virtual string GetStoragePath() /// The file infos. /// The folder. /// IEnumerable<IDefinition>. - protected virtual IEnumerable ParseGameFiles(IGame game, IEnumerable fileInfos, string folder) + protected virtual async Task> ParseGameFiles(IGame game, IEnumerable fileInfos, string folder, IIndexedDefinitions inlineDefinitions) { if (fileInfos == null) { return null; } + var provider = DefinitionInfoProviders.FirstOrDefault(p => p.CanProcess(game.Type)); + var definitions = new List(); foreach (var fileInfo in fileInfos) { @@ -342,14 +483,76 @@ protected virtual IEnumerable ParseGameFiles(IGame game, IEnumerabl Lines = fileInfo.Content, ModName = game.Name, ValidationType = ValidationType.SkipAll - }).Where(p => p.ValueType != Shared.Models.ValueType.Invalid); + }).Where(p => p.ValueType != ValueType.Invalid); MergeDefinitions(fileDefs); definitions.AddRange(fileDefs); } - return definitions; - } + List prunedInlineDefinitions = null; + if (provider!.SupportsInlineScripts && !folder.StandardizeDirectorySeparator().StartsWith(inlineScriptsFolder, StringComparison.OrdinalIgnoreCase)) + { + inlineDefinitions ??= DIResolver.Get(); + prunedInlineDefinitions = []; + foreach (var item in definitions) + { + var addDefault = true; + if (item.Id.Equals(Parser.Common.Constants.Stellaris.InlineScriptId, StringComparison.OrdinalIgnoreCase) || item.ContainsInlineIdentifier) + { + addDefault = false; + var path = Path.Combine(Parser.Common.Constants.Stellaris.InlineScripts, parametrizedParser.GetScriptPath(item.Code)); + var pathCI = path.ToLowerInvariant(); + var files = (await inlineDefinitions.GetByParentDirectoryAsync(Path.GetDirectoryName(path))).Where(p => Path.Combine(Path.GetDirectoryName(p.FileCI)!, Path.GetFileNameWithoutExtension(p.FileCI)!).Equals(pathCI)) + .ToList(); + if (files.Count != 0) + { + var priorityDefinition = EvalDefinitionPriorityInternal([.. files.OrderBy(p => p.FileCI, StringComparer.Ordinal)]); + if (priorityDefinition is { Definition: not null }) + { + var parametrizedCode = parametrizedParser.Process(priorityDefinition.Definition.Code, item.Code); + if (!string.IsNullOrWhiteSpace(parametrizedCode)) + { + var results = parserManager.Parse(new ParserManagerArgs + { + ContentSHA = item.ContentSHA, // Want original file sha id + File = item.File, // To trigger right parser + GameType = game.Type, + Lines = parametrizedCode.SplitOnNewLine(), + FileLastModified = item.LastModified, + ModDependencies = item.Dependencies, + IsBinary = item.ValueType == ValueType.Binary, + ModName = item.ModName, + ValidationType = ValidationType.SkipAll + }); + if (item.Variables != null && item.Variables.Any() && results != null) + { + MergeDefinitions(results.Concat(item.Variables)); + } + + if (results != null && results.Any()) + { + prunedInlineDefinitions.AddRange(results); + } + } + } + } + } - #endregion Methods + if (addDefault) + { + prunedInlineDefinitions.Add(item); + } + } + + GCRunner.RunGC(GCCollectionMode.Optimized); + } + else + { + prunedInlineDefinitions = [.. definitions]; + } + + return prunedInlineDefinitions; + } } + + #endregion Methods } diff --git a/src/IronyModManager.Services/ModPatchCollectionService.cs b/src/IronyModManager.Services/ModPatchCollectionService.cs index cbe2a72b..ffd0b38f 100644 --- a/src/IronyModManager.Services/ModPatchCollectionService.cs +++ b/src/IronyModManager.Services/ModPatchCollectionService.cs @@ -4,7 +4,7 @@ // Created : 05-26-2020 // // Last Modified By : Mario -// Last Modified On : 02-25-2024 +// Last Modified On : 10-30-2024 // *********************************************************************** // // Mario @@ -50,8 +50,8 @@ namespace IronyModManager.Services /// /// The mod patch collection service. /// - /// - /// + /// + /// public class ModPatchCollectionService( ICache cache, IMessageBus messageBus, @@ -383,6 +383,7 @@ public virtual async Task FindConflictsAsync(IIndexedDefinition var allIndexed = DIResolver.Get(); var allDefs = (await indexedDefinitions.GetAllAsync()).ToList(); await allIndexed.InitMapAsync(allDefs); + indexedDefinitions.Dispose(); indexedDefinitions = DIResolver.Get(); await indexedDefinitions.InitMapAsync(allDefs.Where(p => p.MergeType == MergeType.None)); @@ -393,6 +394,7 @@ public virtual async Task FindConflictsAsync(IIndexedDefinition var overwritten = (await indexedDefinitions.GetByValueTypeAsync(ValueType.OverwrittenObject)).Concat(await indexedDefinitions.GetByValueTypeAsync(ValueType.OverwrittenObjectSingleFile)); var empty = await indexedDefinitions.GetByValueTypeAsync(ValueType.EmptyFile); var allCount = (await indexedDefinitions.GetAllAsync()).Count(); + var game = GameService.GetSelected(); double total = (allCount * 2) + typeAndIdKeys.Count() + (overwritten.GroupBy(p => p.TypeAndId).Count() * 2) + empty.Count(); double processed = 0; @@ -400,7 +402,7 @@ public virtual async Task FindConflictsAsync(IIndexedDefinition await messageBus.PublishAsync(new ModDefinitionAnalyzeEvent(0)); // Cheers to the guys at paradox for doing a great job at implementing such a thing - var provider = DefinitionInfoProviders.FirstOrDefault(p => p.CanProcess(GameService.GetSelected().Type)); + var provider = DefinitionInfoProviders.FirstOrDefault(p => p.CanProcess(game.Type)); if (provider!.SupportsScriptMerge && allCount > 0) { var merges = provider.MergeTypes; @@ -448,6 +450,124 @@ public virtual async Task FindConflictsAsync(IIndexedDefinition allDefs = null; GCRunner.RunGC(GCCollectionMode.Optimized, false); + // Handling inlines here now as game now uses these a lot -- Thanks pdx again + allDefs = (await indexedDefinitions.GetAllAsync()).ToList(); + + // Stellaris only (so far) + total += provider!.SupportsInlineScripts ? allDefs.Count : 0; + previousProgress = 0; + List prunedInlineDefinitions; + if (provider.SupportsInlineScripts) + { + var tempIndex = DIResolver.Get(); + await tempIndex.InitMapAsync(allDefs); + prunedInlineDefinitions = []; + var reportedInlineErrors = new HashSet(); + foreach (var item in allDefs) + { + var addDefault = true; + if (item.Id.Equals(Parser.Common.Constants.Stellaris.InlineScriptId, StringComparison.OrdinalIgnoreCase) || item.ContainsInlineIdentifier) + { + addDefault = false; + var path = Path.Combine(Parser.Common.Constants.Stellaris.InlineScripts, parametrizedParser.GetScriptPath(item.Code)); + var pathCI = path.ToLowerInvariant(); + var files = (await tempIndex.GetByParentDirectoryAsync(Path.GetDirectoryName(path))).Where(p => Path.Combine(Path.GetDirectoryName(p.FileCI)!, Path.GetFileNameWithoutExtension(p.FileCI)!).Equals(pathCI)).ToList(); + if (files.Count != 0) + { + var priorityDefinition = EvalDefinitionPriority([.. files.OrderBy(p => modOrder.IndexOf(p.ModName))]); + if (priorityDefinition is { Definition: not null }) + { + var parametrizedCode = parametrizedParser.Process(priorityDefinition.Definition.Code, item.Code); + if (!string.IsNullOrWhiteSpace(parametrizedCode)) + { + var validationType = ValidationType.Full; + if (item.UseSimpleValidation.GetValueOrDefault() || item.UseSimpleValidation == null) + { + validationType = MapValidationType(item); + } + else if (priorityDefinition.Definition.UseSimpleValidation.GetValueOrDefault() || priorityDefinition.Definition.UseSimpleValidation == null) + { + validationType = MapValidationType(priorityDefinition.Definition); + } + + var results = parserManager.Parse(new ParserManagerArgs + { + ContentSHA = item.ContentSHA, // Want original file sha id + File = item.File, // To trigger right parser + GameType = game.Type, + Lines = parametrizedCode.SplitOnNewLine(), + FileLastModified = item.LastModified, + ModDependencies = item.Dependencies, + IsBinary = item.ValueType == ValueType.Binary, + ModName = item.ModName, + ValidationType = validationType // This is kinda difficult but try to guess which validation type we want to inherit + }); + if (item.Variables != null && item.Variables.Any() && results != null) + { + MergeDefinitions(results.Concat(item.Variables)); + } + + if (results != null && results.Any()) + { + prunedInlineDefinitions.AddRange(results); + } + else if (!reportedInlineErrors.Contains($"{item.ModName} - {pathCI}")) + { + // Could happen, will need manually investigation though + var copy = CopyDefinition(item); + copy.ValueType = ValueType.Invalid; + copy.ErrorMessage = $"Inline script {path} failed to be processed. Please report to the author of Irony."; + prunedInlineDefinitions.Add(copy); + reportedInlineErrors.Add($"{item.ModName} - {pathCI}"); + } + } + } + } + else if (!reportedInlineErrors.Contains($"{item.ModName} - {pathCI}")) + { + // Need to report missing inline script + var copy = CopyDefinition(item); + copy.ValueType = ValueType.Invalid; + copy.ErrorMessage = $"Inline script {path} is not found in any mods.{Environment.NewLine}{Environment.NewLine}It is possible that the file is missing or due to a syntax error Irony cannot find it."; + prunedInlineDefinitions.Add(copy); + reportedInlineErrors.Add($"{item.ModName} - {pathCI}"); + } + } + + if (addDefault) + { + prunedInlineDefinitions.Add(item); + } + + processed++; + var perc = GetProgressPercentage(total, processed, 100); + if (perc.IsNotNearlyEqual(previousProgress)) + { + await messageBus.PublishAsync(new ModDefinitionAnalyzeEvent(perc)); + previousProgress = perc; + } + } + + tempIndex.Dispose(); + } + else + { + prunedInlineDefinitions = [.. allDefs]; + } + + // Run another cleanup + indexedDefinitions.Dispose(); + indexedDefinitions = null; + allDefs.Clear(); + allDefs = null; + + indexedDefinitions = DIResolver.Get(); + await indexedDefinitions.InitMapAsync(prunedInlineDefinitions); + + prunedInlineDefinitions.Clear(); + prunedInlineDefinitions = null; + GCRunner.RunGC(GCCollectionMode.Optimized, false); + var stopWatch = new Stopwatch(); stopWatch.Start(); @@ -1021,116 +1141,14 @@ public virtual async Task GetModObjectsAsync(IGame game, IE await messageBus.PublishAsync(new ModDefinitionInvalidReplaceEvent(0)); processed = 0; - - // Stellaris only (so far) - total = provider!.SupportsInlineScripts ? definitions.Count * 2 : definitions.Count; + total = definitions.Count; previousProgress = 0; + List prunedDefinitions; + List definitionsCopy = [.. definitions]; var patchName = GenerateCollectionPatchName(collectionName); var state = await modPatchExporter.GetPatchStateAsync(new ModPatchExporterParameters { RootPath = GetModDirectoryRootPath(game), PatchPath = EvaluatePatchNamePath(game, patchName) }); - // Process so far Giga related stuff for now. Scared what else might be valid for inline_scripts. - List prunedInlineDefinitions; - if (provider.SupportsInlineScripts) - { - var tempIndex = DIResolver.Get(); - await tempIndex.InitMapAsync(definitions); - prunedInlineDefinitions = []; - var reportedInlineErrors = new HashSet(); - foreach (var item in definitions) - { - var addDefault = true; - if (item.Id.Equals(Parser.Common.Constants.Stellaris.InlineScriptId, StringComparison.OrdinalIgnoreCase)) - { - addDefault = false; - var path = Path.Combine(Parser.Common.Constants.Stellaris.InlineScripts, parametrizedParser.GetScriptPath(item.Code)); - var pathCI = path.ToLowerInvariant(); - var files = (await tempIndex.GetByParentDirectoryAsync(Path.GetDirectoryName(path))).Where(p => Path.Combine(Path.GetDirectoryName(p.FileCI)!, Path.GetFileNameWithoutExtension(p.FileCI)!).Equals(pathCI)).ToList(); - if (files.Count != 0) - { - var modOrder = mods.Select(p => p.Name).ToList(); - var priorityDefinition = EvalDefinitionPriority([.. files.OrderBy(p => modOrder.IndexOf(p.ModName))]); - if (priorityDefinition is { Definition: not null }) - { - var parametrizedCode = parametrizedParser.Process(priorityDefinition.Definition.Code, item.Code); - if (!string.IsNullOrWhiteSpace(parametrizedCode)) - { - var validationType = ValidationType.Full; - if (item.UseSimpleValidation.GetValueOrDefault() || item.UseSimpleValidation == null) - { - validationType = MapValidationType(item); - } - else if (priorityDefinition.Definition.UseSimpleValidation.GetValueOrDefault() || priorityDefinition.Definition.UseSimpleValidation == null) - { - validationType = MapValidationType(priorityDefinition.Definition); - } - - var results = parserManager.Parse(new ParserManagerArgs - { - ContentSHA = item.ContentSHA, // Want original file sha id - File = item.File, // To trigger right parser - GameType = game.Type, - Lines = parametrizedCode.SplitOnNewLine(), - FileLastModified = item.LastModified, - ModDependencies = item.Dependencies, - IsBinary = item.ValueType == ValueType.Binary, - ModName = item.ModName, - ValidationType = validationType // This is kinda difficult but try to guess which validation type we want to inherit - }); - if (item.Variables != null && item.Variables.Any() && results != null) - { - MergeDefinitions(results.Concat(item.Variables)); - } - - if (results != null && results.Any()) - { - prunedInlineDefinitions.AddRange(results); - } - else if (!reportedInlineErrors.Contains($"{item.ModName} - {pathCI}")) - { - // Could happen, will need manually investigation though - var copy = CopyDefinition(item); - copy.ValueType = ValueType.Invalid; - copy.ErrorMessage = $"Inline script {path} failed to be processed. Please report to the author of Irony."; - prunedInlineDefinitions.Add(copy); - reportedInlineErrors.Add($"{item.ModName} - {pathCI}"); - } - } - } - } - else if (!reportedInlineErrors.Contains($"{item.ModName} - {pathCI}")) - { - // Need to report missing inline script - var copy = CopyDefinition(item); - copy.ValueType = ValueType.Invalid; - copy.ErrorMessage = $"Inline script {path} is not found in any mods.{Environment.NewLine}{Environment.NewLine}It is possible that the file is missing or due to a syntax error Irony cannot find it."; - prunedInlineDefinitions.Add(copy); - reportedInlineErrors.Add($"{item.ModName} - {pathCI}"); - } - } - - if (addDefault) - { - prunedInlineDefinitions.Add(item); - } - - processed++; - var perc = GetProgressPercentage(total, processed, 100); - if (perc.IsNotNearlyEqual(previousProgress)) - { - await messageBus.PublishAsync(new ModDefinitionInvalidReplaceEvent(perc)); - previousProgress = perc; - } - } - - tempIndex.Dispose(); - GCRunner.RunGC(GCCollectionMode.Optimized); - } - else - { - prunedInlineDefinitions = [.. definitions]; - } - definitions.Clear(); definitions = null; if (state != null && state.CustomConflicts.Any()) @@ -1138,7 +1156,7 @@ public virtual async Task GetModObjectsAsync(IGame game, IE prunedDefinitions = []; var customIndexed = DIResolver.Get(); await customIndexed.InitMapAsync(state.CustomConflicts); - foreach (var item in prunedInlineDefinitions) + foreach (var item in definitionsCopy) { var addDefault = true; if (item.ValueType == ValueType.Invalid) @@ -1197,11 +1215,11 @@ public virtual async Task GetModObjectsAsync(IGame game, IE } else { - prunedDefinitions = [.. prunedInlineDefinitions]; + prunedDefinitions = [.. definitionsCopy]; } - prunedInlineDefinitions.Clear(); - prunedInlineDefinitions = null; + definitionsCopy.Clear(); + definitionsCopy = null; await messageBus.PublishAsync(new ModDefinitionInvalidReplaceEvent(99.9)); var indexed = DIResolver.Get(); @@ -3110,28 +3128,8 @@ protected virtual IDefinition PartialDefinitionCopy(IDefinition definition, bool return CopyDefinition(definition); } - var copy = DIResolver.Get(); - if (copyAdditionalFilenames) - { - copy.AdditionalFileNames = definition.AdditionalFileNames; - } - - copy.DiskFile = definition.DiskFile; - copy.File = definition.File; - copy.Id = definition.Id; - copy.ModName = definition.ModName; - copy.Tags = definition.Tags; - copy.Type = definition.Type; - copy.ValueType = definition.ValueType; - copy.IsFromGame = definition.IsFromGame; - copy.Order = definition.Order; - copy.OriginalFileName = definition.OriginalFileName; - copy.ResetType = definition.ResetType; - copy.FileNameSuffix = definition.FileNameSuffix; - copy.IsPlaceholder = definition.IsPlaceholder; - copy.UseSimpleValidation = definition.UseSimpleValidation; - copy.IsSpecialFolder = definition.IsSpecialFolder; - return copy; + var clone = DIResolver.Get(); + return clone.PartialCloneDefinition(definition, copyAdditionalFilenames); } /// diff --git a/src/IronyModManager.Services/ObjectClone.cs b/src/IronyModManager.Services/ObjectClone.cs index 0dae678f..bc6da5a3 100644 --- a/src/IronyModManager.Services/ObjectClone.cs +++ b/src/IronyModManager.Services/ObjectClone.cs @@ -4,7 +4,7 @@ // Created : 05-14-2023 // // Last Modified By : Mario -// Last Modified On : 10-17-2024 +// Last Modified On : 10-30-2024 // *********************************************************************** // // Mario @@ -82,9 +82,44 @@ public IDefinition CloneDefinition(IDefinition definition, bool includeCode) newDefinition.UseSimpleValidation = definition.UseSimpleValidation; newDefinition.IsSpecialFolder = definition.IsSpecialFolder; newDefinition.MergeType = definition.MergeType; + newDefinition.ContainsInlineIdentifier = definition.ContainsInlineIdentifier; return newDefinition; } + /// + /// Partials the clone definition. + /// + /// The definition. + /// if set to true [copy additional filenames]. + /// IDefinition. + public IDefinition PartialCloneDefinition(IDefinition definition, bool copyAdditionalFilenames = true) + { + var copy = DIResolver.Get(); + if (copyAdditionalFilenames) + { + copy.AdditionalFileNames = definition.AdditionalFileNames; + } + + copy.DiskFile = definition.DiskFile; + copy.File = definition.File; + copy.Id = definition.Id; + copy.ModName = definition.ModName; + copy.Tags = definition.Tags; + copy.Type = definition.Type; + copy.ValueType = definition.ValueType; + copy.IsFromGame = definition.IsFromGame; + copy.Order = definition.Order; + copy.OriginalFileName = definition.OriginalFileName; + copy.ResetType = definition.ResetType; + copy.FileNameSuffix = definition.FileNameSuffix; + copy.IsPlaceholder = definition.IsPlaceholder; + copy.UseSimpleValidation = definition.UseSimpleValidation; + copy.IsSpecialFolder = definition.IsSpecialFolder; + copy.MergeType = definition.MergeType; + copy.ContainsInlineIdentifier = definition.ContainsInlineIdentifier; + return copy; + } + #endregion Methods } } diff --git a/src/IronyModManager.Services/Registrations/GameRegistration.cs b/src/IronyModManager.Services/Registrations/GameRegistration.cs index 94fc979e..c9ee4f32 100644 --- a/src/IronyModManager.Services/Registrations/GameRegistration.cs +++ b/src/IronyModManager.Services/Registrations/GameRegistration.cs @@ -4,7 +4,7 @@ // Created : 02-12-2020 // // Last Modified By : Mario -// Last Modified On : 10-17-2024 +// Last Modified On : 10-29-2024 // *********************************************************************** // // Mario @@ -39,12 +39,12 @@ public class GameRegistration : PostStartup /// /// The hoi4 cache version /// - private const int HOI4CacheVersion = 15; + private const int HOI4CacheVersion = 16; /// /// The stellaris cache version /// - private const int StellarisCacheVersion = 25; + private const int StellarisCacheVersion = 26; /// /// The path resolver diff --git a/src/IronyModManager.Shared/IObjectClone.cs b/src/IronyModManager.Shared/IObjectClone.cs index ed3bb355..51921374 100644 --- a/src/IronyModManager.Shared/IObjectClone.cs +++ b/src/IronyModManager.Shared/IObjectClone.cs @@ -4,13 +4,14 @@ // Created : 05-14-2023 // // Last Modified By : Mario -// Last Modified On : 05-14-2023 +// Last Modified On : 10-30-2024 // *********************************************************************** // // Mario // // // *********************************************************************** + using System; using System.Collections.Generic; using System.Linq; @@ -33,6 +34,14 @@ public interface IObjectClone /// IDefinition. public IDefinition CloneDefinition(IDefinition definition, bool includeCode); + /// + /// Partials the clone definition. + /// + /// The definition. + /// if set to true [copy additional filenames]. + /// IDefinition. + public IDefinition PartialCloneDefinition(IDefinition definition, bool copyAdditionalFilenames = true); + #endregion Methods } } diff --git a/src/IronyModManager.Shared/Models/IDefinition.cs b/src/IronyModManager.Shared/Models/IDefinition.cs index defa4a41..75300594 100644 --- a/src/IronyModManager.Shared/Models/IDefinition.cs +++ b/src/IronyModManager.Shared/Models/IDefinition.cs @@ -4,7 +4,7 @@ // Created : 02-16-2020 // // Last Modified By : Mario -// Last Modified On : 10-17-2024 +// Last Modified On : 10-29-2024 // *********************************************************************** // // Mario @@ -63,6 +63,12 @@ public interface IDefinition : ICEFIndexedListItem, IQueryableModel [JsonIgnore] string CodeTag { get; set; } + /// + /// Gets or sets a value indicating whether [contains inline identifier]. + /// + /// true if [contains inline identifier]; otherwise, false. + bool ContainsInlineIdentifier { get; set; } + /// /// Gets or sets the content sha. ///