From 7ae850f8a0520799ca1bea7da8bc86a44afd2939 Mon Sep 17 00:00:00 2001 From: Christopher Cyclonit Klinge Date: Wed, 7 Jun 2023 08:10:10 +0200 Subject: [PATCH 01/16] Cleaned up existing asset models. --- .../DataArchives/Assets/Models/FertilityAsset.cs | 6 ++++-- AnnoMapEditor/DataArchives/Assets/Models/IslandAsset.cs | 4 ++++ .../DataArchives/Assets/Models/MinimapSceneAsset.cs | 9 ++++----- .../DataArchives/Assets/Models/RandomIslandAsset.cs | 9 ++++----- AnnoMapEditor/DataArchives/Assets/Models/RegionAsset.cs | 7 +++++-- AnnoMapEditor/DataArchives/Assets/Models/SlotAsset.cs | 6 ++++-- .../UI/Controls/Fertilities/FertilityComparer.cs | 4 +--- AnnoMapEditor/UI/Controls/Slots/SlotComparer.cs | 4 +--- 8 files changed, 27 insertions(+), 22 deletions(-) diff --git a/AnnoMapEditor/DataArchives/Assets/Models/FertilityAsset.cs b/AnnoMapEditor/DataArchives/Assets/Models/FertilityAsset.cs index a368d59..3dad8b0 100644 --- a/AnnoMapEditor/DataArchives/Assets/Models/FertilityAsset.cs +++ b/AnnoMapEditor/DataArchives/Assets/Models/FertilityAsset.cs @@ -1,12 +1,14 @@ using AnnoMapEditor.DataArchives.Assets.Attributes; -using System.Windows.Media; using System.Xml.Linq; namespace AnnoMapEditor.DataArchives.Assets.Models { - [AssetTemplate("Fertility")] + [AssetTemplate(TEMPLATE_NAME)] public class FertilityAsset : StandardAsset { + public const string TEMPLATE_NAME = "Fertility"; + + public string DisplayName { get; init; } diff --git a/AnnoMapEditor/DataArchives/Assets/Models/IslandAsset.cs b/AnnoMapEditor/DataArchives/Assets/Models/IslandAsset.cs index c782396..68419a1 100644 --- a/AnnoMapEditor/DataArchives/Assets/Models/IslandAsset.cs +++ b/AnnoMapEditor/DataArchives/Assets/Models/IslandAsset.cs @@ -4,6 +4,10 @@ namespace AnnoMapEditor.DataArchives.Assets.Models { + /// + /// Common base class for RandomIslandAsset and FixedIslandAsset. If an island does not belong + /// to a pool and does not require non-default properties, it may be ommitted from assets.xml. + /// public class IslandAsset { public string DisplayName { get; init; } diff --git a/AnnoMapEditor/DataArchives/Assets/Models/MinimapSceneAsset.cs b/AnnoMapEditor/DataArchives/Assets/Models/MinimapSceneAsset.cs index 40f6cb5..b8b2365 100644 --- a/AnnoMapEditor/DataArchives/Assets/Models/MinimapSceneAsset.cs +++ b/AnnoMapEditor/DataArchives/Assets/Models/MinimapSceneAsset.cs @@ -1,17 +1,16 @@ using AnnoMapEditor.DataArchives.Assets.Attributes; -using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using System.Xml.Linq; namespace AnnoMapEditor.DataArchives.Assets.Models { - [AssetTemplate("MinimapScene")] + [AssetTemplate(TEMPLATE_NAME)] public class MinimapSceneAsset : StandardAsset { - public new const long GUID = 500204; + public const string TEMPLATE_NAME = "MinimapScene"; + + public const long INSTANCE_GUID = 500204; public List FertilityOrderGuids { get; init; } diff --git a/AnnoMapEditor/DataArchives/Assets/Models/RandomIslandAsset.cs b/AnnoMapEditor/DataArchives/Assets/Models/RandomIslandAsset.cs index f167690..354225a 100644 --- a/AnnoMapEditor/DataArchives/Assets/Models/RandomIslandAsset.cs +++ b/AnnoMapEditor/DataArchives/Assets/Models/RandomIslandAsset.cs @@ -3,15 +3,14 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Windows.Media.Imaging; using System.Xml.Linq; namespace AnnoMapEditor.DataArchives.Assets.Models { - [AssetTemplate("RandomIsland")] + [AssetTemplate(TEMPLATE_NAME)] public class RandomIslandAsset : StandardAsset { - public static readonly string TemplateName = "RandomIsland"; + public const string TEMPLATE_NAME = "RandomIsland"; public string FilePath { get; init; } @@ -26,8 +25,8 @@ public class RandomIslandAsset : StandardAsset public RandomIslandAsset(XElement valuesXml) : base(valuesXml) { - XElement randomIslandValues = valuesXml.Element(TemplateName) - ?? throw new Exception($"XML is not a valid {nameof(RandomIslandAsset)}. It does not have '{TemplateName}' section in its values."); + XElement randomIslandValues = valuesXml.Element(TEMPLATE_NAME) + ?? throw new Exception($"XML is not a valid {nameof(RandomIslandAsset)}. It does not have '{TEMPLATE_NAME}' section in its values."); string? regionStr = randomIslandValues.Element(nameof(IslandRegion))?.Value; if (regionStr != null) diff --git a/AnnoMapEditor/DataArchives/Assets/Models/RegionAsset.cs b/AnnoMapEditor/DataArchives/Assets/Models/RegionAsset.cs index 16b1eaa..f0444a8 100644 --- a/AnnoMapEditor/DataArchives/Assets/Models/RegionAsset.cs +++ b/AnnoMapEditor/DataArchives/Assets/Models/RegionAsset.cs @@ -6,9 +6,12 @@ namespace AnnoMapEditor.DataArchives.Assets.Models { - [AssetTemplate("Region")] + [AssetTemplate(TEMPLATE_NAME)] public class RegionAsset : StandardAsset { + public const string TEMPLATE_NAME = "Region"; + + public string DisplayName { get; init; } public string? Ambiente { get; init; } @@ -31,7 +34,7 @@ public RegionAsset(XElement valuesXml) .Value! ?? "Meta"; - XElement regionElement = valuesXml.Element("Region")!; + XElement regionElement = valuesXml.Element(TEMPLATE_NAME)!; Ambiente = regionElement.Element("Ambiente")?.Value; RegionID = regionElement.Element("RegionID")?.Value; diff --git a/AnnoMapEditor/DataArchives/Assets/Models/SlotAsset.cs b/AnnoMapEditor/DataArchives/Assets/Models/SlotAsset.cs index d2d1d52..27a9383 100644 --- a/AnnoMapEditor/DataArchives/Assets/Models/SlotAsset.cs +++ b/AnnoMapEditor/DataArchives/Assets/Models/SlotAsset.cs @@ -7,9 +7,11 @@ namespace AnnoMapEditor.DataArchives.Assets.Models { - [AssetTemplate("Slot")] + [AssetTemplate(TEMPLATE_NAME)] public class SlotAsset : StandardAsset { + public const string TEMPLATE_NAME = "Slot"; + public const long RANDOM_MINE_OLD_WORLD_GUID = 1000029; public const long RANDOM_MINE_NEW_WORLD_GUID = 614; public const long RANDOM_MINE_ARCTIC_GUID = 116037; @@ -50,7 +52,7 @@ public SlotAsset(XElement valuesXml) .Element("Text")! .Value!; - SlotType = valuesXml.Element("Slot")? + SlotType = valuesXml.Element(TEMPLATE_NAME)? .Element("SlotType")? .Value; IsRandomSlot = SlotType == "Random"; diff --git a/AnnoMapEditor/UI/Controls/Fertilities/FertilityComparer.cs b/AnnoMapEditor/UI/Controls/Fertilities/FertilityComparer.cs index 6318ff7..23c47ae 100644 --- a/AnnoMapEditor/UI/Controls/Fertilities/FertilityComparer.cs +++ b/AnnoMapEditor/UI/Controls/Fertilities/FertilityComparer.cs @@ -3,8 +3,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace AnnoMapEditor.UI.Controls.Fertilities { @@ -20,7 +18,7 @@ public class FertilityComparer : IComparer private static readonly Dictionary _orderLookup; static FertilityComparer() { - MinimapSceneAsset minimapScene = Settings.Instance.AssetRepository!.Get(MinimapSceneAsset.GUID); + MinimapSceneAsset minimapScene = Settings.Instance.AssetRepository!.Get(MinimapSceneAsset.INSTANCE_GUID); int index = 0; _orderLookup = minimapScene.FertilityOrderGuids .ToDictionary(f => f, f => index++); diff --git a/AnnoMapEditor/UI/Controls/Slots/SlotComparer.cs b/AnnoMapEditor/UI/Controls/Slots/SlotComparer.cs index bcf0c8c..29e8cd7 100644 --- a/AnnoMapEditor/UI/Controls/Slots/SlotComparer.cs +++ b/AnnoMapEditor/UI/Controls/Slots/SlotComparer.cs @@ -3,8 +3,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace AnnoMapEditor.UI.Controls.Slots { @@ -20,7 +18,7 @@ public class SlotComparer : IComparer private static readonly Dictionary _orderLookup; static SlotComparer() { - MinimapSceneAsset minimapScene = Settings.Instance.AssetRepository!.Get(MinimapSceneAsset.GUID); + MinimapSceneAsset minimapScene = Settings.Instance.AssetRepository!.Get(MinimapSceneAsset.INSTANCE_GUID); int index = 0; _orderLookup = minimapScene.LodesOrderSlotTypes .ToDictionary(f => f, f => index++); From 83d577582e52a283cc9c6d4bf4d6da2f3fd283e8 Mon Sep 17 00:00:00 2001 From: Christopher Cyclonit Klinge Date: Wed, 7 Jun 2023 12:42:25 +0200 Subject: [PATCH 02/16] Add static asset resolution to support hardcoded reference to certain assets. --- .../Assets/Models/MinimapSceneAsset.cs | 5 +++ .../DataArchives/Assets/Models/RegionAsset.cs | 24 ++++++++++++-- .../Assets/Repositories/AssetRepository.cs | 31 +++++++++++++++++-- .../Repositories/StaticAssetAttribute.cs | 15 +++++++++ .../Controls/Fertilities/FertilityComparer.cs | 4 +-- .../UI/Controls/Slots/SlotComparer.cs | 4 +-- 6 files changed, 73 insertions(+), 10 deletions(-) create mode 100644 AnnoMapEditor/DataArchives/Assets/Repositories/StaticAssetAttribute.cs diff --git a/AnnoMapEditor/DataArchives/Assets/Models/MinimapSceneAsset.cs b/AnnoMapEditor/DataArchives/Assets/Models/MinimapSceneAsset.cs index b8b2365..3a89daa 100644 --- a/AnnoMapEditor/DataArchives/Assets/Models/MinimapSceneAsset.cs +++ b/AnnoMapEditor/DataArchives/Assets/Models/MinimapSceneAsset.cs @@ -1,4 +1,5 @@ using AnnoMapEditor.DataArchives.Assets.Attributes; +using AnnoMapEditor.DataArchives.Assets.Repositories; using System.Collections.Generic; using System.Linq; using System.Xml.Linq; @@ -13,6 +14,10 @@ public class MinimapSceneAsset : StandardAsset public const long INSTANCE_GUID = 500204; + [StaticAsset(INSTANCE_GUID)] + public static MinimapSceneAsset Instance { get; set; } + + public List FertilityOrderGuids { get; init; } public List LodesOrderSlotTypes { get; init; } diff --git a/AnnoMapEditor/DataArchives/Assets/Models/RegionAsset.cs b/AnnoMapEditor/DataArchives/Assets/Models/RegionAsset.cs index f0444a8..d464f5c 100644 --- a/AnnoMapEditor/DataArchives/Assets/Models/RegionAsset.cs +++ b/AnnoMapEditor/DataArchives/Assets/Models/RegionAsset.cs @@ -1,4 +1,5 @@ using AnnoMapEditor.DataArchives.Assets.Attributes; +using AnnoMapEditor.DataArchives.Assets.Repositories; using System; using System.Collections.Generic; using System.Linq; @@ -11,6 +12,25 @@ public class RegionAsset : StandardAsset { public const string TEMPLATE_NAME = "Region"; + public const long REGION_MODERATE_GUID = 5000000; + public const long REGION_SOUTHAMERICA_GUID = 5000001; + public const long REGION_ARCTIC_GUID = 160001; + public const long REGION_AFRICA_GUID = 114327; + + [StaticAsset(REGION_MODERATE_GUID)] + public static RegionAsset Moderate { get; private set; } + + [StaticAsset(REGION_SOUTHAMERICA_GUID)] + public static RegionAsset SouthAmerica { get; private set; } + + [StaticAsset(REGION_ARCTIC_GUID)] + public static RegionAsset Arctic { get; private set; } + + [StaticAsset(REGION_AFRICA_GUID)] + public static RegionAsset Africa { get; private set; } + + public static IEnumerable SupportedRegions => new[] { Moderate, SouthAmerica, Arctic, Africa }; + public string DisplayName { get; init; } @@ -22,8 +42,8 @@ public class RegionAsset : StandardAsset [AssetReference(nameof(AllowedFertilityGuids))] public IEnumerable AllowedFertilities { get; init; } - - + + public RegionAsset(XElement valuesXml) : base(valuesXml) { diff --git a/AnnoMapEditor/DataArchives/Assets/Repositories/AssetRepository.cs b/AnnoMapEditor/DataArchives/Assets/Repositories/AssetRepository.cs index 6d6f7a1..9801648 100644 --- a/AnnoMapEditor/DataArchives/Assets/Repositories/AssetRepository.cs +++ b/AnnoMapEditor/DataArchives/Assets/Repositories/AssetRepository.cs @@ -33,6 +33,9 @@ public class AssetRepository : Repository private readonly Dictionary _assets = new(); + private readonly List _assetTypes = new(); + + public AssetRepository(IDataArchive dataArchive) { _dataArchive = dataArchive; @@ -264,6 +267,8 @@ public void Register() resolvers.Add(resolver); } + _assetTypes.Add(typeof(TAsset)); + _logger.LogInformation($"Registered asset type '{typeof(TAsset).FullName}'."); } @@ -295,14 +300,13 @@ private Action CreateSingleReferenceResolver(PropertyInfo refere long? guid = guidProperty.GetValue(asset) as long?; if (guid.HasValue) { - StandardAsset? referencedAsset = GetReferencedAsset((long) guid, referencedType); + StandardAsset? referencedAsset = GetReferencedAsset((long)guid, referencedType); if (referencedAsset != null) referenceProperty.SetValue(asset, referencedAsset); } }; } - private Action CreateEnumerableReferenceResolver(PropertyInfo referenceProperty, PropertyInfo guidProperty) where TAsset : StandardAsset { // determine the type of the referenced assets @@ -338,5 +342,28 @@ private Action CreateEnumerableReferenceResolver(PropertyInfo re } }; } + + private void InitializeStaticAssets() + { + foreach (Type assetType in _assetTypes) + { + foreach (PropertyInfo staticProperty in assetType.GetProperties(BindingFlags.Static | BindingFlags.Public)) + { + StaticAssetAttribute? staticAssetAttribute = staticProperty.GetCustomAttribute(); + if (staticAssetAttribute == null) + continue; + + if (TryGet(staticAssetAttribute.GUID, out StandardAsset? asset)) + { + if (!staticProperty.PropertyType.IsAssignableFrom(asset.GetType())) + throw new Exception($"Could not resolve StaticAsset {assetType.FullName}.{staticProperty.Name}. The asset's type {asset.GetType().FullName} does not match the property's type {staticProperty.PropertyType.FullName}."); + + staticProperty.SetValue(null, asset); + } + else + throw new Exception($"Could not resolve StaticAsset {assetType.FullName}.{staticProperty.Name}. There exists no asset with GUID {staticAssetAttribute.GUID}."); + } + } + } } } diff --git a/AnnoMapEditor/DataArchives/Assets/Repositories/StaticAssetAttribute.cs b/AnnoMapEditor/DataArchives/Assets/Repositories/StaticAssetAttribute.cs new file mode 100644 index 0000000..385fcc5 --- /dev/null +++ b/AnnoMapEditor/DataArchives/Assets/Repositories/StaticAssetAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace AnnoMapEditor.DataArchives.Assets.Repositories +{ + public class StaticAssetAttribute : Attribute + { + public long GUID { get; init; } + + + public StaticAssetAttribute(long guid) + { + GUID = guid; + } + } +} diff --git a/AnnoMapEditor/UI/Controls/Fertilities/FertilityComparer.cs b/AnnoMapEditor/UI/Controls/Fertilities/FertilityComparer.cs index 23c47ae..f1106f9 100644 --- a/AnnoMapEditor/UI/Controls/Fertilities/FertilityComparer.cs +++ b/AnnoMapEditor/UI/Controls/Fertilities/FertilityComparer.cs @@ -1,5 +1,4 @@ using AnnoMapEditor.DataArchives.Assets.Models; -using AnnoMapEditor.Utilities; using System; using System.Collections.Generic; using System.Linq; @@ -18,9 +17,8 @@ public class FertilityComparer : IComparer private static readonly Dictionary _orderLookup; static FertilityComparer() { - MinimapSceneAsset minimapScene = Settings.Instance.AssetRepository!.Get(MinimapSceneAsset.INSTANCE_GUID); int index = 0; - _orderLookup = minimapScene.FertilityOrderGuids + _orderLookup = MinimapSceneAsset.Instance.FertilityOrderGuids .ToDictionary(f => f, f => index++); } diff --git a/AnnoMapEditor/UI/Controls/Slots/SlotComparer.cs b/AnnoMapEditor/UI/Controls/Slots/SlotComparer.cs index 29e8cd7..b8c98e2 100644 --- a/AnnoMapEditor/UI/Controls/Slots/SlotComparer.cs +++ b/AnnoMapEditor/UI/Controls/Slots/SlotComparer.cs @@ -1,5 +1,4 @@ using AnnoMapEditor.DataArchives.Assets.Models; -using AnnoMapEditor.Utilities; using System; using System.Collections.Generic; using System.Linq; @@ -18,9 +17,8 @@ public class SlotComparer : IComparer private static readonly Dictionary _orderLookup; static SlotComparer() { - MinimapSceneAsset minimapScene = Settings.Instance.AssetRepository!.Get(MinimapSceneAsset.INSTANCE_GUID); int index = 0; - _orderLookup = minimapScene.LodesOrderSlotTypes + _orderLookup = MinimapSceneAsset.Instance.LodesOrderSlotTypes .ToDictionary(f => f, f => index++); } From 9a1188049441a10ac4f7616a1645db61fefafd99 Mon Sep 17 00:00:00 2001 From: Christopher Cyclonit Klinge Date: Wed, 7 Jun 2023 17:37:21 +0200 Subject: [PATCH 03/16] Renamed AssetReferenceAttribute to GuidReferenceAttribute for future addition of RegionIdReferenceAttribute. Extracted GuidReferenceResolverFactory from AssetRepository. Moved Ambiente definition to RegionAsset. --- .../AssetTemplateAttribute.cs | 2 +- .../GuidReferenceAttribute.cs} | 6 +- .../GuidReferenceResolverFactory.cs | 129 ++++++++++++++++++ .../Assets/Models/FertilityAsset.cs | 2 +- .../Assets/Models/MinimapSceneAsset.cs | 2 +- .../Assets/Models/RandomIslandAsset.cs | 2 +- .../DataArchives/Assets/Models/RegionAsset.cs | 35 ++++- .../DataArchives/Assets/Models/SlotAsset.cs | 10 +- .../Assets/Models/StandardAsset.cs | 3 +- .../Assets/Repositories/AssetRepository.cs | 92 ++----------- 10 files changed, 183 insertions(+), 100 deletions(-) rename AnnoMapEditor/DataArchives/Assets/{Attributes => Deserialization}/AssetTemplateAttribute.cs (84%) rename AnnoMapEditor/DataArchives/Assets/{Attributes/AssetReferenceAttribute.cs => Deserialization/GuidReferenceAttribute.cs} (54%) create mode 100644 AnnoMapEditor/DataArchives/Assets/Deserialization/GuidReferenceResolverFactory.cs diff --git a/AnnoMapEditor/DataArchives/Assets/Attributes/AssetTemplateAttribute.cs b/AnnoMapEditor/DataArchives/Assets/Deserialization/AssetTemplateAttribute.cs similarity index 84% rename from AnnoMapEditor/DataArchives/Assets/Attributes/AssetTemplateAttribute.cs rename to AnnoMapEditor/DataArchives/Assets/Deserialization/AssetTemplateAttribute.cs index c0e7c4f..4f587bc 100644 --- a/AnnoMapEditor/DataArchives/Assets/Attributes/AssetTemplateAttribute.cs +++ b/AnnoMapEditor/DataArchives/Assets/Deserialization/AssetTemplateAttribute.cs @@ -1,6 +1,6 @@ using System; -namespace AnnoMapEditor.DataArchives.Assets.Attributes +namespace AnnoMapEditor.DataArchives.Assets.Deserialization { [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] public class AssetTemplateAttribute : Attribute diff --git a/AnnoMapEditor/DataArchives/Assets/Attributes/AssetReferenceAttribute.cs b/AnnoMapEditor/DataArchives/Assets/Deserialization/GuidReferenceAttribute.cs similarity index 54% rename from AnnoMapEditor/DataArchives/Assets/Attributes/AssetReferenceAttribute.cs rename to AnnoMapEditor/DataArchives/Assets/Deserialization/GuidReferenceAttribute.cs index 5406265..b0ac97d 100644 --- a/AnnoMapEditor/DataArchives/Assets/Attributes/AssetReferenceAttribute.cs +++ b/AnnoMapEditor/DataArchives/Assets/Deserialization/GuidReferenceAttribute.cs @@ -1,14 +1,14 @@ using System; -namespace AnnoMapEditor.DataArchives.Assets.Attributes +namespace AnnoMapEditor.DataArchives.Assets.Deserialization { [AttributeUsage(AttributeTargets.Property)] - public class AssetReferenceAttribute : Attribute + public class GuidReferenceAttribute : Attribute { public string GuidPropertyName { get; init; } - public AssetReferenceAttribute(string guidPropertyName) + public GuidReferenceAttribute(string guidPropertyName) { GuidPropertyName = guidPropertyName; } diff --git a/AnnoMapEditor/DataArchives/Assets/Deserialization/GuidReferenceResolverFactory.cs b/AnnoMapEditor/DataArchives/Assets/Deserialization/GuidReferenceResolverFactory.cs new file mode 100644 index 0000000..35db63a --- /dev/null +++ b/AnnoMapEditor/DataArchives/Assets/Deserialization/GuidReferenceResolverFactory.cs @@ -0,0 +1,129 @@ +using AnnoMapEditor.DataArchives.Assets.Models; +using AnnoMapEditor.DataArchives.Assets.Repositories; +using AnnoMapEditor.Utilities; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; + +namespace AnnoMapEditor.DataArchives.Assets.Deserialization +{ + public class GuidReferenceResolverFactory + { + private readonly AssetRepository _assetRepository; + + private readonly Logger _logger; + + + public GuidReferenceResolverFactory(AssetRepository assetRepository) + { + _assetRepository = assetRepository; + _logger = new Logger(); + } + + + public Action? CreateResolver(PropertyInfo referenceProperty) + where TAsset : StandardAsset + { + // determine if this property is a reference to another asset + GuidReferenceAttribute? referenceAttribute = referenceProperty.GetCustomAttribute(); + if (referenceAttribute == null) + return null; + + // get the property containing the guid of the referenced asset + PropertyInfo guidProperty = typeof(TAsset).GetProperty(referenceAttribute.GuidPropertyName) + ?? throw new ArgumentException($"Invalid {nameof(GuidReferenceAttribute)} on property {typeof(TAsset).FullName}.{referenceProperty.Name}. Could not find property {referenceAttribute.GuidPropertyName}."); + + // create the resolver + if (guidProperty.PropertyType == typeof(long) || guidProperty.PropertyType == typeof(long?)) + return CreateSingleResolver(referenceProperty, guidProperty); + + else if (guidProperty.PropertyType == typeof(IEnumerable)) + return CreateEnumerableResolver(referenceProperty, guidProperty); + + else + throw new ArgumentException($"Invalid {nameof(GuidReferenceAttribute)} on property {typeof(TAsset).FullName}.{referenceProperty.Name}. Property {referenceAttribute.GuidPropertyName} must either be of type {typeof(long).FullName} or {typeof(IEnumerable).FullName}."); + } + + private Action CreateSingleResolver(PropertyInfo referenceProperty, PropertyInfo guidProperty) + where TAsset : StandardAsset + { + // validate the reference property + Type referencedType = referenceProperty.PropertyType; + if (!typeof(StandardAsset).IsAssignableFrom(referencedType)) + throw new ArgumentException($"Invalid {nameof(GuidReferenceAttribute)} on property {typeof(TAsset).FullName}.{referenceProperty.Name}. The property's type does not extend {typeof(StandardAsset).FullName}."); + + // create the delegate + return (asset) => + { + long? guid = guidProperty.GetValue(asset) as long?; + if (guid.HasValue) + { + if (TryGetReferencedAsset(guid.Value, out StandardAsset? referencedAsset, referencedType)) + referenceProperty.SetValue(asset, referencedAsset); + } + }; + } + + private bool IsValidCollectionReferenceProperty(PropertyInfo referenceProperty) + { + if (!referenceProperty.PropertyType.IsGenericType) + return false; + + // validate the reference property + Type referencedType = referenceProperty.PropertyType.GenericTypeArguments[0]; + if (!typeof(StandardAsset).IsAssignableFrom(referencedType)) + return false; + + Type enumerableType = typeof(ICollection<>).MakeGenericType(referencedType); + if (referenceProperty.PropertyType != enumerableType) + return false; + + return true; + } + + private Action CreateEnumerableResolver(PropertyInfo referenceProperty, PropertyInfo guidProperty) + where TAsset : StandardAsset + { + if (!IsValidCollectionReferenceProperty(referenceProperty)) + throw new ArgumentException($"Invalid {nameof(GuidReferenceAttribute)} on property {typeof(TAsset).FullName}.{referenceProperty.Name}. The property's type does not extend {typeof(ICollection<>)} for matching assets."); + + // create the delegate + Type referencedType = referenceProperty.PropertyType.GenericTypeArguments[0]; + Type listType = typeof(List<>).MakeGenericType(referencedType); + + return (asset) => + { + IEnumerable? guids = guidProperty.GetValue(asset) as IEnumerable; + if (guids != null) + { + IList list = (IList)Activator.CreateInstance(listType)!; + + foreach (long guid in guids) + { + if (TryGetReferencedAsset(guid, out StandardAsset? referencedAsset, referencedType)) + list.Add(referencedAsset); + } + + referenceProperty.SetValue(asset, list); + } + }; + } + + private bool TryGetReferencedAsset(long guid, [MaybeNullWhen(false)] out StandardAsset? referencedAsset, Type referencedType) + { + if (_assetRepository.TryGet(guid, out referencedAsset)) + { + if (referencedType.IsAssignableFrom(referencedAsset!.GetType())) + return true; + else + _logger.LogWarning($"Could not resolve GUID reference to type {referencedType.FullName}. Asset with GUID {guid} has type {referencedAsset.GetType().FullName} instead of expected {referencedType.FullName}."); + } + else + _logger.LogWarning($"Could not resolve GUID reference to type {referencedType.FullName}. No asset with GUID {guid} could be found."); + + return false; + } + } +} \ No newline at end of file diff --git a/AnnoMapEditor/DataArchives/Assets/Models/FertilityAsset.cs b/AnnoMapEditor/DataArchives/Assets/Models/FertilityAsset.cs index 3dad8b0..32a298c 100644 --- a/AnnoMapEditor/DataArchives/Assets/Models/FertilityAsset.cs +++ b/AnnoMapEditor/DataArchives/Assets/Models/FertilityAsset.cs @@ -1,4 +1,4 @@ -using AnnoMapEditor.DataArchives.Assets.Attributes; +using AnnoMapEditor.DataArchives.Assets.Deserialization; using System.Xml.Linq; namespace AnnoMapEditor.DataArchives.Assets.Models diff --git a/AnnoMapEditor/DataArchives/Assets/Models/MinimapSceneAsset.cs b/AnnoMapEditor/DataArchives/Assets/Models/MinimapSceneAsset.cs index 3a89daa..881065b 100644 --- a/AnnoMapEditor/DataArchives/Assets/Models/MinimapSceneAsset.cs +++ b/AnnoMapEditor/DataArchives/Assets/Models/MinimapSceneAsset.cs @@ -1,4 +1,4 @@ -using AnnoMapEditor.DataArchives.Assets.Attributes; +using AnnoMapEditor.DataArchives.Assets.Deserialization; using AnnoMapEditor.DataArchives.Assets.Repositories; using System.Collections.Generic; using System.Linq; diff --git a/AnnoMapEditor/DataArchives/Assets/Models/RandomIslandAsset.cs b/AnnoMapEditor/DataArchives/Assets/Models/RandomIslandAsset.cs index 354225a..1422dc4 100644 --- a/AnnoMapEditor/DataArchives/Assets/Models/RandomIslandAsset.cs +++ b/AnnoMapEditor/DataArchives/Assets/Models/RandomIslandAsset.cs @@ -1,4 +1,4 @@ -using AnnoMapEditor.DataArchives.Assets.Attributes; +using AnnoMapEditor.DataArchives.Assets.Deserialization; using AnnoMapEditor.MapTemplates.Enums; using System; using System.Collections.Generic; diff --git a/AnnoMapEditor/DataArchives/Assets/Models/RegionAsset.cs b/AnnoMapEditor/DataArchives/Assets/Models/RegionAsset.cs index d464f5c..52d0a60 100644 --- a/AnnoMapEditor/DataArchives/Assets/Models/RegionAsset.cs +++ b/AnnoMapEditor/DataArchives/Assets/Models/RegionAsset.cs @@ -1,4 +1,4 @@ -using AnnoMapEditor.DataArchives.Assets.Attributes; +using AnnoMapEditor.DataArchives.Assets.Deserialization; using AnnoMapEditor.DataArchives.Assets.Repositories; using System; using System.Collections.Generic; @@ -17,6 +17,9 @@ public class RegionAsset : StandardAsset public const long REGION_ARCTIC_GUID = 160001; public const long REGION_AFRICA_GUID = 114327; + public const string REGION_MODERATE_REGIONID = "Moderate"; + + [StaticAsset(REGION_MODERATE_GUID)] public static RegionAsset Moderate { get; private set; } @@ -32,15 +35,32 @@ public class RegionAsset : StandardAsset public static IEnumerable SupportedRegions => new[] { Moderate, SouthAmerica, Arctic, Africa }; + /// + /// Each region has its own AmbientName, which is needed when creating the .a7t. These + /// values are missing in assets.xml. The values seen here were reverse engineered from + /// existing a7t files within the game. + /// + /// Note: Region assets do contain an attribute "Ambiente". However its value is always + /// "Region_map_global" and does not match the expected value for a7ts. + /// + private static readonly Dictionary REGION_AMBIENTE_HARDCODED = new Dictionary() + { + [REGION_MODERATE_GUID] = "Moderate_01_day_night", + [REGION_SOUTHAMERICA_GUID] = "south_america_caribic_01", + [REGION_ARCTIC_GUID] = "DLC03_01", + [REGION_AFRICA_GUID] = "Colony_02" + }; + + public string DisplayName { get; init; } public string? Ambiente { get; init; } - public string? RegionID { get; init; } + public string RegionID { get; init; } public IEnumerable AllowedFertilityGuids { get; init; } - [AssetReference(nameof(AllowedFertilityGuids))] + [GuidReference(nameof(AllowedFertilityGuids))] public IEnumerable AllowedFertilities { get; init; } @@ -55,8 +75,13 @@ public RegionAsset(XElement valuesXml) ?? "Meta"; XElement regionElement = valuesXml.Element(TEMPLATE_NAME)!; - Ambiente = regionElement.Element("Ambiente")?.Value; - RegionID = regionElement.Element("RegionID")?.Value; + + if (REGION_AMBIENTE_HARDCODED.ContainsKey(GUID)) + Ambiente = REGION_AMBIENTE_HARDCODED[GUID]; + + // The region Moderate does not have a RegionID specified in assets.xml. All other + // regions have them. + RegionID = regionElement.Element("RegionID")?.Value ?? REGION_MODERATE_REGIONID; AllowedFertilityGuids = regionElement.Element("AllowedFertilities")? .Elements("Item")? diff --git a/AnnoMapEditor/DataArchives/Assets/Models/SlotAsset.cs b/AnnoMapEditor/DataArchives/Assets/Models/SlotAsset.cs index 27a9383..3e5528f 100644 --- a/AnnoMapEditor/DataArchives/Assets/Models/SlotAsset.cs +++ b/AnnoMapEditor/DataArchives/Assets/Models/SlotAsset.cs @@ -1,4 +1,4 @@ -using AnnoMapEditor.DataArchives.Assets.Attributes; +using AnnoMapEditor.DataArchives.Assets.Deserialization; using AnnoMapEditor.MapTemplates.Enums; using System; using System.Collections.Generic; @@ -27,8 +27,8 @@ public class SlotAsset : StandardAsset public IEnumerable ReplacementGuids { get; init; } - [AssetReference(nameof(ReplacementGuids))] - public IEnumerable ReplacementSlotAssets { get; set; } + [GuidReference(nameof(ReplacementGuids))] + public ICollection ReplacementSlotAssets { get; set; } public IEnumerable AssociatedRegions { get; init; } @@ -37,7 +37,7 @@ public SlotAsset() : base() { DisplayName = ""; ReplacementGuids = Enumerable.Empty(); - ReplacementSlotAssets = Enumerable.Empty(); + ReplacementSlotAssets = new SlotAsset[0]; AssociatedRegions = Enumerable.Empty(); } @@ -80,8 +80,6 @@ public SlotAsset(XElement valuesXml) .Select(id => Region.FromRegionId(id)) .ToArray() ?? Array.Empty(); - - ReplacementSlotAssets = Enumerable.Empty(); } } } diff --git a/AnnoMapEditor/DataArchives/Assets/Models/StandardAsset.cs b/AnnoMapEditor/DataArchives/Assets/Models/StandardAsset.cs index 8397dab..56cbffc 100644 --- a/AnnoMapEditor/DataArchives/Assets/Models/StandardAsset.cs +++ b/AnnoMapEditor/DataArchives/Assets/Models/StandardAsset.cs @@ -1,5 +1,4 @@ -using AnnoMapEditor.DataArchives.Assets.Attributes; -using AnnoMapEditor.Utilities; +using AnnoMapEditor.Utilities; using System; using System.Windows.Media; using System.Xml.Linq; diff --git a/AnnoMapEditor/DataArchives/Assets/Repositories/AssetRepository.cs b/AnnoMapEditor/DataArchives/Assets/Repositories/AssetRepository.cs index 9801648..a6004a9 100644 --- a/AnnoMapEditor/DataArchives/Assets/Repositories/AssetRepository.cs +++ b/AnnoMapEditor/DataArchives/Assets/Repositories/AssetRepository.cs @@ -1,4 +1,4 @@ -using AnnoMapEditor.DataArchives.Assets.Attributes; +using AnnoMapEditor.DataArchives.Assets.Deserialization; using AnnoMapEditor.DataArchives.Assets.Models; using AnnoMapEditor.Utilities; using System; @@ -27,6 +27,8 @@ public class AssetRepository : Repository private readonly IDataArchive _dataArchive; + private readonly GuidReferenceResolverFactory _guidReferenceResolverFactory; + private readonly Dictionary> _deserializers = new(); private readonly Dictionary>> _referenceResolvers = new(); @@ -39,6 +41,7 @@ public class AssetRepository : Repository public AssetRepository(IDataArchive dataArchive) { _dataArchive = dataArchive; + _guidReferenceResolverFactory = new(this); } private StandardAsset? ConstructAssetFrom(XElement assetElement) @@ -238,33 +241,18 @@ public void Register() // prepare to resolve references foreach (PropertyInfo property in typeof(TAsset).GetProperties(BindingFlags.Instance | BindingFlags.Public)) { - // determine if this property is a reference to another asset - AssetReferenceAttribute? referenceAttribute = property.GetCustomAttribute(); - if (referenceAttribute == null) - continue; - - // get the property containing the guid of the referenced asset - PropertyInfo guidProperty = typeof(TAsset).GetProperty(referenceAttribute.GuidPropertyName) - ?? throw new ArgumentException($"Invalid {nameof(AssetReferenceAttribute)} on type {typeof(TAsset).FullName}. Could not find property '{referenceAttribute.GuidPropertyName}'."); - - // create the resolver - Action resolver; - if (guidProperty.PropertyType == typeof(long)) - resolver = CreateEnumerableReferenceResolver(property, guidProperty); - - else if (guidProperty.PropertyType == typeof(IEnumerable)) - resolver = CreateEnumerableReferenceResolver(property, guidProperty); - - else - throw new ArgumentException($"Invalid {nameof(AssetReferenceAttribute)} on type {typeof(TAsset).FullName}. Property '{referenceAttribute.GuidPropertyName}' must either be of type {typeof(long).FullName} or {typeof(IEnumerable).FullName}."); + Action? resolver = _guidReferenceResolverFactory.CreateResolver(property); // keep track of all resolvers - if (!_referenceResolvers.TryGetValue(typeof(TAsset), out List>? resolvers)) + if (resolver != null) { - resolvers = new(); - _referenceResolvers.Add(typeof(TAsset), resolvers); + if (!_referenceResolvers.TryGetValue(typeof(TAsset), out List>? resolvers)) + { + resolvers = new(); + _referenceResolvers.Add(typeof(TAsset), resolvers); + } + resolvers.Add(resolver); } - resolvers.Add(resolver); } _assetTypes.Add(typeof(TAsset)); @@ -287,62 +275,6 @@ public void Register() return null; } - private Action CreateSingleReferenceResolver(PropertyInfo referenceProperty, PropertyInfo guidProperty) where TAsset : StandardAsset - { - // validate the reference property - Type referencedType = referenceProperty.PropertyType; - if (!typeof(StandardAsset).IsAssignableFrom(referencedType)) - throw new ArgumentException(); - - // create the delegate - return (asset) => - { - long? guid = guidProperty.GetValue(asset) as long?; - if (guid.HasValue) - { - StandardAsset? referencedAsset = GetReferencedAsset((long)guid, referencedType); - if (referencedAsset != null) - referenceProperty.SetValue(asset, referencedAsset); - } - }; - } - - private Action CreateEnumerableReferenceResolver(PropertyInfo referenceProperty, PropertyInfo guidProperty) where TAsset : StandardAsset - { - // determine the type of the referenced assets - if (!referenceProperty.PropertyType.IsGenericType) - throw new ArgumentException(); - - // validate the reference property - Type referencedType = referenceProperty.PropertyType.GenericTypeArguments[0]; - if (!typeof(StandardAsset).IsAssignableFrom(referencedType)) - throw new ArgumentException(); - - Type enumerableType = typeof(IEnumerable<>).MakeGenericType(referencedType); - if (referenceProperty.PropertyType != enumerableType) - throw new ArgumentException(); - - // create the delegate - Type listType = typeof(List<>).MakeGenericType(referencedType); - return (asset) => - { - IEnumerable? guids = guidProperty.GetValue(asset) as IEnumerable; - if (guids != null) - { - IList list = (IList)Activator.CreateInstance(listType)!; - - foreach (long guid in guids) - { - StandardAsset? referencedAsset = GetReferencedAsset((long)guid, referencedType); - if (referencedAsset != null) - list.Add(referencedAsset); - } - - referenceProperty.SetValue(asset, list); - } - }; - } - private void InitializeStaticAssets() { foreach (Type assetType in _assetTypes) From 46861ee7aa013d9827b9b6a7bd11fd9a1f560401 Mon Sep 17 00:00:00 2001 From: Christopher Cyclonit Klinge Date: Wed, 7 Jun 2023 22:36:48 +0200 Subject: [PATCH 04/16] Replace all uses of Region with RegionAsset or SessionAsset. Change MapTemplate to belong to a SessionAsset instead of RegionAsset. Remove Region. Removed fake MapType for The New World for A future fix. Temporarily disable Mod export for a future fix. --- .../Deserialization/AssetTemplateAttribute.cs | 6 +- .../RegionIdReferenceAttribute.cs | 16 ++ .../RegionIdReferenceResolverFactory.cs | 120 +++++++++++++++ .../DataArchives/Assets/Models/IslandAsset.cs | 2 +- .../Assets/Models/MapTemplateAsset.cs | 46 ++++++ .../Assets/Models/RandomIslandAsset.cs | 24 +-- .../DataArchives/Assets/Models/RegionAsset.cs | 18 ++- .../Assets/Models/SessionAsset.cs | 137 ++++++++++++++++++ .../DataArchives/Assets/Models/SlotAsset.cs | 32 +++- .../Assets/Repositories/AssetRepository.cs | 22 +-- .../Assets/Repositories/IslandRepository.cs | 2 +- AnnoMapEditor/MapTemplates/Enums/Region.cs | 126 ---------------- .../MapTemplates/Models/FixedIslandElement.cs | 2 +- .../MapTemplates/Models/MapTemplate.cs | 24 +-- AnnoMapEditor/MapTemplates/Pool.cs | 41 +++--- .../Serializing/MapTemplateReader.cs | 22 +-- .../Validation/PoolSizeValidator.cs | 2 +- .../Validation/SmallPoolSizeValidator.cs | 7 +- AnnoMapEditor/Mods/Enums/MapType.cs | 33 ++--- AnnoMapEditor/Mods/Models/Mod.cs | 121 ++++++++-------- .../Mods/Serialization/A7tExporter.cs | 9 +- .../Fertilities/FertilitiesViewModel.cs | 5 +- .../FixedIslandPropertiesViewModel.cs | 5 +- .../UI/Controls/MapTemplateProperties.xaml | 11 +- .../MapTemplatePropertiesViewModel.cs | 19 +-- AnnoMapEditor/UI/Controls/MapView.xaml.cs | 2 +- .../UI/Controls/Slots/SlotsViewModel.cs | 5 +- .../ExportAsMod/ExportAsModViewModel.cs | 6 +- .../SelectFertilitiesViewModel.cs | 27 ++-- .../SelectIsland/SelectIslandViewModel.cs | 10 +- .../SelectSlots/SelectSlotsViewModel.cs | 12 +- .../SelectSlots/SlotAssignmentViewModel.cs | 22 ++- .../UI/Windows/Main/MainWindowViewModel.cs | 12 +- AnnoMapEditor/Utilities/Settings.cs | 2 + 34 files changed, 572 insertions(+), 378 deletions(-) create mode 100644 AnnoMapEditor/DataArchives/Assets/Deserialization/RegionIdReferenceAttribute.cs create mode 100644 AnnoMapEditor/DataArchives/Assets/Deserialization/RegionIdReferenceResolverFactory.cs create mode 100644 AnnoMapEditor/DataArchives/Assets/Models/MapTemplateAsset.cs create mode 100644 AnnoMapEditor/DataArchives/Assets/Models/SessionAsset.cs delete mode 100644 AnnoMapEditor/MapTemplates/Enums/Region.cs diff --git a/AnnoMapEditor/DataArchives/Assets/Deserialization/AssetTemplateAttribute.cs b/AnnoMapEditor/DataArchives/Assets/Deserialization/AssetTemplateAttribute.cs index 4f587bc..a8d6e6f 100644 --- a/AnnoMapEditor/DataArchives/Assets/Deserialization/AssetTemplateAttribute.cs +++ b/AnnoMapEditor/DataArchives/Assets/Deserialization/AssetTemplateAttribute.cs @@ -5,12 +5,12 @@ namespace AnnoMapEditor.DataArchives.Assets.Deserialization [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] public class AssetTemplateAttribute : Attribute { - public string TemplateName { get; init; } + public string[] TemplateNames { get; init; } - public AssetTemplateAttribute(string templateName) + public AssetTemplateAttribute(params string[] templateNames) { - TemplateName = templateName; + TemplateNames = templateNames; } } } diff --git a/AnnoMapEditor/DataArchives/Assets/Deserialization/RegionIdReferenceAttribute.cs b/AnnoMapEditor/DataArchives/Assets/Deserialization/RegionIdReferenceAttribute.cs new file mode 100644 index 0000000..3a2ade6 --- /dev/null +++ b/AnnoMapEditor/DataArchives/Assets/Deserialization/RegionIdReferenceAttribute.cs @@ -0,0 +1,16 @@ +using System; + +namespace AnnoMapEditor.DataArchives.Assets.Deserialization +{ + [AttributeUsage(AttributeTargets.Property)] + public class RegionIdReferenceAttribute : Attribute + { + public string RegionIdPropertyName { get; init; } + + + public RegionIdReferenceAttribute(string regionIdPropertyName) + { + RegionIdPropertyName = regionIdPropertyName; + } + } +} \ No newline at end of file diff --git a/AnnoMapEditor/DataArchives/Assets/Deserialization/RegionIdReferenceResolverFactory.cs b/AnnoMapEditor/DataArchives/Assets/Deserialization/RegionIdReferenceResolverFactory.cs new file mode 100644 index 0000000..0461613 --- /dev/null +++ b/AnnoMapEditor/DataArchives/Assets/Deserialization/RegionIdReferenceResolverFactory.cs @@ -0,0 +1,120 @@ +using AnnoMapEditor.DataArchives.Assets.Models; +using AnnoMapEditor.DataArchives.Assets.Repositories; +using AnnoMapEditor.Utilities; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace AnnoMapEditor.DataArchives.Assets.Deserialization +{ + public class RegionIdReferenceResolverFactory + { + private readonly AssetRepository _assetRepository; + + private readonly Logger _logger; + + + public RegionIdReferenceResolverFactory(AssetRepository assetRepository) + { + _assetRepository = assetRepository; + _logger = new Logger(); + } + + + public Action? CreateResolver(PropertyInfo referenceProperty) + where TAsset : StandardAsset + { + // determine if this property is a reference to a RegionAsset + RegionIdReferenceAttribute? referenceAttribute = referenceProperty.GetCustomAttribute(); + if (referenceAttribute == null) + return null; + + // get the property containing the RegionId of the referenced RegionAsset + PropertyInfo regionIdProperty = typeof(TAsset).GetProperty(referenceAttribute.RegionIdPropertyName) + ?? throw new ArgumentException($"Invalid {nameof(RegionIdReferenceAttribute)} on property {typeof(TAsset).FullName}.{referenceProperty.Name}. Could not find property {referenceAttribute.RegionIdPropertyName}."); + + // create the resolver + Action resolver; + if (regionIdProperty.PropertyType == typeof(string)) + return CreateSingleResolver(referenceProperty, regionIdProperty); + + else if (regionIdProperty.PropertyType == typeof(IEnumerable)) + return CreateEnumerableResolver(referenceProperty, regionIdProperty); + + else + throw new ArgumentException($"Invalid {nameof(RegionIdReferenceAttribute)} on property {typeof(TAsset).FullName}.{referenceProperty.Name}. Property {referenceAttribute.RegionIdPropertyName} must either be of type {typeof(string).FullName} or {typeof(IEnumerable).FullName}."); + } + + private Action CreateSingleResolver(PropertyInfo referenceProperty, PropertyInfo regionIdProperty) + where TAsset : StandardAsset + { + // validate the reference property + Type referencedType = referenceProperty.PropertyType; + if (!referencedType.IsAssignableFrom(typeof(RegionAsset))) + throw new ArgumentException($"Invalid {nameof(RegionIdReferenceAttribute)} on property {typeof(TAsset).FullName}.{referenceProperty.Name}. The type {typeof(RegionAsset).FullName} does not extend the property's type."); + + // create the delegate + return (asset) => + { + string? regionId = regionIdProperty.GetValue(asset) as string; + if (regionId != null) + { + RegionAsset? regionAsset = _assetRepository.GetAll().FirstOrDefault(r => r.RegionID == regionId); + if (regionAsset != null) + referenceProperty.SetValue(asset, regionAsset); + else + _logger.LogWarning($"Could not resolve RegionID reference. No RegionAsset with RegionID {regionId} could be found."); + } + }; + } + + private bool IsValidCollectionReferenceProperty(PropertyInfo referenceProperty) + { + if (!referenceProperty.PropertyType.IsGenericType) + return false; + + // validate the reference property + Type referencedType = referenceProperty.PropertyType.GenericTypeArguments[0]; + if (!referencedType.IsAssignableFrom(typeof(RegionAsset))) + return false; + + Type enumerableType = typeof(ICollection<>).MakeGenericType(referencedType); + if (referenceProperty.PropertyType != enumerableType) + return false; + + return true; + } + + private Action CreateEnumerableResolver(PropertyInfo referenceProperty, PropertyInfo regionIdProperty) + where TAsset : StandardAsset + { + if (!IsValidCollectionReferenceProperty(referenceProperty)) + throw new ArgumentException($"Invalid {nameof(RegionIdReferenceResolverFactory)} on property {typeof(TAsset).FullName}.{referenceProperty.Name}. The property's type does not extend {typeof(ICollection<>)} accepting RegionAssets."); + + // create the delegate + Type referencedType = referenceProperty.PropertyType.GenericTypeArguments[0]; + Type listType = typeof(List<>).MakeGenericType(referencedType); + + return (asset) => + { + IEnumerable? regionIds = regionIdProperty.GetValue(asset) as IEnumerable; + if (regionIds != null) + { + List regionAssets = _assetRepository.GetAll().ToList(); + IList list = (IList)Activator.CreateInstance(listType)!; + foreach (string regionId in regionIds) + { + RegionAsset? regionAsset = regionAssets.FirstOrDefault(r => r.RegionID == regionId); + if (regionAsset != null) + list.Add(regionAsset); + else + _logger.LogWarning($"Could not resolve RegionID reference. No RegionAsset with RegionID {regionId} could be found."); + } + referenceProperty.SetValue(asset, list); + } + }; + } + } +} \ No newline at end of file diff --git a/AnnoMapEditor/DataArchives/Assets/Models/IslandAsset.cs b/AnnoMapEditor/DataArchives/Assets/Models/IslandAsset.cs index 68419a1..a293ca1 100644 --- a/AnnoMapEditor/DataArchives/Assets/Models/IslandAsset.cs +++ b/AnnoMapEditor/DataArchives/Assets/Models/IslandAsset.cs @@ -16,7 +16,7 @@ public class IslandAsset public BitmapImage? Thumbnail { get; init; } - public Region Region { get; init; } + public RegionAsset Region { get; init; } public IEnumerable IslandDifficulty { get; init; } diff --git a/AnnoMapEditor/DataArchives/Assets/Models/MapTemplateAsset.cs b/AnnoMapEditor/DataArchives/Assets/Models/MapTemplateAsset.cs new file mode 100644 index 0000000..8c92d95 --- /dev/null +++ b/AnnoMapEditor/DataArchives/Assets/Models/MapTemplateAsset.cs @@ -0,0 +1,46 @@ +using AnnoMapEditor.DataArchives.Assets.Deserialization; +using AnnoMapEditor.Mods.Enums; +using System; +using System.Xml.Linq; + +namespace AnnoMapEditor.DataArchives.Assets.Models +{ + [AssetTemplate(TEMPLATE_NAME)] + public class MapTemplateAsset : StandardAsset + { + public const string TEMPLATE_NAME = "MapTemplate"; + + + public string TemplateFilename { get; init; } + + public string? EnlargedTemplateFilename { get; init; } + + public string TemplateRegionId { get; init; } + + [RegionIdReference(nameof(TemplateRegionId))] + public RegionAsset TemplateRegion { get; set; } + + public MapType? TemplateMapType { get; init; } + + + public MapTemplateAsset(XElement valuesXml) + : base(valuesXml) + { + XElement mapTemplateValues = valuesXml.Element(TEMPLATE_NAME) + ?? throw new Exception($"XML is not a valid {nameof(MapTemplateAsset)}. It does not have '{TEMPLATE_NAME}' section in its values."); + + TemplateFilename = mapTemplateValues.Element(nameof(TemplateFilename))?.Value + ?? throw new Exception($"XML is not a valid {nameof(MapTemplateAsset)}. It does not have '{nameof(TemplateFilename)}' section in its values."); + + EnlargedTemplateFilename = mapTemplateValues.Element(nameof(EnlargedTemplateFilename))?.Value; + + // TemplateRegion defaults to Moderate. If the MapTemplate belongs to another region, + // it must have TemplateRegion set explicitly within assets.xml. + TemplateRegionId = mapTemplateValues.Element(nameof(TemplateRegion))?.Value ?? RegionAsset.REGION_MODERATE_REGIONID; + + string? templateMapTypeStr = mapTemplateValues.Element(nameof(TemplateMapType))?.Value; + if (templateMapTypeStr != null) + TemplateMapType = MapType.FromName(templateMapTypeStr); + } + } +} \ No newline at end of file diff --git a/AnnoMapEditor/DataArchives/Assets/Models/RandomIslandAsset.cs b/AnnoMapEditor/DataArchives/Assets/Models/RandomIslandAsset.cs index 1422dc4..f768688 100644 --- a/AnnoMapEditor/DataArchives/Assets/Models/RandomIslandAsset.cs +++ b/AnnoMapEditor/DataArchives/Assets/Models/RandomIslandAsset.cs @@ -15,7 +15,10 @@ public class RandomIslandAsset : StandardAsset public string FilePath { get; init; } - public Region? IslandRegion { get; init; } + public string IslandRegionId { get; init; } + + [RegionIdReference(nameof(IslandRegionId))] + public RegionAsset IslandRegion { get; init; } public IEnumerable IslandDifficulty { get; init; } @@ -28,9 +31,9 @@ public RandomIslandAsset(XElement valuesXml) XElement randomIslandValues = valuesXml.Element(TEMPLATE_NAME) ?? throw new Exception($"XML is not a valid {nameof(RandomIslandAsset)}. It does not have '{TEMPLATE_NAME}' section in its values."); - string? regionStr = randomIslandValues.Element(nameof(IslandRegion))?.Value; - if (regionStr != null) - IslandRegion = RegionFromName(regionStr); + // IslandRegion defaults to Moderate. If the MapTemplate belongs to another region, + // it must have TemplateRegion set explicitly within assets.xml. + IslandRegionId = randomIslandValues.Element(nameof(IslandRegion))?.Value ?? RegionAsset.REGION_MODERATE_REGIONID; FilePath = randomIslandValues.Element(nameof(FilePath))?.Value ?? throw new Exception($"XML is not a valid {nameof(RandomIslandAsset)}. Required attribute '{nameof(FilePath)}' not found."); @@ -57,18 +60,5 @@ public RandomIslandAsset(XElement valuesXml) else IslandType = Enumerable.Empty(); } - - - private static Region RegionFromName(string name) - { - return name switch - { - "Moderate" => Region.Moderate, - "Colony01" => Region.NewWorld, - "Arctic" => Region.Arctic, - "Africa" => Region.Enbesa, - _ => throw new NotImplementedException() - }; - } } } diff --git a/AnnoMapEditor/DataArchives/Assets/Models/RegionAsset.cs b/AnnoMapEditor/DataArchives/Assets/Models/RegionAsset.cs index 52d0a60..dc38804 100644 --- a/AnnoMapEditor/DataArchives/Assets/Models/RegionAsset.cs +++ b/AnnoMapEditor/DataArchives/Assets/Models/RegionAsset.cs @@ -61,7 +61,7 @@ public class RegionAsset : StandardAsset public IEnumerable AllowedFertilityGuids { get; init; } [GuidReference(nameof(AllowedFertilityGuids))] - public IEnumerable AllowedFertilities { get; init; } + public ICollection AllowedFertilities { get; init; } public RegionAsset(XElement valuesXml) @@ -89,5 +89,21 @@ public RegionAsset(XElement valuesXml) .ToArray() ?? Array.Empty(); } + + + public static RegionAsset DetectFromPath(string filePath) + { + if (filePath.Contains("colony01") || filePath.Contains("ggj") || filePath.Contains("scenario03")) + return SouthAmerica; + else if (filePath.Contains("dlc03") || filePath.Contains("colony_03")) + return Arctic; + else if (filePath.Contains("dlc06") || filePath.Contains("colony02") || filePath.Contains("scenario02")) + return Africa; + else + return Moderate; + } + + + public override string ToString() => DisplayName; } } diff --git a/AnnoMapEditor/DataArchives/Assets/Models/SessionAsset.cs b/AnnoMapEditor/DataArchives/Assets/Models/SessionAsset.cs new file mode 100644 index 0000000..4317305 --- /dev/null +++ b/AnnoMapEditor/DataArchives/Assets/Models/SessionAsset.cs @@ -0,0 +1,137 @@ +using AnnoMapEditor.DataArchives.Assets.Deserialization; +using AnnoMapEditor.DataArchives.Assets.Repositories; +using AnnoMapEditor.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Linq; + +namespace AnnoMapEditor.DataArchives.Assets.Models +{ + [AssetTemplate("SessionModerate", "SessionSouthAmerica", "SessionArctic")] + public class SessionAsset : StandardAsset + { + public const string TEMPLATE_NAME = "Session"; + + public const long SESSION_OLDWORLD_GUID = 180023; + public const long SESSION_NEWWORLD_GUID = 180025; + public const long SESSION_SUNKENTREASURES_GUID = 110934; + public const long SESSION_ARCTIC_GUID = 180045; + public const long SESSION_ENBESA_GUID = 112132; + + + [StaticAsset(SESSION_OLDWORLD_GUID)] + public static SessionAsset OldWorld { get; private set; } + + [StaticAsset(SESSION_NEWWORLD_GUID)] + public static SessionAsset NewWorld { get; private set; } + + [StaticAsset(SESSION_SUNKENTREASURES_GUID)] + public static SessionAsset SunkenTreasures { get; private set; } + + [StaticAsset(SESSION_ARCTIC_GUID)] + public static SessionAsset Arctic { get; private set; } + + [StaticAsset(SESSION_ENBESA_GUID)] + public static SessionAsset Enbesa { get; private set; } + + public static IEnumerable SupportedSessions => new[] { OldWorld, NewWorld, SunkenTreasures, Arctic, Enbesa }; + + + /// + /// The session assets for The Old World and The New World to not properly reference their + /// respective regions in assets.xml. + /// + private static readonly Dictionary REGIONID_HARDCODED = new() + { + [SESSION_OLDWORLD_GUID] = RegionAsset.REGION_MODERATE_GUID, // The Old World => Moderate + [SESSION_NEWWORLD_GUID] = RegionAsset.REGION_SOUTHAMERICA_GUID // The New World => Colony01 + }; + + + public string DisplayName { get; init; } + + public long? MapTemplateGuid { get; init; } + + [GuidReference(nameof(MapTemplateGuid))] + public MapTemplateAsset? MapTemplate { get; set; } + + public long? MapTemplateForMultiplayerGuid { get; init; } + + [GuidReference(nameof(MapTemplateForMultiplayerGuid))] + public MapTemplateAsset? MapTemplateForMultiplayer { get; set; } + + public long RegionGuid { get; init; } + + [GuidReference(nameof(RegionGuid))] + public RegionAsset Region { get; set; } + + + public SessionAsset(XElement valuesXml) + : base(valuesXml) + { + DisplayName = valuesXml.Element("Text")! + .Element("LocaText")? + .Element("English")! + .Element("Text")! + .Value! + ?? "Meta"; + + XElement sessionValues = valuesXml.Element(TEMPLATE_NAME) + ?? throw new Exception($"XML is not a valid {nameof(SessionAsset)}. It does not have '{TEMPLATE_NAME}' section in its values."); + + string? mapTemplateGuidStr = sessionValues.Element(nameof(MapTemplate))?.Value; + if (mapTemplateGuidStr != null) + { + if (long.TryParse(mapTemplateGuidStr, out long mapTemplateGuid)) + MapTemplateGuid = mapTemplateGuid; + else + throw new Exception($"XML is not a valid {nameof(SessionAsset)}. Invalid value '{mapTemplateGuidStr}' for attribute '{nameof(MapTemplate)}'."); + } + + string? mapTemplateForMultiplayerGuidStr = sessionValues.Element(nameof(MapTemplateForMultiplayer))?.Value; + if (mapTemplateForMultiplayerGuidStr != null) + { + if (long.TryParse(mapTemplateForMultiplayerGuidStr, out long mapTemplateForMultiplayerGuid)) + MapTemplateForMultiplayerGuid = mapTemplateForMultiplayerGuid; + else + throw new Exception($"XML is not a valid {nameof(SessionAsset)}. Invalid value '{mapTemplateGuidStr}' for attribute '{nameof(MapTemplateForMultiplayer)}'."); + } + + string? regionGuidStr = sessionValues.Element(nameof(Region))?.Value; + if (regionGuidStr != null) + { + if (long.TryParse(regionGuidStr, out long regionGuid)) + RegionGuid = regionGuid; + else + throw new Exception($"XML is not a valid {nameof(SessionAsset)}. Invalid value '{regionGuidStr}' for attribute '{nameof(Region)}'."); + } + // special handling for The Old World and The New World + else if (REGIONID_HARDCODED.ContainsKey(GUID)) + RegionGuid = REGIONID_HARDCODED[GUID]; + // default to Moderate + else + RegionGuid = RegionAsset.REGION_MODERATE_GUID; + } + + + public static SessionAsset DetectFromPath(string filePath) + { + if (filePath.Contains("colony01") || filePath.Contains("ggj") || filePath.Contains("scenario03")) + return NewWorld; + else if (filePath.Contains("dlc03") || filePath.Contains("colony_03")) + return Arctic; + else if (filePath.Contains("dlc06") || filePath.Contains("colony02") || filePath.Contains("scenario02")) + return Enbesa; + else if (filePath.Contains("sunken_treasures")) + return SunkenTreasures; + else + return OldWorld; + } + + + public override string ToString() => DisplayName; + } +} diff --git a/AnnoMapEditor/DataArchives/Assets/Models/SlotAsset.cs b/AnnoMapEditor/DataArchives/Assets/Models/SlotAsset.cs index 3e5528f..692468a 100644 --- a/AnnoMapEditor/DataArchives/Assets/Models/SlotAsset.cs +++ b/AnnoMapEditor/DataArchives/Assets/Models/SlotAsset.cs @@ -1,5 +1,5 @@ using AnnoMapEditor.DataArchives.Assets.Deserialization; -using AnnoMapEditor.MapTemplates.Enums; +using AnnoMapEditor.DataArchives.Assets.Repositories; using System; using System.Collections.Generic; using System.Linq; @@ -19,6 +19,22 @@ public class SlotAsset : StandardAsset public const long RANDOM_OIL_GUID = 100849; + [StaticAsset(RANDOM_MINE_OLD_WORLD_GUID)] + public static SlotAsset RandomMineOldWorld { get; private set; } + + [StaticAsset(RANDOM_MINE_NEW_WORLD_GUID)] + public static SlotAsset RandomMineNewWorld { get; private set; } + + [StaticAsset(RANDOM_MINE_ARCTIC_GUID)] + public static SlotAsset RandomMineArctic { get; private set; } + + [StaticAsset(RANDOM_CLAY_GUID)] + public static SlotAsset RandomClay { get; private set; } + + [StaticAsset(RANDOM_OIL_GUID)] + public static SlotAsset RandomOil { get; private set; } + + public string DisplayName { get; init; } public string? SlotType { get; init; } @@ -30,15 +46,18 @@ public class SlotAsset : StandardAsset [GuidReference(nameof(ReplacementGuids))] public ICollection ReplacementSlotAssets { get; set; } - public IEnumerable AssociatedRegions { get; init; } + public IEnumerable AssociatedRegionIds { get; init; } + + [RegionIdReference(nameof(AssociatedRegionIds))] + public ICollection AssociatedRegions { get; set; } public SlotAsset() : base() { DisplayName = ""; ReplacementGuids = Enumerable.Empty(); - ReplacementSlotAssets = new SlotAsset[0]; - AssociatedRegions = Enumerable.Empty(); + ReplacementSlotAssets = Array.Empty(); + AssociatedRegions = Array.Empty(); } @@ -73,13 +92,12 @@ public SlotAsset(XElement valuesXml) ReplacementGuids = replacementGuids ?? dlcReplacementGuids ?? Array.Empty(); - AssociatedRegions = valuesXml.Element("Building")? + AssociatedRegionIds = valuesXml.Element("Building")? .Element("AssociatedRegions")? .Value? .Split(';') - .Select(id => Region.FromRegionId(id)) .ToArray() - ?? Array.Empty(); + ?? Array.Empty(); } } } diff --git a/AnnoMapEditor/DataArchives/Assets/Repositories/AssetRepository.cs b/AnnoMapEditor/DataArchives/Assets/Repositories/AssetRepository.cs index a6004a9..d3dfae3 100644 --- a/AnnoMapEditor/DataArchives/Assets/Repositories/AssetRepository.cs +++ b/AnnoMapEditor/DataArchives/Assets/Repositories/AssetRepository.cs @@ -2,7 +2,6 @@ using AnnoMapEditor.DataArchives.Assets.Models; using AnnoMapEditor.Utilities; using System; -using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -19,16 +18,19 @@ namespace AnnoMapEditor.DataArchives.Assets.Repositories { public class AssetRepository : Repository { + private const string ASSETS_XML_PATH = "data/config/export/main/asset/assets.xml"; + + private static readonly Logger _logger = new(); - private const string AssetsXmlPath = "data/config/export/main/asset/assets.xml"; private const string CachedAssetsXml = "assets.cached.xml"; - private readonly IDataArchive _dataArchive; private readonly GuidReferenceResolverFactory _guidReferenceResolverFactory; - + + private readonly RegionIdReferenceResolverFactory _regionIdReferenceResolverFactory; + private readonly Dictionary> _deserializers = new(); private readonly Dictionary>> _referenceResolvers = new(); @@ -42,6 +44,7 @@ public AssetRepository(IDataArchive dataArchive) { _dataArchive = dataArchive; _guidReferenceResolverFactory = new(this); + _regionIdReferenceResolverFactory = new(this); } private StandardAsset? ConstructAssetFrom(XElement assetElement) @@ -107,7 +110,7 @@ protected override async Task DoLoad() // load assets.xml Stopwatch watch = Stopwatch.StartNew(); - Stream assetsXmlStream = _dataArchive.OpenRead(AssetsXmlPath) + Stream assetsXmlStream = _dataArchive.OpenRead(ASSETS_XML_PATH) ?? throw new Exception($"Could not locate assets.xml."); using var hasher = SHA256.Create(); @@ -229,19 +232,20 @@ public void Register() { AssetTemplateAttribute assetTemplateAttribute = typeof(TAsset).GetCustomAttribute() ?? throw new Exception($"Cannot register type '{typeof(TAsset).FullName}' as an asset model, because it lacks the {nameof(AssetTemplateAttribute)}."); - string templateName = assetTemplateAttribute.TemplateName; - + // get the deserializer ConstructorInfo deserializerConstructor = typeof(TAsset).GetConstructor(new[] { typeof(XElement) }) ?? throw new Exception($"Type {typeof(TAsset).FullName} is not a valid asset model. Asset models must have a deserialization constructor."); Func deserializer = (x) => (TAsset)deserializerConstructor.Invoke(new[] { x }); - _deserializers.Add(templateName, deserializer); + foreach (string templateName in assetTemplateAttribute.TemplateNames) + _deserializers.Add(templateName, deserializer); // prepare to resolve references foreach (PropertyInfo property in typeof(TAsset).GetProperties(BindingFlags.Instance | BindingFlags.Public)) { - Action? resolver = _guidReferenceResolverFactory.CreateResolver(property); + Action? resolver = _guidReferenceResolverFactory.CreateResolver(property) + ?? _regionIdReferenceResolverFactory.CreateResolver(property); // keep track of all resolvers if (resolver != null) diff --git a/AnnoMapEditor/DataArchives/Assets/Repositories/IslandRepository.cs b/AnnoMapEditor/DataArchives/Assets/Repositories/IslandRepository.cs index c89fe90..8c0ea43 100644 --- a/AnnoMapEditor/DataArchives/Assets/Repositories/IslandRepository.cs +++ b/AnnoMapEditor/DataArchives/Assets/Repositories/IslandRepository.cs @@ -105,7 +105,7 @@ protected override async Task DoLoad() FilePath = filePath, DisplayName = randomIsland?.Name ?? Path.GetFileNameWithoutExtension(filePath), Thumbnail = fixedIsland.Thumbnail, - Region = randomIsland?.IslandRegion ?? Region.DetectFromPath(filePath), + Region = randomIsland?.IslandRegion ?? RegionAsset.DetectFromPath(filePath), IslandDifficulty = randomIsland?.IslandDifficulty ?? new[] { IslandDifficulty.Normal }, IslandType = randomIsland?.IslandType ?? new[] { DetectIslandTypeFromPath(filePath) }, IslandSize = new[] { islandSize }, diff --git a/AnnoMapEditor/MapTemplates/Enums/Region.cs b/AnnoMapEditor/MapTemplates/Enums/Region.cs deleted file mode 100644 index 25ab007..0000000 --- a/AnnoMapEditor/MapTemplates/Enums/Region.cs +++ /dev/null @@ -1,126 +0,0 @@ -using AnnoMapEditor.Utilities; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; - -namespace AnnoMapEditor.MapTemplates.Enums -{ - public class Region - { - private static readonly Logger _logger = new(); - - - //Technically, Cape Trelawney is in Moderate Region but has ambientName "Moderate_01_day_night_st", - //but we don't allow Mod exports for that anyways - public static readonly Region Moderate = new(5000000, "Moderate", "Moderate", "Moderate_01_day_night", allowModding:true, "moderate", - new[] { "ll", "lm", "ls", "ml", "mm", "ms", "sl", "sm", "ss" }, - new[] { "01", "02" }, - usesAllSizeIndices:false, hasMapExtension:false); - - public static readonly Region NewWorld = new(5000001, "Colony01", "New World", "south_america_caribic_01", allowModding: true, "colony01", - new[] { "s", "m", "l"}, - new[] { "01", "02", "03" }, - usesAllSizeIndices: true, hasMapExtension: true); - - //poolFolderName is manually selected, the game files don't have a special one for the arctic as it only has one map - public static readonly Region Arctic = new(160001, "Arctic", "Arctic", "DLC03_01", allowModding: false, poolFolderName:"colony03", - new[] { "sp" }, - new[] { "" }, - usesAllSizeIndices: true, hasMapExtension: false); - - public static readonly Region Enbesa = new(114327, "Africa", "Enbesa", "Colony_02", allowModding: false, "land_of_lions", - new[] { "01" }, - new[] { "", "mp" }, - usesAllSizeIndices: true, hasMapExtension: false); - - public static readonly Region[] All = new Region[] { Moderate, NewWorld, Arctic, Enbesa }; - - - public long AssetGuid { get; init; } - - public string RegionID { get; init; } - - public string Name { get; init; } - - public string AmbientName { get; init; } - - public bool AllowModding { get; init; } - - public string PoolFolderName { get; init; } - - public IReadOnlyCollection MapSizes { get; init; } - - public IReadOnlyCollection MapSizeIndices { get; init; } - - public bool UsesAllSizeIndices { get; init; } - - public bool HasMapExtension { get; init; } - - - private Region(long assetGuid, string regionId, string name, string ambientName, bool allowModding, string poolFolderName, - string[] mapSizes, string[] sizeIndices, bool usesAllSizeIndices, bool hasMapExtension) - { - AssetGuid = assetGuid; - RegionID = regionId; - Name = name; - AmbientName = ambientName; - AllowModding = allowModding; - - PoolFolderName = poolFolderName; - MapSizes = new ReadOnlyCollection(mapSizes); - MapSizeIndices = new ReadOnlyCollection(sizeIndices); - - UsesAllSizeIndices = usesAllSizeIndices; - HasMapExtension = hasMapExtension; - } - - public IEnumerable GetAllSizeCombinations() - { - if (UsesAllSizeIndices) - { - foreach (string size in MapSizes) - { - foreach (string subsize in MapSizeIndices) - { - string result = size + (string.IsNullOrEmpty(size) || string.IsNullOrEmpty(subsize) ? "" : "_") + subsize; - yield return result; - } - } - } - else - { - foreach (string size in MapSizes) - { - yield return size; - } - } - } - - public static Region DetectFromPath(string filePath) - { - if (filePath.Contains("colony01") || filePath.Contains("ggj") || filePath.Contains("scenario03")) - return NewWorld; - else if (filePath.Contains("dlc03") || filePath.Contains("colony_03")) - return Arctic; - else if (filePath.Contains("dlc06") || filePath.Contains("colony02") || filePath.Contains("scenario02")) - return Enbesa; - return Moderate; - } - - public static Region FromRegionId(string regionId) - { - Region? region = All.FirstOrDefault(t => t.RegionID == regionId); - - if (region is null) - { - _logger.LogWarning($"{regionId} is not a valid RegionID for {nameof(Region)}. Defaulting to {Moderate.RegionID}."); - region = Moderate; - } - - return region; - } - - - public override string ToString() => Name; - } -} diff --git a/AnnoMapEditor/MapTemplates/Models/FixedIslandElement.cs b/AnnoMapEditor/MapTemplates/Models/FixedIslandElement.cs index 0a8f23a..9971bf6 100644 --- a/AnnoMapEditor/MapTemplates/Models/FixedIslandElement.cs +++ b/AnnoMapEditor/MapTemplates/Models/FixedIslandElement.cs @@ -265,7 +265,7 @@ private void SetDummyAsset(Element sourceElement) FilePath = islandFilePath, DisplayName = System.IO.Path.GetFileNameWithoutExtension(islandFilePath), Thumbnail = null, - Region = Region.DetectFromPath(islandFilePath), + Region = RegionAsset.DetectFromPath(islandFilePath), IslandDifficulty = new[] { islandDifficulty }, IslandType = new[] { IslandRepository.DetectIslandTypeFromPath(islandFilePath) }, IslandSize = new[] { islandSize }, diff --git a/AnnoMapEditor/MapTemplates/Models/MapTemplate.cs b/AnnoMapEditor/MapTemplates/Models/MapTemplate.cs index 5c07a01..3f9f092 100644 --- a/AnnoMapEditor/MapTemplates/Models/MapTemplate.cs +++ b/AnnoMapEditor/MapTemplates/Models/MapTemplate.cs @@ -1,5 +1,5 @@ using Anno.FileDBModels.Anno1800.MapTemplate; -using AnnoMapEditor.MapTemplates.Enums; +using AnnoMapEditor.DataArchives.Assets.Models; using AnnoMapEditor.Utilities; using System; using System.Collections.Generic; @@ -27,12 +27,12 @@ public Rect2 PlayableArea } private Rect2 _playableAea = new(); - public Region Region + public SessionAsset Session { - get => _region; - set => SetProperty(ref _region, value); + get => _session; + set => SetProperty(ref _session, value); } - private Region _region; + private SessionAsset _session; public bool ResizingInProgress { @@ -50,15 +50,15 @@ public bool ResizingInProgress public string MapSizeText => $"Size: {Size.X}, Playable: {PlayableArea.Width}"; - public MapTemplate(Region region) + public MapTemplate(SessionAsset session) { - _region = region; + _session = session; _templateDocument = new MapTemplateDocument(); } - public MapTemplate(MapTemplateDocument document, Region region) + public MapTemplate(MapTemplateDocument document, SessionAsset session) { - _region = region; + _session = session; _size = new Vector2(document.MapTemplate?.Size); _playableAea = new Rect2(document.MapTemplate?.PlayableArea); _templateDocument = document; @@ -80,7 +80,7 @@ public MapTemplate(MapTemplateDocument document, Region region) _templateDocument.MapTemplate.TemplateElement = null; } - public MapTemplate(int mapSize, int playableSize, Region region) + public MapTemplate(int mapSize, int playableSize, SessionAsset session) { int margin = (mapSize - playableSize) / 2; @@ -94,7 +94,7 @@ public MapTemplate(int mapSize, int playableSize, Region region) } }; - _region = region; + _session = session; _size = new Vector2(_templateDocument.MapTemplate.Size); _playableAea = new Rect2(_templateDocument.MapTemplate.PlayableArea); @@ -182,7 +182,7 @@ public void ResizeAndCommitMapTemplate(int mapSize, (int x1, int y1, int x2, int _templateDocument.MapTemplate.TemplateElement = new List(Elements.Select(x => x.ToTemplate()).Where(x => x is not null)!); _templateDocument.MapTemplate.ElementCount = _templateDocument.MapTemplate.TemplateElement.Count; - if (Region.HasMapExtension && writeInitialArea) + if (Session == SessionAsset.NewWorld) _templateDocument.MapTemplate.InitialPlayableArea = _templateDocument.MapTemplate.PlayableArea; else _templateDocument.MapTemplate.InitialPlayableArea = null; diff --git a/AnnoMapEditor/MapTemplates/Pool.cs b/AnnoMapEditor/MapTemplates/Pool.cs index 8b77498..41cd44b 100644 --- a/AnnoMapEditor/MapTemplates/Pool.cs +++ b/AnnoMapEditor/MapTemplates/Pool.cs @@ -1,4 +1,5 @@ -using AnnoMapEditor.MapTemplates.Enums; +using AnnoMapEditor.DataArchives.Assets.Models; +using AnnoMapEditor.MapTemplates.Enums; using System; using System.Collections.Generic; @@ -9,13 +10,13 @@ public class Pool public static readonly IEnumerable All = new List() { // Moderate - new(Region.Moderate, IslandSize.Small, + new(RegionAsset.Moderate, IslandSize.Small, "data/sessions/islands/pool/moderate/moderate_s_{0}/moderate_s_{0}.a7m", 12 ), - new(Region.Moderate, IslandSize.Medium, + new(RegionAsset.Moderate, IslandSize.Medium, "data/sessions/islands/pool/moderate/moderate_m_{0}/moderate_m_{0}.a7m", 9 ), - new(Region.Moderate, IslandSize.Large, + new(RegionAsset.Moderate, IslandSize.Large, new FilePathRange[] { new FilePathRange("data/sessions/islands/pool/moderate/moderate_l_{0}/moderate_l_{0}.a7m", 1, 14), @@ -23,19 +24,19 @@ public class Pool }), // NewWorld - new(Region.NewWorld, IslandSize.Small, + new(RegionAsset.SouthAmerica, IslandSize.Small, new FilePathRange[] { new FilePathRange("data/sessions/islands/pool/colony01/colony01_s_{0}/colony01_s_{0}.a7m", 1, 4), new FilePathRange("data/dlc12/sessions/islands/pool/colony01/colony01_s_{0}/colony01_s_{0}.a7m", 5, 3) }), - new(Region.NewWorld, IslandSize.Medium, + new(RegionAsset.SouthAmerica, IslandSize.Medium, new FilePathRange[] { new FilePathRange("data/sessions/islands/pool/colony01/colony01_m_{0}/colony01_m_{0}.a7m", 1, 6), new FilePathRange("data/dlc12/sessions/islands/pool/colony01/colony01_m_{0}/colony01_m_{0}.a7m", 7, 3) }), - new(Region.NewWorld, IslandSize.Large, + new(RegionAsset.SouthAmerica, IslandSize.Large, new FilePathRange[] { new FilePathRange("data/sessions/islands/pool/colony01/colony01_l_{0}/colony01_l_{0}.a7m", 1, 5), @@ -44,17 +45,17 @@ public class Pool }), // Arctic - new(Region.Arctic, IslandSize.Small, "data/dlc03/sessions/islands/pool/colony03_a01_{0}/colony03_a01_{0}.a7m", 8), - new(Region.Arctic, IslandSize.Medium, "data/dlc03/sessions/islands/pool/colony03_a02_{0}/colony03_a02_{0}.a7m", 4), - new(Region.Arctic, IslandSize.Large, "data/dlc03/sessions/islands/pool/moderate/moderate_l_{0}/moderate_l_{0}.a7m", 14), + new(RegionAsset.Arctic, IslandSize.Small, "data/dlc03/sessions/islands/pool/colony03_a01_{0}/colony03_a01_{0}.a7m", 8), + new(RegionAsset.Arctic, IslandSize.Medium, "data/dlc03/sessions/islands/pool/colony03_a02_{0}/colony03_a02_{0}.a7m", 4), + new(RegionAsset.Arctic, IslandSize.Large, "data/dlc03/sessions/islands/pool/moderate/moderate_l_{0}/moderate_l_{0}.a7m", 14), // Enbesa - new(Region.Enbesa, IslandSize.Small, "data/dlc06/sessions/islands/pool/colony02_s_{0}/colony02_s_{0}.a7m", new int[] { 1, 2, 3, 5 }), - new(Region.Enbesa, IslandSize.Medium, "data/dlc06/sessions/islands/pool/colony02_m_{0}/colony02_m_{0}.a7m", new int[] { 2, 4, 5, 9 }), - new(Region.Enbesa, IslandSize.Large, "data/dlc06/sessions/islands/pool/colony02_l_{0}/colony02_l_{0}.a7m", new int[] { 1, 3, 5, 6 }), + new(RegionAsset.Africa, IslandSize.Small, "data/dlc06/sessions/islands/pool/colony02_s_{0}/colony02_s_{0}.a7m", new int[] { 1, 2, 3, 5 }), + new(RegionAsset.Africa, IslandSize.Medium, "data/dlc06/sessions/islands/pool/colony02_m_{0}/colony02_m_{0}.a7m", new int[] { 2, 4, 5, 9 }), + new(RegionAsset.Africa, IslandSize.Large, "data/dlc06/sessions/islands/pool/colony02_l_{0}/colony02_l_{0}.a7m", new int[] { 1, 3, 5, 6 }), }; - private static readonly Dictionary<(Region, IslandSize), Pool> _poolsMap; + private static readonly Dictionary<(RegionAsset, IslandSize), Pool> _poolsMap; static Pool() { _poolsMap = new(); foreach (Pool pool in All) @@ -64,17 +65,17 @@ static Pool() { } - public readonly Region Region; + public readonly RegionAsset Region; public readonly IslandSize IslandSize; - public static Pool GetPool(Region region, IslandSize islandSize) + public static Pool GetPool(RegionAsset region, IslandSize islandSize) { return _poolsMap[(region, islandSize)]; } - public static string GetRandomIslandPath(Region region, IslandSize islandSize) + public static string GetRandomIslandPath(RegionAsset region, IslandSize islandSize) { // use a random Small island for IslandSize.Default if (islandSize == IslandSize.Default) @@ -119,14 +120,14 @@ public string GetPath(int i) } - public Pool(Region region, IslandSize islandSize, FilePathRange[] paths) + public Pool(RegionAsset region, IslandSize islandSize, FilePathRange[] paths) { Region = region; IslandSize = islandSize; _paths = paths; } - public Pool(Region region, IslandSize islandSize, string filePath, int size) + public Pool(RegionAsset region, IslandSize islandSize, string filePath, int size) : this(region, islandSize, new FilePathRange[] { new FilePathRange(filePath, 1, size) @@ -134,7 +135,7 @@ public Pool(Region region, IslandSize islandSize, string filePath, int size) { } - public Pool(Region region, IslandSize islandSize, string filePath, int[] ids) + public Pool(RegionAsset region, IslandSize islandSize, string filePath, int[] ids) : this(region, islandSize, new FilePathRange[] { new FilePathRange(filePath, ids) diff --git a/AnnoMapEditor/MapTemplates/Serializing/MapTemplateReader.cs b/AnnoMapEditor/MapTemplates/Serializing/MapTemplateReader.cs index 8489cc3..8da09c9 100644 --- a/AnnoMapEditor/MapTemplates/Serializing/MapTemplateReader.cs +++ b/AnnoMapEditor/MapTemplates/Serializing/MapTemplateReader.cs @@ -1,5 +1,5 @@ using Anno.FileDBModels.Anno1800.MapTemplate; -using AnnoMapEditor.MapTemplates.Enums; +using AnnoMapEditor.DataArchives.Assets.Models; using AnnoMapEditor.Utilities; using System; using System.IO; @@ -12,11 +12,11 @@ public class MapTemplateReader { public async Task FromDataArchiveAsync(string a7tinfoPath) { - Region region = Region.DetectFromPath(a7tinfoPath); + SessionAsset session = SessionAsset.DetectFromPath(a7tinfoPath); Stream a7tinfoStream = Settings.Instance!.DataArchive.OpenRead(a7tinfoPath) ?? throw new FileNotFoundException($"Could not find file \"{a7tinfoPath}\" in DataArchive."); - return await FromBinaryStreamAsync(region, a7tinfoStream); + return await FromBinaryStreamAsync(session, a7tinfoStream); } public async Task FromFileAsync(string filePath) @@ -32,34 +32,34 @@ public async Task FromFileAsync(string filePath) public async Task FromXmlFileAsync(string filePath) { - Region region = Region.DetectFromPath(filePath); + SessionAsset session = SessionAsset.DetectFromPath(filePath); Stream a7tinfoXmlStream = File.OpenRead(filePath); - return await FromXmlStreamAsync(region, a7tinfoXmlStream); + return await FromXmlStreamAsync(session, a7tinfoXmlStream); } public async Task FromBinaryFileAsync(string filePath) { - Region region = Region.DetectFromPath(filePath); + SessionAsset session = SessionAsset.DetectFromPath(filePath); Stream a7tinfoStream = File.OpenRead(filePath); - return await FromBinaryStreamAsync(region, a7tinfoStream); + return await FromBinaryStreamAsync(session, a7tinfoStream); } - public async Task FromBinaryStreamAsync(Region region, Stream a7tinfoStream) + public async Task FromBinaryStreamAsync(SessionAsset session, Stream a7tinfoStream) { var doc = await FileDBSerializer.ReadAsync(a7tinfoStream); if (doc is null) throw new Exception($"Could not read MapTemplate from binary stream."); - return new MapTemplate(doc, region); + return new MapTemplate(doc, session); } - public async Task FromXmlStreamAsync(Region region, Stream a7tinfoXmlStream) + public async Task FromXmlStreamAsync(SessionAsset session, Stream a7tinfoXmlStream) { var doc = await FileDBSerializer.ReadFromXmlAsync(a7tinfoXmlStream); if (doc is null) throw new Exception($"Could not read MapTemplate from XML stream."); - return new MapTemplate(doc, region); + return new MapTemplate(doc, session); } } } diff --git a/AnnoMapEditor/MapTemplates/Validation/PoolSizeValidator.cs b/AnnoMapEditor/MapTemplates/Validation/PoolSizeValidator.cs index 277ab6a..d55c1b1 100644 --- a/AnnoMapEditor/MapTemplates/Validation/PoolSizeValidator.cs +++ b/AnnoMapEditor/MapTemplates/Validation/PoolSizeValidator.cs @@ -26,7 +26,7 @@ public MapTemplateValidatorResult Validate(MapTemplate mapTemplate) } } - int maxPoolSize = Pool.GetPool(mapTemplate.Region, _islandSize).Size; + int maxPoolSize = Pool.GetPool(mapTemplate.Session.Region, _islandSize).Size; if (islandCount <= maxPoolSize) return MapTemplateValidatorResult.Ok; else diff --git a/AnnoMapEditor/MapTemplates/Validation/SmallPoolSizeValidator.cs b/AnnoMapEditor/MapTemplates/Validation/SmallPoolSizeValidator.cs index cad0898..3250a6c 100644 --- a/AnnoMapEditor/MapTemplates/Validation/SmallPoolSizeValidator.cs +++ b/AnnoMapEditor/MapTemplates/Validation/SmallPoolSizeValidator.cs @@ -1,4 +1,5 @@ -using AnnoMapEditor.MapTemplates.Enums; +using AnnoMapEditor.DataArchives.Assets.Models; +using AnnoMapEditor.MapTemplates.Enums; using AnnoMapEditor.MapTemplates.Models; namespace AnnoMapEditor.MapTemplates.Validation @@ -27,14 +28,14 @@ public MapTemplateValidatorResult Validate(MapTemplate mapTemplate) } // subtract Archibald / Nate / Isabel from the counter - if ((mapTemplate.Region == Region.Moderate || mapTemplate.Region == Region.NewWorld) && thirdPartyCount > 0) + if ((mapTemplate.Session == SessionAsset.OldWorld || mapTemplate.Session == SessionAsset.NewWorld || mapTemplate.Session == SessionAsset.SunkenTreasures) && thirdPartyCount > 0) --smallIslandCount; // subtract all but one pirate island from the counter if (pirateCount > 0) smallIslandCount -= pirateCount - 1; - int maxPoolSize = Pool.GetPool(mapTemplate.Region, IslandSize.Small).Size; + int maxPoolSize = Pool.GetPool(mapTemplate.Session.Region, IslandSize.Small).Size; if (smallIslandCount <= maxPoolSize) return MapTemplateValidatorResult.Ok; else diff --git a/AnnoMapEditor/Mods/Enums/MapType.cs b/AnnoMapEditor/Mods/Enums/MapType.cs index 96e5879..5560d14 100644 --- a/AnnoMapEditor/Mods/Enums/MapType.cs +++ b/AnnoMapEditor/Mods/Enums/MapType.cs @@ -1,5 +1,4 @@ -using AnnoMapEditor.MapTemplates.Enums; -using AnnoMapEditor.Utilities; +using AnnoMapEditor.Utilities; using System.Collections.Generic; using System.Linq; @@ -9,29 +8,16 @@ public class MapType { private static readonly Logger _logger = new(); - //Old World - public static readonly MapType Archipelago = new("Archipelago", guid: "17079", assetName: "moderate_archipel"); - public static readonly MapType Atoll = new("Atoll", guid: "17080", assetName: "moderate_atoll"); - public static readonly MapType Corners = new("Corners", guid: "17082", assetName: "moderate_corners"); - public static readonly MapType IslandArc = new("Island Arc", guid: "17081", assetName: "moderate_islandarc", templateType: "Arc"); - public static readonly MapType Snowflake = new("Snowflake", guid: "17083", assetName: "moderate_snowflake"); + public static readonly MapType Archipelago = new("Archipelago", "Archipelago", guid: "17079", assetName: "moderate_archipel"); + public static readonly MapType Atoll = new("Atoll", "Atoll", guid: "17080", assetName: "moderate_atoll"); + public static readonly MapType Corners = new("Corners", "Corners", guid: "17082", assetName: "moderate_corners"); + public static readonly MapType IslandArc = new("Island Arc", "Arc", guid: "17081", assetName: "moderate_islandarc", templateType: "Arc"); + public static readonly MapType Snowflake = new("Snowflake", "Snowflake", guid: "17083", assetName: "moderate_snowflake"); - //New World - public static readonly MapType Colony01 = new("New World", guid: "", assetName: "SouthAmerica", ' ', fileNamePart: "colony01"); - - //Lists - public static readonly IEnumerable OldWorld = new[] { Archipelago, Atoll, Corners, IslandArc, Snowflake }; - public static readonly IEnumerable NewWorld = new[] { Colony01 }; - public static readonly IEnumerable All = new[] { Archipelago, Atoll, Corners, IslandArc, Snowflake, Colony01 }; - - //Region/MapType Dictionary - public static readonly Dictionary> MapTypesForRegion = new() - { - [Region.Moderate] = OldWorld, - [Region.NewWorld] = NewWorld - }; + public static readonly IEnumerable All = new[] { Archipelago, Atoll, Corners, IslandArc, Snowflake }; + public readonly string DisplayName; public readonly string Name; public readonly string Guid; public readonly string AssetName; @@ -40,8 +26,9 @@ public class MapType public readonly string TemplateType; - private MapType(string name, string guid, string assetName, char assetNameSeparator = '_', string? fileNamePart = null, string? templateType = null) + private MapType(string displayName, string name, string guid, string assetName, char assetNameSeparator = '_', string? fileNamePart = null, string? templateType = null) { + DisplayName = displayName; Name = name; Guid = guid; AssetName = assetName; diff --git a/AnnoMapEditor/Mods/Models/Mod.cs b/AnnoMapEditor/Mods/Models/Mod.cs index 1302ffc..4cf211a 100644 --- a/AnnoMapEditor/Mods/Models/Mod.cs +++ b/AnnoMapEditor/Mods/Models/Mod.cs @@ -1,4 +1,4 @@ -using AnnoMapEditor.MapTemplates.Enums; +using AnnoMapEditor.DataArchives.Assets.Models; using AnnoMapEditor.MapTemplates.Models; using AnnoMapEditor.MapTemplates.Serializing; using AnnoMapEditor.Mods.Enums; @@ -46,9 +46,11 @@ public Mod(MapTemplate mapTemplate, MapType? mapType) public async Task Save(string modPath, string modName, string? modID) { + throw new NotImplementedException($"Mod export is currently broken!"); + string fullModName = "[Map] " + modName; - List sizes = MapTemplate.Region.GetAllSizeCombinations().ToList(); + List sizes = new List(); // MapTemplate.Session.GetAllSizeCombinations().ToList(); string size = sizes[0]; try @@ -59,9 +61,6 @@ public async Task Save(string modPath, string modName, string? modID) if (mapTypeFileName is null || mapTypeGuid is null) throw new Exception("invalid MapType"); - if(!MapTemplate.Region.AllowModding) - throw new Exception("not supported map region"); - FileUtils.TryDeleteDirectory(modPath); Directory.CreateDirectory(modPath); @@ -70,23 +69,24 @@ public async Task Save(string modPath, string modName, string? modID) await WriteLanguageXml(modPath, modName, mapTypeGuid); // write meta JSON, assets XML, .a7tinfo, .a7t and .a7te - string mapFilePath = Path.Combine(AME_POOL_PATH, MapTemplate.Region.PoolFolderName, mapTypeFileName); + string poolFolderName = Regex.Replace(MapTemplate.Session.DisplayName, @"\s+", "_"); + string mapFilePath = Path.Combine(AME_POOL_PATH, poolFolderName, mapTypeFileName); string a7tBasePath = Path.Combine(modPath, $"{mapFilePath}"); string a7tInfoPath = a7tBasePath + $"_{size}.a7tinfo"; string a7tPath = a7tBasePath + $"_{size}.a7t"; string a7tePath = a7tBasePath + $"_{size}.a7te"; await WriteMetaJson(modPath, modName, modID); - await WriteAssetsXml(modPath, fullModName, mapFilePath, MapTemplate.Region, MapType); + await WriteAssetsXml(modPath, mapFilePath, MapTemplate.Session.Region, MapType); MapTemplateWriter mapTemplateWriter = new(); await mapTemplateWriter.ToA7tinfoAsync(MapTemplate, a7tBasePath + $"_{size}.a7tinfo"); (int x, int y, int size) playableArea = (MapTemplate.PlayableArea.X, MapTemplate.PlayableArea.Y, MapTemplate.PlayableArea.Width); - await Task.Run(() => new A7tExporter(MapTemplate.Size.X, playableArea, MapTemplate.Region).ExportA7T(a7tPath)); + await Task.Run(() => new A7tExporter(MapTemplate.Size.X, playableArea, MapTemplate.Session.Region).ExportA7T(a7tPath)); await Task.Run(() => new A7teExporter(MapTemplate.Size.X).ExportA7te(a7tePath)); - if (MapTemplate.Region.HasMapExtension) + if (MapTemplate.Session == SessionAsset.NewWorld) { File.Copy(a7tInfoPath, a7tBasePath + $"_{size}_enlarged.a7tinfo"); File.Copy(a7tPath, a7tBasePath + $"_{size}_enlarged.a7t"); @@ -102,7 +102,7 @@ public async Task Save(string modPath, string modName, string? modID) File.Copy(a7tPath, a7tBasePath + $"_{size}.a7t"); File.Copy(a7tePath, a7tBasePath + $"_{size}.a7te"); - if (MapTemplate.Region.HasMapExtension) + if (MapTemplate.Session == SessionAsset.NewWorld) { File.Copy(a7tInfoPath, a7tBasePath + $"_{size}_enlarged.a7tinfo"); File.Copy(a7tPath, a7tBasePath + $"_{size}_enlarged.a7t"); @@ -124,15 +124,6 @@ public async Task Save(string modPath, string modName, string? modID) return true; } - - public static bool CanSave(MapTemplate? mapTemplate) - { - if (mapTemplate is null) - return false; - - return mapTemplate.Region.AllowModding; - } - private static async Task WriteMetaJson(string modPath, string modName, string? modID) { Modinfo? modinfo; @@ -192,7 +183,7 @@ private static async Task WriteLanguageXml(string modPath, string name, string g } } - private static async Task WriteAssetsXml(string modPath, string fullModName, string mapFilePath, Region region, MapType mapType) + private static async Task WriteAssetsXml(string modPath, string mapFilePath, RegionAsset region, MapType mapType) { string assetsXmlPath = Path.Combine(modPath, @"data\config\export\main\asset\assets.xml"); string? assetsXmlDir = Path.GetDirectoryName(assetsXmlPath); @@ -205,51 +196,53 @@ private static async Task WriteAssetsXml(string modPath, string fullModName, str await writer.WriteAsync(content); } - public static string CreateAssetsModOps(Region region, MapType mapType, string fullMapPath) + public static string CreateAssetsModOps(RegionAsset region, MapType mapType, string fullMapPath) { - IEnumerable sizes = region.MapSizes; - // some maps have updates sizes, but make sure to only replace one - IEnumerable subSizes = region.MapSizeIndices; - - static string MakeXPath(string mapTypeName, string size, string subsize) - { - if (string.IsNullOrEmpty(subsize)) - return $"../Standard/Name='{mapTypeName}{size}'"; - else - return $"../Standard/Name='{mapTypeName}{size}_{subsize}'"; - } - - string content = "\n"; - - if (region.UsesAllSizeIndices) - { - //Single ModOp for all Sub-Sizes - foreach (var size in sizes) - { - foreach(var subsize in subSizes) - { - content += CreateModOp( - new string[] { MakeXPath(mapType.ToName(), size, subsize) }, - fullMapPath, - $"{size}_{subsize}", - region.HasMapExtension); - } - } - } - else - { - //XPath OR - foreach (var size in sizes) - { - var xpaths = subSizes.Select(x => MakeXPath(mapType.ToName(), size, x)); - - content += CreateModOp(xpaths, fullMapPath, size, region.HasMapExtension); - } - } - - - content += "\n"; - return content; + throw new NotImplementedException($"Mod export is currently broken!"); + +// IEnumerable sizes = region.MapSizes; +// // some maps have updates sizes, but make sure to only replace one +// IEnumerable subSizes = region.MapSizeIndices; +// +// static string MakeXPath(string mapTypeName, string size, string subsize) +// { +// if (string.IsNullOrEmpty(subsize)) +// return $"../Standard/Name='{mapTypeName}{size}'"; +// else +// return $"../Standard/Name='{mapTypeName}{size}_{subsize}'"; +// } +// +// string content = "\n"; +// +// if (region.UsesAllSizeIndices) +// { +// //Single ModOp for all Sub-Sizes +// foreach (var size in sizes) +// { +// foreach(var subsize in subSizes) +// { +// content += CreateModOp( +// new string[] { MakeXPath(mapType.ToName(), size, subsize) }, +// fullMapPath, +// $"{size}_{subsize}", +// region.HasMapExtension); +// } +// } +// } +// else +// { +// //XPath OR +// foreach (var size in sizes) +// { +// var xpaths = subSizes.Select(x => MakeXPath(mapType.ToName(), size, x)); +// +// content += CreateModOp(xpaths, fullMapPath, size, region.HasMapExtension); +// } +// } +// +// +// content += "\n"; +// return content; } private static string CreateModOp(IEnumerable xPaths, string basePath, string size, bool extension) diff --git a/AnnoMapEditor/Mods/Serialization/A7tExporter.cs b/AnnoMapEditor/Mods/Serialization/A7tExporter.cs index 4ea7605..5726a26 100644 --- a/AnnoMapEditor/Mods/Serialization/A7tExporter.cs +++ b/AnnoMapEditor/Mods/Serialization/A7tExporter.cs @@ -1,6 +1,5 @@ using Anno.FileDBModels.Anno1800.Gamedata.Models.Shared; -using AnnoMapEditor.MapTemplates; -using AnnoMapEditor.MapTemplates.Enums; +using AnnoMapEditor.DataArchives.Assets.Models; using FileDBSerializing; using FileDBSerializing.ObjectSerializer; using RDAExplorer; @@ -16,10 +15,10 @@ public class A7tExporter private readonly (int x, int y, int size) _playableArea; - private readonly Region _mapRegion; + private readonly RegionAsset _mapRegion; - public A7tExporter(int mapSize, (int x, int y, int size) playableArea, Region mapRegion) + public A7tExporter(int mapSize, (int x, int y, int size) playableArea, RegionAsset mapRegion) { _mapSize = mapSize; _playableArea = playableArea; @@ -32,7 +31,7 @@ public void ExportA7T(string a7tPath) using MemoryStream nestedDataStream = new(); //Create actual a7t File - Gamedata gameDataItem = new(_mapSize, _playableArea, _mapRegion.AmbientName, true); + Gamedata gameDataItem = new(_mapSize, _playableArea, _mapRegion.Ambiente!, true); FileDBDocumentSerializer serializer = new(new() { Version = FileDBDocumentVersion.Version1 }); IFileDBDocument generatedFileDB = serializer.WriteObjectStructureToFileDBDocument(gameDataItem); diff --git a/AnnoMapEditor/UI/Controls/Fertilities/FertilitiesViewModel.cs b/AnnoMapEditor/UI/Controls/Fertilities/FertilitiesViewModel.cs index ab2806e..371fdfb 100644 --- a/AnnoMapEditor/UI/Controls/Fertilities/FertilitiesViewModel.cs +++ b/AnnoMapEditor/UI/Controls/Fertilities/FertilitiesViewModel.cs @@ -1,5 +1,4 @@ using AnnoMapEditor.DataArchives.Assets.Models; -using AnnoMapEditor.MapTemplates.Enums; using AnnoMapEditor.MapTemplates.Models; using AnnoMapEditor.UI.Overlays; using AnnoMapEditor.UI.Overlays.SelectFertilities; @@ -14,12 +13,12 @@ public class FertilitiesViewModel { public FixedIslandElement FixedIsland { get; init; } - public Region Region { get; init; } + public RegionAsset Region { get; init; } public ObservableCollection Fertilities { get; init; } = new(); - public FertilitiesViewModel(FixedIslandElement fixedIsland, Region region) + public FertilitiesViewModel(FixedIslandElement fixedIsland, RegionAsset region) { FixedIsland = fixedIsland; Region = region; diff --git a/AnnoMapEditor/UI/Controls/IslandProperties/FixedIslandPropertiesViewModel.cs b/AnnoMapEditor/UI/Controls/IslandProperties/FixedIslandPropertiesViewModel.cs index e2817d9..b824052 100644 --- a/AnnoMapEditor/UI/Controls/IslandProperties/FixedIslandPropertiesViewModel.cs +++ b/AnnoMapEditor/UI/Controls/IslandProperties/FixedIslandPropertiesViewModel.cs @@ -1,4 +1,5 @@ -using AnnoMapEditor.MapTemplates.Enums; +using AnnoMapEditor.DataArchives.Assets.Models; +using AnnoMapEditor.MapTemplates.Enums; using AnnoMapEditor.MapTemplates.Models; using AnnoMapEditor.UI.Controls.Fertilities; using AnnoMapEditor.UI.Controls.Slots; @@ -23,7 +24,7 @@ public class FixedIslandPropertiesViewModel : ObservableBase public SlotsViewModel SlotsViewModel { get; init; } - public FixedIslandPropertiesViewModel(FixedIslandElement fixedIsland, Region region) + public FixedIslandPropertiesViewModel(FixedIslandElement fixedIsland, RegionAsset region) { FixedIsland = fixedIsland; FertilitiesViewModel = new(fixedIsland, region); diff --git a/AnnoMapEditor/UI/Controls/MapTemplateProperties.xaml b/AnnoMapEditor/UI/Controls/MapTemplateProperties.xaml index df8a43b..1edb85f 100644 --- a/AnnoMapEditor/UI/Controls/MapTemplateProperties.xaml +++ b/AnnoMapEditor/UI/Controls/MapTemplateProperties.xaml @@ -28,11 +28,16 @@ + ItemsSource="{Binding SupportedSessions}" + SelectedItem="{Binding SelectedSession}"> - + + + + diff --git a/AnnoMapEditor/UI/Controls/MapTemplatePropertiesViewModel.cs b/AnnoMapEditor/UI/Controls/MapTemplatePropertiesViewModel.cs index 10b6748..2f93919 100644 --- a/AnnoMapEditor/UI/Controls/MapTemplatePropertiesViewModel.cs +++ b/AnnoMapEditor/UI/Controls/MapTemplatePropertiesViewModel.cs @@ -1,7 +1,8 @@ -using AnnoMapEditor.MapTemplates.Enums; +using AnnoMapEditor.DataArchives.Assets.Models; using AnnoMapEditor.MapTemplates.Models; using AnnoMapEditor.Utilities; using System; +using System.Collections.Generic; namespace AnnoMapEditor.UI.Controls { @@ -19,20 +20,20 @@ public string MapSizeText } private string _mapSizeText = ""; - public Region SelectedRegion + public SessionAsset SelectedSession { - get => _selectedRegion; + get => _selectedSession; set { - if (value != _selectedRegion) + if (value != _selectedSession) { - _selectedRegion = value; - _mapTemplate.Region = value; + _selectedSession = value; + _mapTemplate.Session = value; OnSelectedRegionChanged(); } } } - private Region _selectedRegion; + private SessionAsset _selectedSession; public event EventHandler? SelectedRegionChanged; @@ -63,7 +64,7 @@ private void ResizeMapTemplateValues() _mapTemplate.PlayableArea.Y + _mapTemplate.PlayableArea.Height); } - public Region[] Regions { get; } = Region.All; + public IEnumerable SupportedSessions { get; } = SessionAsset.SupportedSessions; public int MapSize { @@ -117,7 +118,7 @@ public MapTemplatePropertiesViewModel(MapTemplate mapTemplate) _mapTemplate = mapTemplate; _mapTemplate.MapSizeConfigCommitted += HandleMapTemplateSizeCommitted; - _selectedRegion = _mapTemplate.Region; + _selectedSession = _mapTemplate.Session; MapSize = mapTemplate.Size.X; diff --git a/AnnoMapEditor/UI/Controls/MapView.xaml.cs b/AnnoMapEditor/UI/Controls/MapView.xaml.cs index 131a3a7..fc61c58 100644 --- a/AnnoMapEditor/UI/Controls/MapView.xaml.cs +++ b/AnnoMapEditor/UI/Controls/MapView.xaml.cs @@ -354,7 +354,7 @@ private void ProtoIsland_DragEnded(object? sender, DragEndedEventArgs e) // if it is a FixedIsland, let the user select the correct island if (protoIslandViewModel.MapElementType == MapElementType.FixedIsland) { - SelectIslandViewModel selectIslandViewModel = new(_mapTemplate.Region, protoIslandViewModel.Island.IslandType, protoIslandViewModel.IslandSize); + SelectIslandViewModel selectIslandViewModel = new(_mapTemplate.Session.Region, protoIslandViewModel.Island.IslandType, protoIslandViewModel.IslandSize); selectIslandViewModel.IslandSelected += (s, e) => SelectIsland_IslandSelected(s, e, protoIslandViewModel.Island.Position); OverlayService.Instance.Show(selectIslandViewModel); diff --git a/AnnoMapEditor/UI/Controls/Slots/SlotsViewModel.cs b/AnnoMapEditor/UI/Controls/Slots/SlotsViewModel.cs index ff63c78..4d7ee80 100644 --- a/AnnoMapEditor/UI/Controls/Slots/SlotsViewModel.cs +++ b/AnnoMapEditor/UI/Controls/Slots/SlotsViewModel.cs @@ -1,5 +1,4 @@ using AnnoMapEditor.DataArchives.Assets.Models; -using AnnoMapEditor.MapTemplates.Enums; using AnnoMapEditor.MapTemplates.Models; using AnnoMapEditor.UI.Overlays; using AnnoMapEditor.UI.Overlays.SelectSlots; @@ -14,12 +13,12 @@ public class SlotsViewModel { public FixedIslandElement FixedIsland { get; init; } - public Region Region { get; init; } + public RegionAsset Region { get; init; } public ObservableCollection SlotCounters { get; init; } = new(); - public SlotsViewModel(FixedIslandElement fixedIsland, Region region) + public SlotsViewModel(FixedIslandElement fixedIsland, RegionAsset region) { FixedIsland = fixedIsland; Region = region; diff --git a/AnnoMapEditor/UI/Overlays/ExportAsMod/ExportAsModViewModel.cs b/AnnoMapEditor/UI/Overlays/ExportAsMod/ExportAsModViewModel.cs index fd9207b..9452da2 100644 --- a/AnnoMapEditor/UI/Overlays/ExportAsMod/ExportAsModViewModel.cs +++ b/AnnoMapEditor/UI/Overlays/ExportAsMod/ExportAsModViewModel.cs @@ -1,4 +1,4 @@ -using AnnoMapEditor.MapTemplates.Enums; +using AnnoMapEditor.DataArchives.Assets.Models; using AnnoMapEditor.MapTemplates.Models; using AnnoMapEditor.Mods.Enums; using AnnoMapEditor.Mods.Models; @@ -29,7 +29,7 @@ public MapTemplate? MapTemplate _mapTemplate = value; if(_mapTemplate is not null) { - AllowedMapTypes = MapType.MapTypesForRegion[_mapTemplate.Region]; + AllowedMapTypes = MapType.All; SelectedMapType = AllowedMapTypes.First(); } else @@ -37,7 +37,7 @@ public MapTemplate? MapTemplate AllowedMapTypes = Enumerable.Empty(); SelectedMapType = null; } - InfoMapTypeSelection = _mapTemplate is not null && _mapTemplate.Region == Region.Moderate; + InfoMapTypeSelection = _mapTemplate is not null && _mapTemplate.Session == SessionAsset.OldWorld; CheckExistingMod(); } } diff --git a/AnnoMapEditor/UI/Overlays/SelectFertilities/SelectFertilitiesViewModel.cs b/AnnoMapEditor/UI/Overlays/SelectFertilities/SelectFertilitiesViewModel.cs index 513ffbd..8de019d 100644 --- a/AnnoMapEditor/UI/Overlays/SelectFertilities/SelectFertilitiesViewModel.cs +++ b/AnnoMapEditor/UI/Overlays/SelectFertilities/SelectFertilitiesViewModel.cs @@ -1,9 +1,7 @@ using AnnoMapEditor.DataArchives.Assets.Models; using AnnoMapEditor.DataArchives.Assets.Repositories; -using AnnoMapEditor.MapTemplates.Enums; using AnnoMapEditor.MapTemplates.Models; using AnnoMapEditor.Utilities; -using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; @@ -27,11 +25,11 @@ public string? NameFilter } private string? _nameFilter; - public IEnumerable Regions { get; init; } = Region.All; + public IEnumerable Regions { get; init; } = RegionAsset.SupportedRegions; - private readonly Region _initialRegion; + private readonly RegionAsset _initialRegion; - public Region SelectedRegion + public RegionAsset SelectedRegion { get => _selectedRegion; set @@ -42,7 +40,7 @@ public Region SelectedRegion ShowRegionWarning = _selectedRegion != _initialRegion; } } - private Region _selectedRegion; + private RegionAsset _selectedRegion; public bool ShowRegionWarning { @@ -54,7 +52,7 @@ public bool ShowRegionWarning public ObservableCollection FertilityItems { get; init; } - public SelectFertilitiesViewModel(Region region, FixedIslandElement fixedIsland) + public SelectFertilitiesViewModel(RegionAsset region, FixedIslandElement fixedIsland) { // TODO: Should this happen here? AssetRepository assetRepository = Settings.Instance.AssetRepository!; @@ -102,17 +100,10 @@ private bool FertilityFilter(object item) FertilityAsset fertilityAsset = fertilityItem.FertilityAsset; - if (SelectedRegion != null) - { - // TODO: Should this happen here? - AssetRepository assetRepository = Settings.Instance.AssetRepository!; - RegionAsset regionAsset = assetRepository.Get(SelectedRegion.AssetGuid); - - if (!regionAsset.AllowedFertilities.Contains(fertilityAsset)) - return false; - } - - if (!string.IsNullOrEmpty(_nameFilter)) + if (SelectedRegion != null && !SelectedRegion.AllowedFertilities.Contains(fertilityAsset)) + return false; + + else if (!string.IsNullOrEmpty(_nameFilter)) { string filter = _nameFilter.ToLower(); if (fertilityAsset.Name?.ToLower().Contains(filter) != true && !fertilityAsset.DisplayName.ToLower().Contains(filter)) diff --git a/AnnoMapEditor/UI/Overlays/SelectIsland/SelectIslandViewModel.cs b/AnnoMapEditor/UI/Overlays/SelectIsland/SelectIslandViewModel.cs index e8e7b3c..2897da5 100644 --- a/AnnoMapEditor/UI/Overlays/SelectIsland/SelectIslandViewModel.cs +++ b/AnnoMapEditor/UI/Overlays/SelectIsland/SelectIslandViewModel.cs @@ -27,11 +27,11 @@ public string? PathFilter } private string? _pathFilter; - public IEnumerable Regions { get; init; } = Region.All; + public IEnumerable Regions { get; init; } = RegionAsset.SupportedRegions; - private readonly Region _initialRegion; + private readonly RegionAsset _initialRegion; - public Region? SelectedRegion + public RegionAsset? SelectedRegion { get => _selectedRegion; set @@ -42,7 +42,7 @@ public Region? SelectedRegion ShowRegionWarning = _selectedRegion != _initialRegion; } } - private Region? _selectedRegion; + private RegionAsset? _selectedRegion; public IEnumerable IslandTypes { get; init; } = IslandType.All; @@ -106,7 +106,7 @@ public bool ShowRegionWarning private bool _showRegionWarning = false; - public SelectIslandViewModel(Region region, IslandType? islandType = null, IslandSize? islandSize = null) + public SelectIslandViewModel(RegionAsset region, IslandType? islandType = null, IslandSize? islandSize = null) { SelectedRegion = _initialRegion = region; SelectedIslandType = islandType; diff --git a/AnnoMapEditor/UI/Overlays/SelectSlots/SelectSlotsViewModel.cs b/AnnoMapEditor/UI/Overlays/SelectSlots/SelectSlotsViewModel.cs index 1fa4535..26d5d79 100644 --- a/AnnoMapEditor/UI/Overlays/SelectSlots/SelectSlotsViewModel.cs +++ b/AnnoMapEditor/UI/Overlays/SelectSlots/SelectSlotsViewModel.cs @@ -1,12 +1,10 @@ using AnnoMapEditor.DataArchives.Assets.Models; -using AnnoMapEditor.MapTemplates.Enums; using AnnoMapEditor.MapTemplates.Models; using AnnoMapEditor.Utilities; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; -using System.Windows; using System.Windows.Data; namespace AnnoMapEditor.UI.Overlays.SelectSlots @@ -77,11 +75,11 @@ public int Compare(Vector2? a, Vector2? b) public class SelectSlotsViewModel : ObservableBase, IOverlayViewModel { public event EventHandler>? FilterModified; - public IEnumerable Regions { get; init; } = Region.All; + public IEnumerable Regions { get; init; } = RegionAsset.SupportedRegions; - private readonly Region _initialRegion; + private readonly RegionAsset _initialRegion; - public Region SelectedRegion + public RegionAsset SelectedRegion { get => _selectedRegion; set @@ -92,7 +90,7 @@ public Region SelectedRegion ShowRegionWarning = _selectedRegion != _initialRegion; } } - private Region _selectedRegion; + private RegionAsset _selectedRegion; public bool ShowRegionWarning { @@ -141,7 +139,7 @@ public bool ShowOil public IEnumerable SlotAssignmentViewModelsRight { get; private set; } - public SelectSlotsViewModel(Region region, FixedIslandElement fixedIsland) + public SelectSlotsViewModel(RegionAsset region, FixedIslandElement fixedIsland) { _selectedRegion = _initialRegion = region; FixedIsland = fixedIsland; diff --git a/AnnoMapEditor/UI/Overlays/SelectSlots/SlotAssignmentViewModel.cs b/AnnoMapEditor/UI/Overlays/SelectSlots/SlotAssignmentViewModel.cs index 163f430..3723378 100644 --- a/AnnoMapEditor/UI/Overlays/SelectSlots/SlotAssignmentViewModel.cs +++ b/AnnoMapEditor/UI/Overlays/SelectSlots/SlotAssignmentViewModel.cs @@ -1,10 +1,8 @@ using AnnoMapEditor.DataArchives.Assets.Models; -using AnnoMapEditor.MapTemplates.Enums; using AnnoMapEditor.MapTemplates.Models; using AnnoMapEditor.Utilities; using System; using System.Collections.ObjectModel; -using System.Linq; using System.Windows.Data; using System.Windows.Media; @@ -62,7 +60,7 @@ public Brush BackgroundBrush } private Brush _backgroundBrush; - public Region SelectedRegion + public RegionAsset SelectedRegion { get => _selectedRegion; set @@ -71,10 +69,10 @@ public Region SelectedRegion UpdateFilter(); } } - private Region _selectedRegion; + private RegionAsset _selectedRegion; - public SlotAssignmentViewModel(SlotAssignment slotAssignment, Region selectedRegion) + public SlotAssignmentViewModel(SlotAssignment slotAssignment, RegionAsset selectedRegion) { SlotAssignment = slotAssignment; _selectedRegion = selectedRegion; @@ -96,11 +94,11 @@ private bool SlotAssetFilter(object item) throw new ArgumentException(); // Warning: Hardcoding - // The New World uses both random mine slots 614 and 1000029. However, none of the + // SouthAmerica uses both random mine slots 614 and 1000029. However, none of the // replacements for 1000029 contain the New World as an AssociatedRegion. Despite of // this, it is possible to have both 1010502 Iron Deposit and 1010507 Gold Deposit in // the New World. - if (_selectedRegion == Region.NewWorld + if (_selectedRegion == RegionAsset.SouthAmerica && SlotAssignment.Slot.ObjectGuid == SlotAsset.RANDOM_MINE_OLD_WORLD_GUID && (slotAsset.GUID == 1010501 || slotAsset.GUID == 1010507)) return true; @@ -142,13 +140,13 @@ private void UpdateBrushes() { long randomSlotGuid = SlotAssignment.Slot.ObjectGuid; - if (randomSlotGuid == SlotAsset.RANDOM_MINE_OLD_WORLD_GUID - || randomSlotGuid == SlotAsset.RANDOM_MINE_NEW_WORLD_GUID - || randomSlotGuid == SlotAsset.RANDOM_MINE_ARCTIC_GUID) + if (randomSlotGuid == SlotAsset.RandomMineOldWorld.GUID + || randomSlotGuid == SlotAsset.RandomMineNewWorld.GUID + || randomSlotGuid == SlotAsset.RandomMineArctic.GUID) PinBrush = Brushes.Gray; - else if (randomSlotGuid == SlotAsset.RANDOM_CLAY_GUID) + else if (randomSlotGuid == SlotAsset.RandomClay.GUID) PinBrush = Brushes.SandyBrown; - else if (randomSlotGuid == SlotAsset.RANDOM_OIL_GUID) + else if (randomSlotGuid == SlotAsset.RandomOil.GUID) PinBrush = Brushes.DarkSlateGray; else PinBrush = Brushes.Red; diff --git a/AnnoMapEditor/UI/Windows/Main/MainWindowViewModel.cs b/AnnoMapEditor/UI/Windows/Main/MainWindowViewModel.cs index 2b2f24d..1e7d206 100644 --- a/AnnoMapEditor/UI/Windows/Main/MainWindowViewModel.cs +++ b/AnnoMapEditor/UI/Windows/Main/MainWindowViewModel.cs @@ -1,8 +1,7 @@ using AnnoMapEditor.DataArchives; -using AnnoMapEditor.MapTemplates.Enums; +using AnnoMapEditor.DataArchives.Assets.Models; using AnnoMapEditor.MapTemplates.Models; using AnnoMapEditor.MapTemplates.Serializing; -using AnnoMapEditor.Mods.Models; using AnnoMapEditor.UI.Controls; using AnnoMapEditor.UI.Controls.IslandProperties; using AnnoMapEditor.UI.Controls.MapTemplates; @@ -71,7 +70,7 @@ public IslandElement? SelectedIsland else if (value is FixedIslandElement fixedIsland) { SelectedRandomIslandPropertiesViewModel = null; - SelectedFixedIslandPropertiesViewModel = new(fixedIsland, MapTemplate!.Region); + SelectedFixedIslandPropertiesViewModel = new(fixedIsland, MapTemplate!.Session.Region); } } } @@ -175,7 +174,7 @@ public void CreateNewMap() MapTemplateFilePath = null; - MapTemplate = new MapTemplate(DEFAULT_MAP_SIZE, DEFAULT_PLAYABLE_SIZE, Region.Moderate); + MapTemplate = new MapTemplate(DEFAULT_MAP_SIZE, DEFAULT_PLAYABLE_SIZE, SessionAsset.OldWorld); UpdateExportStatus(); } @@ -207,13 +206,12 @@ private void UpdateExportStatus() } else if (Settings.IsValidDataPath) { - bool supportedFormat = Mod.CanSave(MapTemplate); bool archiveReady = Settings.DataArchive is RdaDataArchive; ExportStatus = new ExportStatus() { - CanExportAsMod = archiveReady && supportedFormat, - ExportAsModText = archiveReady ? supportedFormat ? "As playable mod..." : "As mod: only works with Old World maps currently" : "As mod: set game path to save" + CanExportAsMod = archiveReady, + ExportAsModText = archiveReady ? "As playable mod..." : "As mod: set game path to save" }; } else diff --git a/AnnoMapEditor/Utilities/Settings.cs b/AnnoMapEditor/Utilities/Settings.cs index eb1e067..da531c4 100644 --- a/AnnoMapEditor/Utilities/Settings.cs +++ b/AnnoMapEditor/Utilities/Settings.cs @@ -190,6 +190,8 @@ private async Task LoadDataPathAsync(string? path) assetRepository.Register(); assetRepository.Register(); assetRepository.Register(); + assetRepository.Register(); + assetRepository.Register(); await assetRepository.LoadAsync(); FixedIslandRepository fixedIslandRepository = new(archive); From 3927d5e9deed38c228fb79951a9fa84f56b7dd42 Mon Sep 17 00:00:00 2001 From: Christopher Cyclonit Klinge Date: Thu, 8 Jun 2023 11:35:29 +0200 Subject: [PATCH 05/16] Rename SessionAsset.SunkenTreasures to SessionAsset.CapeTrelawney to reflect the session's name instead of the DLC. Extract ModWriter from Mod. Clean up ModWriter. Add support for exporting Cape Trewlaney, Arctic and Enbesa mods. --- .../Assets/Models/SessionAsset.cs | 6 +- .../Serializing/MapTemplateWriter.cs | 15 +- .../Validation/SmallPoolSizeValidator.cs | 2 +- AnnoMapEditor/Mods/Models/Mod.cs | 254 +------------ AnnoMapEditor/Mods/Serialization/ModWriter.cs | 336 ++++++++++++++++++ .../ExportAsMod/ExportAsModOverlay.xaml | 42 ++- .../ExportAsMod/ExportAsModViewModel.cs | 89 +++-- .../UI/Windows/Main/MainWindow.xaml.cs | 5 +- .../UI/Windows/Main/MainWindowViewModel.cs | 4 +- 9 files changed, 432 insertions(+), 321 deletions(-) create mode 100644 AnnoMapEditor/Mods/Serialization/ModWriter.cs diff --git a/AnnoMapEditor/DataArchives/Assets/Models/SessionAsset.cs b/AnnoMapEditor/DataArchives/Assets/Models/SessionAsset.cs index 4317305..af21350 100644 --- a/AnnoMapEditor/DataArchives/Assets/Models/SessionAsset.cs +++ b/AnnoMapEditor/DataArchives/Assets/Models/SessionAsset.cs @@ -29,7 +29,7 @@ public class SessionAsset : StandardAsset public static SessionAsset NewWorld { get; private set; } [StaticAsset(SESSION_SUNKENTREASURES_GUID)] - public static SessionAsset SunkenTreasures { get; private set; } + public static SessionAsset CapeTrelawney { get; private set; } [StaticAsset(SESSION_ARCTIC_GUID)] public static SessionAsset Arctic { get; private set; } @@ -37,7 +37,7 @@ public class SessionAsset : StandardAsset [StaticAsset(SESSION_ENBESA_GUID)] public static SessionAsset Enbesa { get; private set; } - public static IEnumerable SupportedSessions => new[] { OldWorld, NewWorld, SunkenTreasures, Arctic, Enbesa }; + public static IEnumerable SupportedSessions => new[] { OldWorld, NewWorld, CapeTrelawney, Arctic, Enbesa }; /// @@ -126,7 +126,7 @@ public static SessionAsset DetectFromPath(string filePath) else if (filePath.Contains("dlc06") || filePath.Contains("colony02") || filePath.Contains("scenario02")) return Enbesa; else if (filePath.Contains("sunken_treasures")) - return SunkenTreasures; + return CapeTrelawney; else return OldWorld; } diff --git a/AnnoMapEditor/MapTemplates/Serializing/MapTemplateWriter.cs b/AnnoMapEditor/MapTemplates/Serializing/MapTemplateWriter.cs index 01880ce..4230f23 100644 --- a/AnnoMapEditor/MapTemplates/Serializing/MapTemplateWriter.cs +++ b/AnnoMapEditor/MapTemplates/Serializing/MapTemplateWriter.cs @@ -1,4 +1,5 @@ using AnnoMapEditor.MapTemplates.Models; +using System; using System.IO; using System.Threading.Tasks; @@ -6,22 +7,20 @@ namespace AnnoMapEditor.MapTemplates.Serializing { public class MapTemplateWriter { - public async Task ToXmlAsync(MapTemplate mapTemplate, string filePath) + public async Task WriteXmlAsync(MapTemplate mapTemplate, string filePath) { - var export = mapTemplate.ToTemplateDocument(); - if (export is null) - return; + var export = mapTemplate.ToTemplateDocument() + ?? throw new NullReferenceException($"Failed to write MapTemplate to XML. The MapTemplate could not be translated to a MapTemplateDocument."); using Stream file = File.OpenWrite(filePath); file.SetLength(0); // clear await FileDBSerializer.WriteToXmlAsync(export, file); } - public async Task ToA7tinfoAsync(MapTemplate mapTemplate, string filePath) + public async Task WriteA7tinfoAsync(MapTemplate mapTemplate, string filePath) { - var export = mapTemplate.ToTemplateDocument(); - if (export is null) - return; + var export = mapTemplate.ToTemplateDocument() + ?? throw new NullReferenceException($"Failed to write MapTemplate to a7tinfo. The MapTemplate could not be translated to a MapTemplateDocument."); var parentPath = Path.GetDirectoryName(filePath); if (parentPath is not null) diff --git a/AnnoMapEditor/MapTemplates/Validation/SmallPoolSizeValidator.cs b/AnnoMapEditor/MapTemplates/Validation/SmallPoolSizeValidator.cs index 3250a6c..59d8210 100644 --- a/AnnoMapEditor/MapTemplates/Validation/SmallPoolSizeValidator.cs +++ b/AnnoMapEditor/MapTemplates/Validation/SmallPoolSizeValidator.cs @@ -28,7 +28,7 @@ public MapTemplateValidatorResult Validate(MapTemplate mapTemplate) } // subtract Archibald / Nate / Isabel from the counter - if ((mapTemplate.Session == SessionAsset.OldWorld || mapTemplate.Session == SessionAsset.NewWorld || mapTemplate.Session == SessionAsset.SunkenTreasures) && thirdPartyCount > 0) + if ((mapTemplate.Session == SessionAsset.OldWorld || mapTemplate.Session == SessionAsset.NewWorld || mapTemplate.Session == SessionAsset.CapeTrelawney) && thirdPartyCount > 0) --smallIslandCount; // subtract all but one pirate island from the counter diff --git a/AnnoMapEditor/Mods/Models/Mod.cs b/AnnoMapEditor/Mods/Models/Mod.cs index 4cf211a..d244964 100644 --- a/AnnoMapEditor/Mods/Models/Mod.cs +++ b/AnnoMapEditor/Mods/Models/Mod.cs @@ -1,17 +1,5 @@ -using AnnoMapEditor.DataArchives.Assets.Models; -using AnnoMapEditor.MapTemplates.Models; -using AnnoMapEditor.MapTemplates.Serializing; +using AnnoMapEditor.MapTemplates.Models; using AnnoMapEditor.Mods.Enums; -using AnnoMapEditor.Mods.Serialization; -using AnnoMapEditor.Utilities; -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using System.Windows; /* * Modloader doesn't support a7t because they are loaded as .rda archive. @@ -27,248 +15,26 @@ namespace AnnoMapEditor.Mods.Models { - internal class Mod + public class Mod { public const string AME_POOL_PATH = @"data\ame\maps\pool"; + public string Name { get; init; } + + public string? Id { get; init; } + public MapTemplate MapTemplate { get; init; } - public MapType MapType { get; init; } + public MapType? MapType { get; init; } - public Mod(MapTemplate mapTemplate, MapType? mapType) + public Mod(string name, string? modId, MapTemplate mapTemplate, MapType? mapType) { + Name = name; + Id = modId; MapTemplate = mapTemplate; MapType = mapType; } - - - public async Task Save(string modPath, string modName, string? modID) - { - throw new NotImplementedException($"Mod export is currently broken!"); - - string fullModName = "[Map] " + modName; - - List sizes = new List(); // MapTemplate.Session.GetAllSizeCombinations().ToList(); - string size = sizes[0]; - - try - { - string? mapTypeFileName = MapType.ToFileName(); - string? mapTypeGuid = MapType.Guid; - - if (mapTypeFileName is null || mapTypeGuid is null) - throw new Exception("invalid MapType"); - - FileUtils.TryDeleteDirectory(modPath); - Directory.CreateDirectory(modPath); - - //Only write Language XML for OW Maps, as only they need naming in a menu - if (!string.IsNullOrEmpty(mapTypeGuid)) - await WriteLanguageXml(modPath, modName, mapTypeGuid); - - // write meta JSON, assets XML, .a7tinfo, .a7t and .a7te - string poolFolderName = Regex.Replace(MapTemplate.Session.DisplayName, @"\s+", "_"); - string mapFilePath = Path.Combine(AME_POOL_PATH, poolFolderName, mapTypeFileName); - string a7tBasePath = Path.Combine(modPath, $"{mapFilePath}"); - string a7tInfoPath = a7tBasePath + $"_{size}.a7tinfo"; - string a7tPath = a7tBasePath + $"_{size}.a7t"; - string a7tePath = a7tBasePath + $"_{size}.a7te"; - - await WriteMetaJson(modPath, modName, modID); - await WriteAssetsXml(modPath, mapFilePath, MapTemplate.Session.Region, MapType); - - MapTemplateWriter mapTemplateWriter = new(); - await mapTemplateWriter.ToA7tinfoAsync(MapTemplate, a7tBasePath + $"_{size}.a7tinfo"); - - (int x, int y, int size) playableArea = (MapTemplate.PlayableArea.X, MapTemplate.PlayableArea.Y, MapTemplate.PlayableArea.Width); - await Task.Run(() => new A7tExporter(MapTemplate.Size.X, playableArea, MapTemplate.Session.Region).ExportA7T(a7tPath)); - await Task.Run(() => new A7teExporter(MapTemplate.Size.X).ExportA7te(a7tePath)); - - if (MapTemplate.Session == SessionAsset.NewWorld) - { - File.Copy(a7tInfoPath, a7tBasePath + $"_{size}_enlarged.a7tinfo"); - File.Copy(a7tPath, a7tBasePath + $"_{size}_enlarged.a7t"); - File.Copy(a7tePath, a7tBasePath + $"_{size}_enlarged.a7te"); - } - - //copy a7tinfo, a7t and a7te for all sizes - for (int i = 1; i(File.ReadAllText(modinfoPath)); - //} - - //if (modinfo is null) - { - modinfo = new() - { - Version = "1", - ModID = string.IsNullOrEmpty(modID) ? $"ame_{MakeSafeName(modName)}_{Guid.NewGuid().ToString().Split('-').FirstOrDefault("")}" : modID, - ModName = new(modName), - Category = new("Map"), - Description = new($"Select Map Type '{modName}' to play this map.\n" + - $"World and island sizes are fixed.\n" + - $"\n" + - $"Note:\n" + - $"- Do not rename the mod folder. It will lead to a loading screen freeze.\n" + - $"- You can combine map mods as long as they do not replace the same map type.\n" + - $"\n" + - $"This mod has been created with the {App.Title}.\n" + - $"You can download the editor at:\nhttps://github.com/anno-mods/AnnoMapEditor/releases/latest"), - CreatorName = App.TitleShort, - CreatorContact = "https://github.com/anno-mods/AnnoMapEditor" - }; - } - - using StreamWriter writer = new(File.Create(modinfoPath)); - await writer.WriteAsync(JsonConvert.SerializeObject(modinfo, Newtonsoft.Json.Formatting.Indented)); - } - - private static async Task WriteLanguageXml(string modPath, string name, string guid) - { - string[] languages = new string[] { "chinese", "english", "french", "german", "italian", "japanese", "korean", "polish", "russian", "spanish", "taiwanese" }; - - foreach (var language in languages) - { - string languageXmlPath = Path.Combine(modPath, $@"data\config\gui\texts_{language}.xml"); - string? languageXmlDir = Path.GetDirectoryName(languageXmlPath); - if (languageXmlDir is not null) - Directory.CreateDirectory(languageXmlDir); - - string content = - $"\n" + - $" \n" + - $" {name}\n" + - $" \n" + - $"\n"; - - using StreamWriter writer = new(File.Create(languageXmlPath)); - await writer.WriteAsync(content); - } - } - - private static async Task WriteAssetsXml(string modPath, string mapFilePath, RegionAsset region, MapType mapType) - { - string assetsXmlPath = Path.Combine(modPath, @"data\config\export\main\asset\assets.xml"); - string? assetsXmlDir = Path.GetDirectoryName(assetsXmlPath); - if (assetsXmlDir is not null) - Directory.CreateDirectory(assetsXmlDir); - - string content = CreateAssetsModOps(region, mapType, mapFilePath.Replace("\\", "/")); - - using StreamWriter writer = new(File.Create(assetsXmlPath)); - await writer.WriteAsync(content); - } - - public static string CreateAssetsModOps(RegionAsset region, MapType mapType, string fullMapPath) - { - throw new NotImplementedException($"Mod export is currently broken!"); - -// IEnumerable sizes = region.MapSizes; -// // some maps have updates sizes, but make sure to only replace one -// IEnumerable subSizes = region.MapSizeIndices; -// -// static string MakeXPath(string mapTypeName, string size, string subsize) -// { -// if (string.IsNullOrEmpty(subsize)) -// return $"../Standard/Name='{mapTypeName}{size}'"; -// else -// return $"../Standard/Name='{mapTypeName}{size}_{subsize}'"; -// } -// -// string content = "\n"; -// -// if (region.UsesAllSizeIndices) -// { -// //Single ModOp for all Sub-Sizes -// foreach (var size in sizes) -// { -// foreach(var subsize in subSizes) -// { -// content += CreateModOp( -// new string[] { MakeXPath(mapType.ToName(), size, subsize) }, -// fullMapPath, -// $"{size}_{subsize}", -// region.HasMapExtension); -// } -// } -// } -// else -// { -// //XPath OR -// foreach (var size in sizes) -// { -// var xpaths = subSizes.Select(x => MakeXPath(mapType.ToName(), size, x)); -// -// content += CreateModOp(xpaths, fullMapPath, size, region.HasMapExtension); -// } -// } -// -// -// content += "\n"; -// return content; - } - - private static string CreateModOp(IEnumerable xPaths, string basePath, string size, bool extension) - { - string result = CreateModOpSingle(xPaths, basePath, size, false); - if (extension) - { - result += CreateModOpSingle(xPaths, basePath, size, true); - } - return result; - } - - private static string CreateModOpSingle(IEnumerable xPaths, string basePath, string size, bool extension) - { - const string TEMPLATE = "TemplateFilename"; - const string ENLARGED_TEMPLATE = "EnlargedTemplateFilename"; - - string target = extension ? ENLARGED_TEMPLATE : TEMPLATE; - - string result = $" \n" + - $" <{target}>{basePath}_{size}{(extension ? "_enlarged" : "")}.a7t\n" + - $" \n"; - - return result; - } - - private static string MakeSafeName(string unsafeName) => new Regex(@"\W").Replace(unsafeName, "_").ToLower(); } } diff --git a/AnnoMapEditor/Mods/Serialization/ModWriter.cs b/AnnoMapEditor/Mods/Serialization/ModWriter.cs new file mode 100644 index 0000000..9a16442 --- /dev/null +++ b/AnnoMapEditor/Mods/Serialization/ModWriter.cs @@ -0,0 +1,336 @@ +using Anno.FileDBModels.Anno1800.Gamedata.Models.Shared; +using AnnoMapEditor.DataArchives.Assets.Models; +using AnnoMapEditor.DataArchives.Assets.Repositories; +using AnnoMapEditor.MapTemplates.Serializing; +using AnnoMapEditor.Mods.Enums; +using AnnoMapEditor.Mods.Models; +using AnnoMapEditor.Utilities; +using FileDBSerializing; +using FileDBSerializing.ObjectSerializer; +using Newtonsoft.Json; +using RDAExplorer; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Xml; +using System.Xml.Serialization; + +namespace AnnoMapEditor.Mods.Serialization +{ + public class ModWriter + { + public const string AME_POOL_PATH = @"data\ame\maps\pool"; + + + private readonly MapTemplateWriter _mapTemplateWriter; + + private readonly AssetRepository _assetRepository; + + + public ModWriter() + { + _mapTemplateWriter = new(); + _assetRepository = Settings.Instance.AssetRepository + ?? throw new Exception($"AssetRepository has not been initialized."); + } + + + /// + /// Determines the list of MapTemplateAssets to be replaced for a mod targeting the given + /// session and mapType. + /// + /// + /// The way Anno 1800 chooses which template to use differs between sessions. As of GU17.1, + /// there are three scenarios we support: + /// + /// + /// 1. The Old World
+ /// The map template used for The Old World is randomly chosen out of one from a set pools. + /// Separate map pools exist for different map sizes and MapTypes. + /// To guarantee the mod's map template will be used, we replace the entire pool for the + /// given mapType.
+ /// + /// 2. The New World
+ /// Similarly to The Old World, the The New World's map template is are chosen randomly + /// from a pool. However, there exist no MapTypes for this session.
+ /// Additionally, The 13th DLC "New World Rising" introduced the concept of map extensions. + /// All new world map templates were extended to make room for the island Manola in the top + /// corner of the session.
+ /// All New World map templates and their extended versions are being targeted to be + /// replaced.
+ /// + /// 3. Cape Trelawney, + /// Arctic and Enbesa + ///
+ /// All other supported sessions reference a map template directly via the attributes + /// MapTemplate and + /// MapTemplateForMultiplayer. In + /// their case, replacing these map templates is sufficient to use the mod's map template + /// instead. + ///
+ /// + /// The MapType to be used for the mod. + /// Must not be null if session is + /// OldWorld. + /// + /// + private IList GetMapTemplatesToReplace(SessionAsset session, MapType? mapType = null) + { + // Sessions may reference the MapTemplates to be used directly via the MapTemplate and + // MapTemplateForMultiplayer attributes. + if (session.MapTemplate != null) + { + List mapTemplates = new(); + mapTemplates.Add(session.MapTemplate); + if (session.MapTemplateForMultiplayer != null) + mapTemplates.Add(session.MapTemplateForMultiplayer); + + return mapTemplates; + } + + // Otherwise get pool maps matching the Session's region. + else + { + IEnumerable mapTemplates = _assetRepository.GetAll() + .Where(m => m.TemplateRegion == session.Region) + .Where(m => m.TemplateFilename.Contains("pool")); + + // For The Old World filter according to the MapType. + if (session == SessionAsset.OldWorld) + { + if (mapType != null) + mapTemplates = mapTemplates.Where(m => m.TemplateMapType == mapType); + else + throw new ArgumentException($"Cannot determine MapTemplates to replace for session {session.GUID} \"{session.DisplayName}\" without a given {typeof(MapType).FullName}."); + } + + return mapTemplates.ToList(); + } + } + + public async Task WriteAsync(Mod mod, string modPath) + { + FileUtils.TryDeleteDirectory(modPath); + Directory.CreateDirectory(modPath); + + SessionAsset session = mod.MapTemplate.Session; + + IList mapTemplatesToReplace = GetMapTemplatesToReplace(session, mod.MapType); + + await WriteModinfoJson(mod, modPath); + + //Only write Language XML for OW Maps, as only they need naming in a menu + if (session == SessionAsset.OldWorld) + await WriteLanguageXml(modPath, mod.Name, mod.MapType!.Guid); + + // create the first copy of a7t, a7tinfo and a7te + MapTemplateAsset firstMapTemplate = mapTemplatesToReplace.First(); + string mapFileDirectory = Path.Combine(modPath, AME_POOL_PATH); + string a7tPath = Path.Combine(mapFileDirectory, Path.GetFileName(firstMapTemplate.TemplateFilename)); + string a7tinfoPath = Path.ChangeExtension(a7tPath, "a7tinfo"); + string a7tePath = Path.ChangeExtension(a7tPath, "a7te"); + + await _mapTemplateWriter.WriteA7tinfoAsync(mod.MapTemplate, Path.Combine(modPath, a7tinfoPath)); + WriteA7T(mod, a7tPath); + WriteA7te(mod.MapTemplate.Size.X, a7tePath); + + // copy a7t, a7tinfo and a7te for all MapTemplates that must be replaced + for (int i = 1; i < mapTemplatesToReplace.Count; ++i) + { + MapTemplateAsset mapTemplate = mapTemplatesToReplace[i]; + string mapFilename = Path.GetFileNameWithoutExtension(mapTemplate.TemplateFilename); + + File.Copy(a7tPath, Path.Combine(mapFileDirectory, mapFilename + ".a7t")); + File.Copy(a7tinfoPath, Path.Combine(mapFileDirectory, mapFilename + ".a7tinfo")); + File.Copy(a7tePath, Path.Combine(mapFileDirectory, mapFilename + ".a7te")); + + if (mapTemplate.EnlargedTemplateFilename != null) + { + File.Copy(a7tPath, Path.Combine(mapFileDirectory, mapFilename + "_enlarged.a7t")); + File.Copy(a7tinfoPath, Path.Combine(mapFileDirectory, mapFilename + "_enlarged.a7tinfo")); + File.Copy(a7tePath, Path.Combine(mapFileDirectory, mapFilename + "_enlarged.a7te")); + } + } + + await WriteAssetsXml(modPath, mapTemplatesToReplace); + + return true; + } + + private static async Task WriteAssetsXml(string modPath, IEnumerable mapTemplatesToReplace) + { + string assetsXmlPath = Path.Combine(modPath, @"data\config\export\main\asset\assets.xml"); + + string? assetsXmlDir = Path.GetDirectoryName(assetsXmlPath); + if (assetsXmlDir is not null) + Directory.CreateDirectory(assetsXmlDir); + + StringBuilder sb = new(); + sb.Append("\n"); + + foreach (MapTemplateAsset mapTemplateAsset in mapTemplatesToReplace) + { + string templateFilename = Path.Combine(AME_POOL_PATH, Path.GetFileName(mapTemplateAsset.TemplateFilename)); + string? enlargedTemplateFilename = mapTemplateAsset.EnlargedTemplateFilename != null ? Path.Combine(AME_POOL_PATH, Path.GetFileName(mapTemplateAsset.EnlargedTemplateFilename)) : null; + + WriteModOpReplaceTemplateFilename(sb, mapTemplateAsset.GUID, templateFilename, enlargedTemplateFilename); + + if (mapTemplateAsset.EnlargedTemplateFilename != null) + WriteModOpReplaceTemplateFilename(sb, mapTemplateAsset.GUID, templateFilename, enlargedTemplateFilename); + } + + sb.Append("\n"); + + using StreamWriter writer = new(File.Create(assetsXmlPath)); + await writer.WriteAsync(sb.ToString()); + } + + private static async Task WriteModinfoJson(Mod mod, string modPath) + { + string modDescription; + if (mod.MapTemplate.Session == SessionAsset.OldWorld) + modDescription = $"Select Map Type '{mod.Name}' to play this map.\n" + + $"World and island sizes are fixed.\n\n"; + else + modDescription = $"This mod will automatically replace {mod.MapTemplate.Session.DisplayName}.\n\n"; + + modDescription += $"Note:\n" + + $"- Do not rename the mod folder. It will lead to a loading screen freeze.\n" + + $"- You can combine map mods as long as they do not replace the same session and map type.\n" + + $"\n" + + $"This mod has been created with the {App.Title}.\n" + + $"You can download the editor at:\nhttps://github.com/anno-mods/AnnoMapEditor/releases/latest"; + + Modinfo modinfo = new() + { + Version = "1", + ModID = string.IsNullOrEmpty(mod.Id) ? $"ame_{MakeSafeName(mod.Name)}_{Guid.NewGuid().ToString().Split('-').FirstOrDefault("")}" : mod.Id, + ModName = new(mod.Name), + Category = new("Map"), + Description = new(modDescription), + CreatorName = App.TitleShort, + CreatorContact = "https://github.com/anno-mods/AnnoMapEditor" + }; + + string modinfoPath = Path.Combine(modPath, "modinfo.json"); + + using StreamWriter writer = new(File.Create(modinfoPath)); + await writer.WriteAsync(JsonConvert.SerializeObject(modinfo, Newtonsoft.Json.Formatting.Indented)); + } + + private static void WriteModOpReplaceTemplateFilename(StringBuilder sb, long mapTemplateGuid, string templateFilename, string? enlargedTemplateFilename) + { + sb.Append($" \n") + .Append($" {templateFilename.Replace('\\', '/')}\n"); + + if (enlargedTemplateFilename != null) + sb.Append($" {templateFilename.Replace('\\', '/')}\n"); + + sb.Append($" \n"); + } + + + private static void WriteA7T(Mod mod, string a7tPath) + { + using MemoryStream nestedDataStream = new(); + + // If the session has existing Gamedata, it must be updated to reflect all changes made within the AME. + (int x, int y, int size) playableArea = (mod.MapTemplate.PlayableArea.X, mod.MapTemplate.PlayableArea.Y, mod.MapTemplate.PlayableArea.Width); + Gamedata gameDataItem = new(mod.MapTemplate.Size.X, playableArea, mod.MapTemplate.Session.Region.Ambiente!, true); + + //Create actual a7t File + FileDBDocumentSerializer serializer = new(new() { Version = FileDBDocumentVersion.Version1 }); + IFileDBDocument generatedFileDB = serializer.WriteObjectStructureToFileDBDocument(gameDataItem); + + using MemoryStream fileDbStream = new(); + + DocumentWriter gamedataDocWriter = new(); + gamedataDocWriter.WriteFileDBToStream(generatedFileDB, fileDbStream); + + if (fileDbStream.Position > 0) + { + fileDbStream.Seek(0, SeekOrigin.Begin); + } + + RDABlockCreator.FileType_CompressedExtensions.Add(".data"); + + using (RDAReader rdaReader = new()) + using (BinaryReader reader = new(fileDbStream)) + { + RDAFolder rdaFolder = new(FileHeader.Version.Version_2_2); + + rdaReader.rdaFolder = rdaFolder; + DirEntry gamedataFileDirEntry = new() + { + filename = RDAFile.FileNameToRDAFileName("gamedata.data", ""), + offset = 0, + compressed = (ulong)fileDbStream.Length, + filesize = (ulong)fileDbStream.Length, + timestamp = RDAExplorer.Misc.DateTimeExtension.ToTimeStamp(DateTime.Now), + }; + + BlockInfo gamedataFileBlockInfo = new() + { + flags = 0, + fileCount = 1, + directorySize = (ulong)fileDbStream.Length, + decompressedSize = (ulong)fileDbStream.Length, + nextBlock = 0 + }; + + RDAFile rdaFile = RDAFile.FromUnmanaged(FileHeader.Version.Version_2_2, gamedataFileDirEntry, gamedataFileBlockInfo, reader, null); + rdaFolder.AddFiles(new List() { rdaFile }); + RDAWriter writer = new(rdaFolder); + bool compress = true; + writer.Write(a7tPath, FileHeader.Version.Version_2_2, compress, rdaReader, null); + + } + + RDABlockCreator.FileType_CompressedExtensions.Remove(".data"); + } + + private static void WriteA7te(int mapRadius, string a7tePath) + { + AnnoEditorLevel a7te = new(mapRadius); + + XmlSerializer a7teSerializer = new(typeof(AnnoEditorLevel)); + XmlWriterSettings xmlSettings = new() { Indent = true, IndentChars = " ", OmitXmlDeclaration = true, Async = true }; + XmlSerializerNamespaces noNamespaces = new(new XmlQualifiedName[] { XmlQualifiedName.Empty }); + + using StreamWriter streamWriter = new(a7tePath, false, Encoding.UTF8); + using XmlWriter xmlWriter = XmlWriter.Create(streamWriter, xmlSettings); + + a7teSerializer.Serialize(xmlWriter, a7te, noNamespaces); + } + + private static async Task WriteLanguageXml(string modPath, string name, string guid) + { + string[] languages = new string[] { "chinese", "english", "french", "german", "italian", "japanese", "korean", "polish", "russian", "spanish", "taiwanese" }; + + foreach (var language in languages) + { + string languageXmlPath = Path.Combine(modPath, $@"data\config\gui\texts_{language}.xml"); + string? languageXmlDir = Path.GetDirectoryName(languageXmlPath); + if (languageXmlDir is not null) + Directory.CreateDirectory(languageXmlDir); + + string content = + $"\n" + + $" \n" + + $" {name}\n" + + $" \n" + + $"\n"; + + using StreamWriter writer = new(File.Create(languageXmlPath)); + await writer.WriteAsync(content); + } + } + + + private static string MakeSafeName(string unsafeName) => new Regex(@"\W").Replace(unsafeName, "_").ToLower(); + } +} \ No newline at end of file diff --git a/AnnoMapEditor/UI/Overlays/ExportAsMod/ExportAsModOverlay.xaml b/AnnoMapEditor/UI/Overlays/ExportAsMod/ExportAsModOverlay.xaml index bf3cf4e..75d552f 100644 --- a/AnnoMapEditor/UI/Overlays/ExportAsMod/ExportAsModOverlay.xaml +++ b/AnnoMapEditor/UI/Overlays/ExportAsMod/ExportAsModOverlay.xaml @@ -33,6 +33,7 @@ Works only with new save games. + @@ -42,28 +43,49 @@ + - - - - + + + + + + + + + + + + + + + + - + Select the following map type in Sandbox mode: @@ -79,10 +101,10 @@ - The map mod will automatically apply to the selected region. + Visibility="{Binding ShowMapTypeSelection, Mode=OneWay, Converter={StaticResource InvertedBoolToVisibility}}"> + The map mod will automatically apply to the selected session. - Please make sure that only one Map Mod per non-moderate Region is active. + Please make sure that at most one Map Mod per session is active. diff --git a/AnnoMapEditor/UI/Overlays/ExportAsMod/ExportAsModViewModel.cs b/AnnoMapEditor/UI/Overlays/ExportAsMod/ExportAsModViewModel.cs index 9452da2..c63e36b 100644 --- a/AnnoMapEditor/UI/Overlays/ExportAsMod/ExportAsModViewModel.cs +++ b/AnnoMapEditor/UI/Overlays/ExportAsMod/ExportAsModViewModel.cs @@ -2,11 +2,14 @@ using AnnoMapEditor.MapTemplates.Models; using AnnoMapEditor.Mods.Enums; using AnnoMapEditor.Mods.Models; +using AnnoMapEditor.Mods.Serialization; using AnnoMapEditor.Utilities; +using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; +using System.Windows; namespace AnnoMapEditor.UI.Overlays.ExportAsMod { @@ -21,27 +24,7 @@ enum ModStatus Inactive } - public MapTemplate? MapTemplate - { - get => _mapTemplate; - set - { - _mapTemplate = value; - if(_mapTemplate is not null) - { - AllowedMapTypes = MapType.All; - SelectedMapType = AllowedMapTypes.First(); - } - else - { - AllowedMapTypes = Enumerable.Empty(); - SelectedMapType = null; - } - InfoMapTypeSelection = _mapTemplate is not null && _mapTemplate.Session == SessionAsset.OldWorld; - CheckExistingMod(); - } - } - MapTemplate? _mapTemplate; + public MapTemplate MapTemplate { get; init; } public bool IsSaving { @@ -67,12 +50,7 @@ public string ModName public string ModExistsWarning { get; private set; } = string.Empty; - public bool InfoMapTypeSelection - { - get => _infoMapTypeSelection; - set => SetProperty(ref _infoMapTypeSelection, value); - } - private bool _infoMapTypeSelection = true; + public bool ShowMapTypeSelection { get; init; } public string ModID { @@ -92,24 +70,21 @@ public MapType? SelectedMapType } private MapType? _mapType = null; - public IEnumerable AllowedMapTypes - { - get => _allowedMapTypes; - set => SetProperty(ref _allowedMapTypes, value); - } - private IEnumerable _allowedMapTypes = Enumerable.Empty(); + public IEnumerable AllowedMapTypes { get; init; } = MapType.All; - public ExportAsModViewModel() - { - Settings.Instance.PropertyChanged += Settings_PropertyChanged; - } - private void Settings_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) + public ExportAsModViewModel(MapTemplate mapTemplate) { - if (e.PropertyName == nameof(Settings.IsLoading)) - CheckExistingMod(); + MapTemplate = mapTemplate; + + ShowMapTypeSelection = mapTemplate.Session == SessionAsset.OldWorld; + if (ShowMapTypeSelection) + SelectedMapType = AllowedMapTypes.First(); + + CheckExistingMod(); } + private void CheckExistingMod() { ResultingModName = (_modName.Trim() == string.Empty ? $"Custom {_mapType}" : _modName); @@ -138,27 +113,43 @@ private static ModStatus ModExists(string modName) public async Task Save() { - IsSaving = true; - string? modsFolderPath = Settings.Instance.DataPath; if (modsFolderPath is not null) modsFolderPath = Path.Combine(modsFolderPath, "mods"); - if (!Directory.Exists(modsFolderPath) || MapTemplate is null) + if (!Directory.Exists(modsFolderPath)) { - _logger.LogWarning("mods/ path or session not set. This shouldn't have happened."); + _logger.LogWarning("mods/ path not set. This shouldn't have happened."); return false; } - Mod mod = new(MapTemplate, SelectedMapType); + Mod mod = new(ResultingModName, ModID, MapTemplate, SelectedMapType); CheckExistingMod(); - bool result = await mod.Save(Path.Combine(modsFolderPath, ResultingFullModName), ResultingModName, ModID); - OverlayService.Instance.Close(this); + try + { + IsSaving = true; + ModWriter modWriter = new(); + await modWriter.WriteAsync(mod, Path.Combine(modsFolderPath, ResultingFullModName)); - IsSaving = false; - return result; + OverlayService.Instance.Close(this); + return true; + } + catch (UnauthorizedAccessException) + { + MessageBox.Show("Failed to save the mod.\n\nIt looks like some files are locked, possibly by another application.\n\nThe mod may be broken now.", App.TitleShort, MessageBoxButton.OK, MessageBoxImage.Exclamation); + return false; + } + catch (Exception) + { + MessageBox.Show("Failed to save the mod.\n\nThe mod may be broken now.", App.TitleShort, MessageBoxButton.OK, MessageBoxImage.Exclamation); + return false; + } + finally + { + IsSaving = false; + } } } } diff --git a/AnnoMapEditor/UI/Windows/Main/MainWindow.xaml.cs b/AnnoMapEditor/UI/Windows/Main/MainWindow.xaml.cs index c1577f0..828e64b 100644 --- a/AnnoMapEditor/UI/Windows/Main/MainWindow.xaml.cs +++ b/AnnoMapEditor/UI/Windows/Main/MainWindow.xaml.cs @@ -172,10 +172,7 @@ private void ExportMod_Click(object sender, RoutedEventArgs e) if (ViewModel.MapTemplate is null) return; - OverlayService.Instance.Show(new ExportAsModViewModel() - { - MapTemplate = ViewModel.MapTemplate - }); + OverlayService.Instance.Show(new ExportAsModViewModel(ViewModel.MapTemplate)); } private void NewMapFile_Click(object sender, RoutedEventArgs e) diff --git a/AnnoMapEditor/UI/Windows/Main/MainWindowViewModel.cs b/AnnoMapEditor/UI/Windows/Main/MainWindowViewModel.cs index 1e7d206..38b4976 100644 --- a/AnnoMapEditor/UI/Windows/Main/MainWindowViewModel.cs +++ b/AnnoMapEditor/UI/Windows/Main/MainWindowViewModel.cs @@ -188,9 +188,9 @@ public async Task SaveMap(string filePath) MapTemplateWriter mapTemplateWriter = new(); if (Path.GetExtension(filePath).ToLower() == ".a7tinfo") - await mapTemplateWriter.ToA7tinfoAsync(MapTemplate, filePath); + await mapTemplateWriter.WriteA7tinfoAsync(MapTemplate, filePath); else - await mapTemplateWriter.ToXmlAsync(MapTemplate, filePath); + await mapTemplateWriter.WriteXmlAsync(MapTemplate, filePath); } private void UpdateExportStatus() From fadfee5f58664a9019ad917c33e2adc6dda714f9 Mon Sep 17 00:00:00 2001 From: Christopher Cyclonit Klinge Date: Mon, 12 Jun 2023 12:00:35 +0200 Subject: [PATCH 06/16] Move imprint from MainWindow in dedicated control for reuse. --- AnnoMapEditor/UI/Controls/Imprint.xaml | 25 ++++++++++++++++++ AnnoMapEditor/UI/Controls/Imprint.xaml.cs | 26 +++++++++++++++++++ AnnoMapEditor/UI/Windows/Main/MainWindow.xaml | 14 +--------- 3 files changed, 52 insertions(+), 13 deletions(-) create mode 100644 AnnoMapEditor/UI/Controls/Imprint.xaml create mode 100644 AnnoMapEditor/UI/Controls/Imprint.xaml.cs diff --git a/AnnoMapEditor/UI/Controls/Imprint.xaml b/AnnoMapEditor/UI/Controls/Imprint.xaml new file mode 100644 index 0000000..01276a9 --- /dev/null +++ b/AnnoMapEditor/UI/Controls/Imprint.xaml @@ -0,0 +1,25 @@ + + + + + + + + The editor is work in progress. + + Visit GitHub for + roadmap + and + issue reporting. + + diff --git a/AnnoMapEditor/UI/Controls/Imprint.xaml.cs b/AnnoMapEditor/UI/Controls/Imprint.xaml.cs new file mode 100644 index 0000000..21fae12 --- /dev/null +++ b/AnnoMapEditor/UI/Controls/Imprint.xaml.cs @@ -0,0 +1,26 @@ +using System.Diagnostics; +using System.Windows.Controls; +using System.Windows.Navigation; + +namespace AnnoMapEditor.UI.Controls +{ + /// + /// Interaction logic for Imprint.xaml + /// + public partial class Imprint : UserControl + { + public Imprint() + { + InitializeComponent(); + } + + private void Hyperlink_OpenBrowser(object sender, RequestNavigateEventArgs e) + { + var info = new ProcessStartInfo(e.Uri.AbsoluteUri) + { + UseShellExecute = true, + }; + Process.Start(info); + } + } +} diff --git a/AnnoMapEditor/UI/Windows/Main/MainWindow.xaml b/AnnoMapEditor/UI/Windows/Main/MainWindow.xaml index 02b6c28..ff65820 100644 --- a/AnnoMapEditor/UI/Windows/Main/MainWindow.xaml +++ b/AnnoMapEditor/UI/Windows/Main/MainWindow.xaml @@ -103,19 +103,7 @@ - - The editor is work in progress. - - Visit GitHub for - roadmap - and - issue reporting. - + Date: Mon, 12 Jun 2023 18:35:29 +0200 Subject: [PATCH 07/16] Rename Settings.DataPath to GamePath to reflect its actual value. Add DataPath and ModsPath as separate UserSettings. Refactored DataArchive and mod export to use the new settings. --- .../Utils/TheoryWithGameFilesAttribute.cs | 2 +- AnnoMapEditor/DataArchives/DataArchive.cs | 30 ++----- AnnoMapEditor/DataArchives/RdaDataArchive.cs | 4 +- .../ExportAsMod/ExportAsModViewModel.cs | 16 ++-- .../UI/Windows/Main/DataPathStatus.cs | 2 +- AnnoMapEditor/UI/Windows/Main/MainWindow.xaml | 10 +-- .../UI/Windows/Main/MainWindow.xaml.cs | 18 ++-- .../UI/Windows/Main/MainWindowViewModel.cs | 18 ++-- AnnoMapEditor/UserSettings.Designer.cs | 24 +++++ AnnoMapEditor/UserSettings.settings | 6 ++ AnnoMapEditor/Utilities/Settings.cs | 90 ++++++++++++++----- 11 files changed, 135 insertions(+), 85 deletions(-) diff --git a/AnnoMapEditor.Tests/Utils/TheoryWithGameFilesAttribute.cs b/AnnoMapEditor.Tests/Utils/TheoryWithGameFilesAttribute.cs index 9e3cc7f..9d69643 100644 --- a/AnnoMapEditor.Tests/Utils/TheoryWithGameFilesAttribute.cs +++ b/AnnoMapEditor.Tests/Utils/TheoryWithGameFilesAttribute.cs @@ -13,7 +13,7 @@ public TheoryWithGameFilesAttribute() Utilities.Settings.Instance.WaitForLoadingBlocking(); waitTimer.Stop(); - if (!Utilities.Settings.Instance.IsValidDataPath) + if (!Utilities.Settings.Instance.IsValidGamePath) { Skip = "The curring test environment has not detected the game files required for this unit test. " + $"Waited {waitTimer.ElapsedMilliseconds}ms for this information..."; diff --git a/AnnoMapEditor/DataArchives/DataArchive.cs b/AnnoMapEditor/DataArchives/DataArchive.cs index 1369042..f2fb4e0 100644 --- a/AnnoMapEditor/DataArchives/DataArchive.cs +++ b/AnnoMapEditor/DataArchives/DataArchive.cs @@ -22,25 +22,20 @@ public abstract class DataArchive : ObservableBase, IDataArchive public abstract string DataPath { get; } - public static async Task OpenAsync(string? folderPath) + public static async Task OpenAsync(string? dataPath) { - if (folderPath is null) - return Default; - - var adjustedPath = AdjustDataPath(folderPath); - - if (adjustedPath is null) + if (dataPath is null) return Default; IDataArchive archive; - if (File.Exists(Path.Combine(adjustedPath, "maindata/data0.rda"))) + if (File.Exists(Path.Combine(dataPath, "data0.rda"))) { - RdaDataArchive rdaArchive = new RdaDataArchive(adjustedPath); + RdaDataArchive rdaArchive = new RdaDataArchive(dataPath); await rdaArchive.LoadAsync(); archive = rdaArchive; } else - archive = new FolderDataArchive(adjustedPath); + archive = new FolderDataArchive(dataPath); return archive; } @@ -50,21 +45,6 @@ public static async Task OpenAsync(string? folderPath) public abstract IEnumerable Find(string pattern); - private static string? AdjustDataPath(string? path) - { - if (path is null) - return null; - if (File.Exists(Path.Combine(path, "maindata/data0.rda"))) - return path; - if (File.Exists(Path.Combine(path, "data0.rda"))) - return Path.GetDirectoryName(path); - if (Directory.Exists(Path.Combine(path, "data/dlc01"))) - return path; - if (Directory.Exists(Path.Combine(path, "dlc01"))) - return Path.GetDirectoryName(path); - return null; - } - public ImageSource? TryLoadIcon(string iconPath, Point? desiredSize = null) { // Icons are referenced as .png but stored as .dds. diff --git a/AnnoMapEditor/DataArchives/RdaDataArchive.cs b/AnnoMapEditor/DataArchives/RdaDataArchive.cs index 872cf82..7b3d692 100644 --- a/AnnoMapEditor/DataArchives/RdaDataArchive.cs +++ b/AnnoMapEditor/DataArchives/RdaDataArchive.cs @@ -48,7 +48,7 @@ public async Task LoadAsync() IsValid = false; _logger.LogInformation($"Discovering RDA archives at '{DataPath}'."); var builder = FileSystemBuilder.Create() - .FromPath(System.IO.Path.Combine(DataPath, "maindata")) + .FromPath(DataPath) .OnlyArchivesMatchingWildcard("data*.rda") .WithDefaultSorting() .ConfigureLoadZeroByteFiles(false) @@ -70,7 +70,7 @@ await Task.Run(() => var loadedCount = builder.ArchiveFileNames.Count(); if (loadedCount == 0) { - _logger.LogWarning($"No .rda files found at {System.IO.Path.Combine(DataPath, "maindata")}"); + _logger.LogWarning($"No .rda files found at {DataPath}"); MessageBox.Show($"Something went wrong opening the RDA files.\n\nDo you have another Editor or the RDAExplorer open by any chance?\n\nLog file: {Log.LogFilePath}", App.TitleShort, MessageBoxButton.OK, MessageBoxImage.Exclamation); } _logger.LogInformation($"Loaded {loadedCount} RDAs."); diff --git a/AnnoMapEditor/UI/Overlays/ExportAsMod/ExportAsModViewModel.cs b/AnnoMapEditor/UI/Overlays/ExportAsMod/ExportAsModViewModel.cs index c63e36b..ae0ccb8 100644 --- a/AnnoMapEditor/UI/Overlays/ExportAsMod/ExportAsModViewModel.cs +++ b/AnnoMapEditor/UI/Overlays/ExportAsMod/ExportAsModViewModel.cs @@ -100,28 +100,24 @@ private void CheckExistingMod() private static ModStatus ModExists(string modName) { - if (Settings.Instance.DataPath is null) + if (Settings.Instance.GamePath is null) return ModStatus.NotFound; - string activeModPath = Path.Combine(Settings.Instance.DataPath, "mods", modName); + string activeModPath = Path.Combine(Settings.Instance.ModsPath!, modName); if (Directory.Exists(activeModPath)) return ModStatus.Active; - string inactiveModPath = Path.Combine(Settings.Instance.DataPath, "mods", "-" + modName); + string inactiveModPath = Path.Combine(Settings.Instance.ModsPath!, "-" + modName); return Directory.Exists(inactiveModPath) ? ModStatus.Inactive : ModStatus.NotFound; } public async Task Save() { - string? modsFolderPath = Settings.Instance.DataPath; - if (modsFolderPath is not null) - modsFolderPath = Path.Combine(modsFolderPath, "mods"); + string modsFolderPath = Settings.Instance.ModsPath + ?? throw new Exception($"ModsPath is not set."); if (!Directory.Exists(modsFolderPath)) - { - _logger.LogWarning("mods/ path not set. This shouldn't have happened."); - return false; - } + throw new Exception($"Mods directory '{modsFolderPath}' does not exist."); Mod mod = new(ResultingModName, ModID, MapTemplate, SelectedMapType); diff --git a/AnnoMapEditor/UI/Windows/Main/DataPathStatus.cs b/AnnoMapEditor/UI/Windows/Main/DataPathStatus.cs index 7bedb5f..01ea7a4 100644 --- a/AnnoMapEditor/UI/Windows/Main/DataPathStatus.cs +++ b/AnnoMapEditor/UI/Windows/Main/DataPathStatus.cs @@ -2,7 +2,7 @@ namespace AnnoMapEditor.UI.Windows.Main { - public class DataPathStatus + public class GamePathStatus { public string Status { get; set; } = string.Empty; diff --git a/AnnoMapEditor/UI/Windows/Main/MainWindow.xaml b/AnnoMapEditor/UI/Windows/Main/MainWindow.xaml index ff65820..d2cebdd 100644 --- a/AnnoMapEditor/UI/Windows/Main/MainWindow.xaml +++ b/AnnoMapEditor/UI/Windows/Main/MainWindow.xaml @@ -94,14 +94,14 @@ Orientation="Horizontal" VerticalAlignment="Bottom" Margin="8,0,0,0"> - + + Visibility="{Binding GamePathStatus.AutoDetect, UpdateSourceTrigger=PropertyChanged}">Auto detect diff --git a/AnnoMapEditor/UI/Windows/Main/MainWindow.xaml.cs b/AnnoMapEditor/UI/Windows/Main/MainWindow.xaml.cs index 828e64b..576dccb 100644 --- a/AnnoMapEditor/UI/Windows/Main/MainWindow.xaml.cs +++ b/AnnoMapEditor/UI/Windows/Main/MainWindow.xaml.cs @@ -46,14 +46,14 @@ private void ViewModel_PropertyChanged(object? sender, System.ComponentModel.Pro { switch (e.PropertyName) { - case "MapTemplate": + case nameof(MainWindowViewModel.MapTemplate): if (ViewModel?.MapTemplate is not null) Title = $"{title} - {Path.GetFileName(ViewModel.MapTemplateFilePath)}"; else Title = title; break; - case "Maps": - case "IsValidDataPath": + + case nameof(MainWindowViewModel.Maps): CreateImportMenu(openMapMenu, ViewModel?.Maps); break; } @@ -68,13 +68,13 @@ private async void OpenFile_Click(object sender, RoutedEventArgs e) if (true == picker.ShowDialog()) { - if (!Settings.Instance.IsValidDataPath) + if (!Settings.Instance.IsValidGamePath) { int end = picker.FileName.IndexOf(@"\data\session"); if (end == -1) end = picker.FileName.IndexOf(@"\data\dlc"); if (end != -1) - Settings.Instance.DataPath = picker.FileName[..end]; + Settings.Instance.GamePath = picker.FileName[..end]; } await ViewModel.OpenMap(picker.FileName); @@ -89,18 +89,18 @@ private void Configure_Click(object _, RoutedEventArgs _1) Description = "Select your game (i.e. \"Anno 1800/\") folder or a folder where all .rda files are extracted into" }; - if (Settings.Instance.DataPath != null) - picker.SelectedPath = Settings.Instance.DataPath; + if (Settings.Instance.GamePath != null) + picker.SelectedPath = Settings.Instance.GamePath; if (true == picker.ShowDialog()) { - Settings.Instance.DataPath = picker.SelectedPath; + Settings.Instance.GamePath = picker.SelectedPath; } } private void AutoDetect_Click(object _, RoutedEventArgs _1) { - Settings.Instance.DataPath = Settings.GetInstallDirFromRegistry(); + Settings.Instance.GamePath = Settings.GetInstallDirFromRegistry(); } private void CreateImportMenu(ContextMenu parentMenu, List? mapGroups) diff --git a/AnnoMapEditor/UI/Windows/Main/MainWindowViewModel.cs b/AnnoMapEditor/UI/Windows/Main/MainWindowViewModel.cs index 38b4976..f68011d 100644 --- a/AnnoMapEditor/UI/Windows/Main/MainWindowViewModel.cs +++ b/AnnoMapEditor/UI/Windows/Main/MainWindowViewModel.cs @@ -110,12 +110,12 @@ public string? MapTemplateFilePath } private string? _mapTemplateFilePath; - public DataPathStatus DataPathStatus + public GamePathStatus GamePathStatus { - get => _dataPathStatus; - private set => SetProperty(ref _dataPathStatus, value); + get => _gamePathStatus; + private set => SetProperty(ref _gamePathStatus, value); } - private DataPathStatus _dataPathStatus = new(); + private GamePathStatus _gamePathStatus = new(); public ExportStatus ExportStatus { @@ -204,7 +204,7 @@ private void UpdateExportStatus() ExportAsModText = "(loading RDA...)" }; } - else if (Settings.IsValidDataPath) + else if (Settings.IsValidGamePath) { bool archiveReady = Settings.DataArchive is RdaDataArchive; @@ -229,7 +229,7 @@ private void UpdateStatusAndMenus() if (Settings.IsLoading) { // still loading - DataPathStatus = new DataPathStatus() + GamePathStatus = new GamePathStatus() { Status = "loading RDA...", ToolTip = "", @@ -237,9 +237,9 @@ private void UpdateStatusAndMenus() AutoDetect = Visibility.Collapsed, }; } - else if (Settings.IsValidDataPath) + else if (Settings.IsValidGamePath) { - DataPathStatus = new DataPathStatus() + GamePathStatus = new GamePathStatus() { Status = Settings.DataArchive is RdaDataArchive ? "Game path set ✔" : "Extracted RDA path set ✔", ToolTip = Settings.DataArchive.DataPath, @@ -274,7 +274,7 @@ private void UpdateStatusAndMenus() } else { - DataPathStatus = new DataPathStatus() + GamePathStatus = new GamePathStatus() { Status = "⚠ Game or RDA path not valid.", ToolTip = null, diff --git a/AnnoMapEditor/UserSettings.Designer.cs b/AnnoMapEditor/UserSettings.Designer.cs index 997498e..d3f64a8 100644 --- a/AnnoMapEditor/UserSettings.Designer.cs +++ b/AnnoMapEditor/UserSettings.Designer.cs @@ -23,6 +23,18 @@ public static UserSettings Default { } } + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string GamePath { + get { + return ((string)(this["GamePath"])); + } + set { + this["GamePath"] = value; + } + } + [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("")] @@ -35,6 +47,18 @@ public string DataPath { } } + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string ModsPath { + get { + return ((string)(this["ModsPath"])); + } + set { + this["ModsPath"] = value; + } + } + [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("")] diff --git a/AnnoMapEditor/UserSettings.settings b/AnnoMapEditor/UserSettings.settings index 2cbd714..e7419d8 100644 --- a/AnnoMapEditor/UserSettings.settings +++ b/AnnoMapEditor/UserSettings.settings @@ -2,9 +2,15 @@ + + + + + + diff --git a/AnnoMapEditor/Utilities/Settings.cs b/AnnoMapEditor/Utilities/Settings.cs index da531c4..dd9a315 100644 --- a/AnnoMapEditor/Utilities/Settings.cs +++ b/AnnoMapEditor/Utilities/Settings.cs @@ -4,6 +4,7 @@ using Microsoft.Win32; using System; using System.ComponentModel; +using System.IO; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -30,7 +31,7 @@ private set if (_dataArchive is System.IDisposable disposable) disposable.Dispose(); SetProperty(ref _dataArchive, value); - OnPropertyChanged(nameof(DataPath)); + OnPropertyChanged(nameof(GamePath)); } } @@ -51,28 +52,59 @@ public AssetRepository? AssetRepository private AssetRepository? _assetRepository; - public string? DataPath + public string? GamePath + { + get => UserSettings.Default.GamePath; + set + { + if (value != GamePath) + { + UserSettings.Default.GamePath = value; + UserSettings.Default.Save(); + + DataPath = value != null ? Path.Combine(value, "maindata") : null; + ModsPath = value != null ? Path.Combine(value, "mods") : null; + } + } + } + + public string? DataPath { - get => _dataArchive.DataPath; + get => UserSettings.Default.DataPath; set { if (value != DataPath) { - LoadDataPath(value); - UserSettings.Default.DataPath = DataArchive.DataPath; + UserSettings.Default.DataPath = value; UserSettings.Default.Save(); OnPropertyChanged(nameof(DataPath)); - OnPropertyChanged(nameof(IsValidDataPath)); + + LoadDataPath(value); } } } - public bool IsValidDataPath + public string? ModsPath { - get => _isValidDataPath; - private set => SetProperty(ref _isValidDataPath, value); + get => UserSettings.Default.ModsPath; + set + { + if (value != ModsPath) + { + UserSettings.Default.ModsPath = value; + UserSettings.Default.Save(); + OnPropertyChanged(nameof(ModsPath)); + } + } + } + + + public bool IsValidGamePath + { + get => _isValidGamePath; + private set => SetProperty(ref _isValidGamePath, value); } - private bool _isValidDataPath = false; + private bool _isValidGamePath = false; public bool IsLoading { @@ -94,6 +126,8 @@ public bool IsLoading ///
private Settings() { + + //We set the LoadingDoneTrigger as Reset by default, so anything that waits for the setup to finish //has to actually wait for the SetupAsync called in Initialize to finish. LoadingDoneTrigger = new ManualResetEvent(false); @@ -113,8 +147,8 @@ private void Initialize() } /// - /// This avoids direct writes to the DataPath Property that would run - /// its setter on the thread pool, making proper awaiting of the setup impossible. + /// This avoids direct writes to the path properties that would run their + /// setters on the thread pool, making proper awaiting of the setup impossible. /// /// If the UserSettings should be overwritten by a valid result. /// @@ -138,8 +172,7 @@ private async Task SetupAsync(bool updateInvalidUserSettings) if (DataArchive?.IsValid == true && updateInvalidUserSettings) { - UserSettings.Default.DataPath = DataArchive.DataPath; - UserSettings.Default.Save(); + DataPath = DataArchive.DataPath; } LoadingDoneTrigger.Set(); @@ -149,22 +182,22 @@ private async Task SetupAsync(bool updateInvalidUserSettings) /// Used to set DataPath with a synchronous method call /// by running the actual async code on the tread pool. /// - /// The DataPath to load. - private void LoadDataPath(string? path) + /// The DataPath to load. + private void LoadDataPath(string? dataPath) { IsLoading = true; _loadingTask = Task.Run(async () => { - await LoadDataPathAsync(path); + await LoadDataPathAsync(dataPath); }); } /// /// Asynchronously sets the DataPath and reads all the Repositories. /// - /// The DataPath to load. + /// The DataPath to load. /// - private async Task LoadDataPathAsync(string? path) + private async Task LoadDataPathAsync(string? dataPath) { //Only use the LoadingDoneTrigger, if it is not already Reset. //A reset LoadingDoneTrigger here means, that the Setup is using it. @@ -180,7 +213,7 @@ private async Task LoadDataPathAsync(string? path) IsLoading = true; }); - var archive = await DataArchives.DataArchive.OpenAsync(path); + var archive = await DataArchives.DataArchive.OpenAsync(dataPath); try { @@ -205,7 +238,7 @@ private async Task LoadDataPathAsync(string? path) DataArchive = archive; AssetRepository = assetRepository; IslandRepository = islandRepository; - IsValidDataPath = true; + IsValidGamePath = true; }); } catch (Exception ex) @@ -213,7 +246,7 @@ private async Task LoadDataPathAsync(string? path) _logger.LogError($"An error occured during setup.", ex); Dispatch(() => { - IsValidDataPath = false; + IsValidGamePath = false; }); } finally @@ -248,7 +281,18 @@ private void Dispatch(Action action) { string installDirKey = @"SOFTWARE\WOW6432Node\Ubisoft\Anno 1800"; using RegistryKey? key = Registry.LocalMachine.OpenSubKey(installDirKey); - return key?.GetValue("InstallDir") as string; + + string? installDir = key?.GetValue("InstallDir") as string; + if (installDir == null) + return null; + + if (!installDir.Contains(Path.DirectorySeparatorChar)) + { + char wrongSeparator = Path.DirectorySeparatorChar == '/' ? '\\' : '/'; + return installDir.Replace(wrongSeparator, Path.DirectorySeparatorChar); + } + else + return installDir; } /// From ef14bb8466e9c96ff09e0255afcbd33bbd701f48 Mon Sep 17 00:00:00 2001 From: Christopher Cyclonit Klinge Date: Mon, 12 Jun 2023 21:46:36 +0200 Subject: [PATCH 08/16] Extract instances for Repositories and DataArchive from Settings to DataManager. Remove async logic from within Repositories and DataArchive for simpler setup. --- .../Assets/Repositories/AssetRepository.cs | 4 +- .../Repositories/FixedIslandRepository.cs | 46 +--- .../Assets/Repositories/IslandRepository.cs | 15 +- .../Assets/Repositories/Repository.cs | 38 +-- AnnoMapEditor/DataArchives/DataArchive.cs | 41 ++-- .../DataArchives/DataArchiveFactory.cs | 76 ++++++ AnnoMapEditor/DataArchives/DataManager.cs | 116 +++++++++ .../DataArchives/FolderDataArchive.cs | 28 +-- AnnoMapEditor/DataArchives/IDataArchive.cs | 6 - .../DataArchives/IDataArchiveFactory.cs | 7 + .../DataArchives/InvalidDataArchive.cs | 44 ---- AnnoMapEditor/DataArchives/RdaDataArchive.cs | 72 +----- .../MapTemplates/Models/FixedIslandElement.cs | 66 +---- .../Serializing/MapTemplateReader.cs | 3 +- AnnoMapEditor/Mods/Serialization/ModWriter.cs | 4 +- .../Controls/AddIsland/AddIslandViewModel.cs | 4 - .../MapTemplates/MapElementViewModel.cs | 2 +- .../SelectFertilitiesViewModel.cs | 5 +- .../SelectIsland/SelectIslandViewModel.cs | 5 +- AnnoMapEditor/UI/Windows/Main/MainWindow.xaml | 4 +- .../UI/Windows/Main/MainWindow.xaml.cs | 58 ++--- .../UI/Windows/Main/MainWindowViewModel.cs | 187 +++----------- AnnoMapEditor/Utilities/Settings.cs | 231 +----------------- 23 files changed, 321 insertions(+), 741 deletions(-) create mode 100644 AnnoMapEditor/DataArchives/DataArchiveFactory.cs create mode 100644 AnnoMapEditor/DataArchives/DataManager.cs create mode 100644 AnnoMapEditor/DataArchives/IDataArchiveFactory.cs delete mode 100644 AnnoMapEditor/DataArchives/InvalidDataArchive.cs diff --git a/AnnoMapEditor/DataArchives/Assets/Repositories/AssetRepository.cs b/AnnoMapEditor/DataArchives/Assets/Repositories/AssetRepository.cs index d3dfae3..2928380 100644 --- a/AnnoMapEditor/DataArchives/Assets/Repositories/AssetRepository.cs +++ b/AnnoMapEditor/DataArchives/Assets/Repositories/AssetRepository.cs @@ -103,7 +103,7 @@ private void RenewCache(IEnumerable assets) doc.Save(File.Create(CachedAssetsXml)); } - protected override async Task DoLoad() + public override async Task Initialize() { _logger.LogInformation($"Loading assets..."); @@ -161,7 +161,7 @@ protected override async Task DoLoad() } } - _logger.LogInformation($"Finished Deserialising at {watch.Elapsed.TotalMilliseconds} ms"); + _logger.LogInformation($"Finished Deserialising at {watch.Elapsed.TotalMilliseconds} ms."); _logger.LogInformation($"Resolving asset references..."); // resolve references diff --git a/AnnoMapEditor/DataArchives/Assets/Repositories/FixedIslandRepository.cs b/AnnoMapEditor/DataArchives/Assets/Repositories/FixedIslandRepository.cs index 231607d..a414581 100644 --- a/AnnoMapEditor/DataArchives/Assets/Repositories/FixedIslandRepository.cs +++ b/AnnoMapEditor/DataArchives/Assets/Repositories/FixedIslandRepository.cs @@ -32,11 +32,10 @@ public class FixedIslandRepository : Repository, INotifyCollectionChanged, IEnum public FixedIslandRepository(IDataArchive dataArchive) { _dataArchive = dataArchive; - LoadAsync(); } - protected override async Task DoLoad() + public override async Task Initialize() { _logger.LogInformation($"Begin loading fixed islands."); Stopwatch watch = Stopwatch.StartNew(); @@ -88,7 +87,7 @@ public FixedIslandAsset LoadIslandAsset(String mapFilePath) } catch (Exception e) { - throw new Exception($"Could not load a7minfo '{infoFilePath}'."); + throw new Exception($"Could not load a7minfo '{infoFilePath}'.", e); } // get the island's size in tiles @@ -142,14 +141,13 @@ public FixedIslandAsset LoadIslandAsset(String mapFilePath) } } - FixedIslandAsset fixedIsland = new() + return new() { FilePath = mapFilePath, SizeInTiles = sizeInTiles, Thumbnail = thumbnail, Slots = mineSlots }; - return fixedIsland; } public void Add(FixedIslandAsset fixedIsland) @@ -173,19 +171,6 @@ public bool TryGetByFilePath(string filePath, [NotNullWhen(false)] out FixedIsla #pragma warning restore CS8762 // Parameter must have a non-null value when exiting in some condition. } - - private int ReadSizeInTiles(string mapFilePath) - { - string infoFilePath = mapFilePath + "info"; - - IFileDBDocument? infoDoc = ReadFileDB(infoFilePath); - if (infoDoc?.Roots.FirstOrDefault(x => x.Name == "MapSize" && x.NodeType == FileDBNodeType.Attrib) is not Attrib mapSize) - return 0; - - int sizeInTiles = BitConverter.ToInt32(new ReadOnlySpan(mapSize.Content, 0, 4)); - return sizeInTiles; - } - public IFileDBDocument? ReadFileDB(string mapFilePath) { using Stream? stream = _dataArchive?.OpenRead(mapFilePath); @@ -210,31 +195,6 @@ private int ReadSizeInTiles(string mapFilePath) } } - private BitmapImage? ReadThumbnail(string mapFilePath) - { - string thumbnailPath = Path.Combine( - Path.GetDirectoryName(mapFilePath)!, - "_gamedata", - Path.GetFileNameWithoutExtension(mapFilePath), - "mapimage.png"); - - using Stream? stream = _dataArchive?.OpenRead(thumbnailPath); - if (stream == null) - { - _logger.LogWarning($"Could not load island thumbnail from '{mapFilePath}'. The file could not be found."); - return null; - } - - BitmapImage thumbnail = new(); - thumbnail.BeginInit(); - thumbnail.StreamSource = stream; - thumbnail.CacheOption = BitmapCacheOption.OnLoad; - thumbnail.EndInit(); - thumbnail.Freeze(); - - return thumbnail; - } - public IEnumerator GetEnumerator() => _islands.GetEnumerator(); diff --git a/AnnoMapEditor/DataArchives/Assets/Repositories/IslandRepository.cs b/AnnoMapEditor/DataArchives/Assets/Repositories/IslandRepository.cs index 8c0ea43..ee38b96 100644 --- a/AnnoMapEditor/DataArchives/Assets/Repositories/IslandRepository.cs +++ b/AnnoMapEditor/DataArchives/Assets/Repositories/IslandRepository.cs @@ -45,8 +45,6 @@ public IslandRepository(FixedIslandRepository fixedIslandRepository, AssetReposi { _fixedIslandRepository = fixedIslandRepository; _assetRepository = assetRepository; - - LoadAsync(); } @@ -67,17 +65,9 @@ public bool TryGetByFilePath(string mapFilePath, [NotNullWhen(false)] out Island => _byFilePath.TryGetValue(mapFilePath, out islandAsset); #pragma warning restore CS8762 // Parameter must have a non-null value when exiting in some condition. - protected override async Task DoLoad() + public override Task Initialize() { _logger.LogInformation($"Begin loading islands."); - _logger.LogInformation($"Waiting for random and fixed islands to be loaded."); - - // wait for both the random and fixed island repositories to be loaded - // TODO: BUG In theory both randomIslandRepositoryand fixedIslandRepository should be - // able to initialize at the same time. However, there occurs a deadlock when - // doing so. - await _assetRepository.AwaitLoadingAsync(); - await _fixedIslandRepository.AwaitLoadingAsync(); Dictionary randomByFilePath = _assetRepository .GetAll() @@ -92,7 +82,7 @@ protected override async Task DoLoad() randomByFilePath.TryGetValue(filePath, out RandomIslandAsset? randomIsland); IslandSize islandSize = IslandSize.All.FirstOrDefault(s => fixedIsland.SizeInTiles <= s.DefaultSizeInTiles)!; - + // resolve slot guids to assets ignoring WorkAreas foreach (Slot slot in fixedIsland.Slots.Values) { @@ -115,6 +105,7 @@ protected override async Task DoLoad() } _logger.LogInformation($"Finished loading {_fixedIslandRepository.Count()} islands."); + return Task.CompletedTask; } diff --git a/AnnoMapEditor/DataArchives/Assets/Repositories/Repository.cs b/AnnoMapEditor/DataArchives/Assets/Repositories/Repository.cs index c8575f8..85d4c3b 100644 --- a/AnnoMapEditor/DataArchives/Assets/Repositories/Repository.cs +++ b/AnnoMapEditor/DataArchives/Assets/Repositories/Repository.cs @@ -1,46 +1,14 @@ -using AnnoMapEditor.Utilities; -using System; -using System.Threading.Tasks; +using System.Threading.Tasks; namespace AnnoMapEditor.DataArchives.Assets.Repositories { - public abstract class Repository : ObservableBase + public abstract class Repository { - private Task? _loadingTask; - - public bool IsLoaded - { - get => _isLoading; - private set => SetProperty(ref _isLoading, value); - } - private bool _isLoading = false; - - public Repository() { } - public Task LoadAsync() - { - if (_loadingTask != null) - throw new InvalidOperationException(); - - _loadingTask = Task.Run(async () => { - await DoLoad(); - IsLoaded = true; - }); - return _loadingTask; - } - - protected abstract Task DoLoad(); - - public async Task AwaitLoadingAsync() - { - if (_loadingTask != null) - await _loadingTask; - else - throw new Exception($"LoadAsync has not been called."); - } + public abstract Task Initialize(); } } diff --git a/AnnoMapEditor/DataArchives/DataArchive.cs b/AnnoMapEditor/DataArchives/DataArchive.cs index f2fb4e0..672ce00 100644 --- a/AnnoMapEditor/DataArchives/DataArchive.cs +++ b/AnnoMapEditor/DataArchives/DataArchive.cs @@ -6,7 +6,6 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; -using System.Threading.Tasks; using System.Windows.Media; using System.Windows.Media.Imaging; @@ -14,37 +13,25 @@ namespace AnnoMapEditor.DataArchives { public abstract class DataArchive : ObservableBase, IDataArchive { - public static readonly IDataArchive Default = new InvalidDataArchive(""); - - - public abstract bool IsValid { get; protected set; } - - public abstract string DataPath { get; } + public abstract Stream? OpenRead(string path); + public abstract IEnumerable Find(string pattern); - public static async Task OpenAsync(string? dataPath) + private static string? AdjustDataPath(string? path) { - if (dataPath is null) - return Default; - - IDataArchive archive; - if (File.Exists(Path.Combine(dataPath, "data0.rda"))) - { - RdaDataArchive rdaArchive = new RdaDataArchive(dataPath); - await rdaArchive.LoadAsync(); - archive = rdaArchive; - } - else - archive = new FolderDataArchive(dataPath); - - return archive; + if (path is null) + return null; + if (File.Exists(Path.Combine(path, "maindata/data0.rda"))) + return path; + if (File.Exists(Path.Combine(path, "data0.rda"))) + return Path.GetDirectoryName(path); + if (Directory.Exists(Path.Combine(path, "data/dlc01"))) + return path; + if (Directory.Exists(Path.Combine(path, "dlc01"))) + return Path.GetDirectoryName(path); + return null; } - - public abstract Stream? OpenRead(string path); - - public abstract IEnumerable Find(string pattern); - public ImageSource? TryLoadIcon(string iconPath, Point? desiredSize = null) { // Icons are referenced as .png but stored as .dds. diff --git a/AnnoMapEditor/DataArchives/DataArchiveFactory.cs b/AnnoMapEditor/DataArchives/DataArchiveFactory.cs new file mode 100644 index 0000000..1316dc0 --- /dev/null +++ b/AnnoMapEditor/DataArchives/DataArchiveFactory.cs @@ -0,0 +1,76 @@ +using AnnoMapEditor.Utilities; +using AnnoRDA; +using AnnoRDA.Builder; +using System; + +namespace AnnoMapEditor.DataArchives +{ + public class DataArchiveFactory : IDataArchiveFactory + { + private static readonly Logger _logger = new(); + + private static readonly string[] EXTENSION_WHITELIST = new[] { "*.a7tinfo", "*.png", "*.a7minfo", "*.a7t", "*.a7te", "assets.xml", "*.a7m", "*.dds" }; + + + public IDataArchive CreateDataArchive(string dataPath) + { + // RdaDataArchive + try + { + _logger.LogInformation($"Trying to load '{dataPath}' as a RdaDataArchive."); + return CreateRdaDataArchive(dataPath); + } + catch (Exception ex) + { + _logger.LogWarning(ex.Message); + } + + // FolderDataArchive + try + { + _logger.LogInformation($"Trying to load '{dataPath}' as a FolderDataArchive."); + return CreateFolderDataArchive(dataPath); + } + catch (Exception ex) + { + _logger.LogWarning(ex.Message); + } + + throw new Exception($"Could not find valid Anno 1800 data at '{dataPath}'."); + } + + public IDataArchive CreateRdaDataArchive(string dataPath) + { + _logger.LogInformation($"Discovering RDA archives at '{dataPath}'."); + + FileSystemBuilder builder = FileSystemBuilder.Create() + .FromPath(dataPath) + .OnlyArchivesMatchingWildcard("data*.rda") + .WithDefaultSorting() + .ConfigureLoadZeroByteFiles(false) + .AddWhitelisted(EXTENSION_WHITELIST); + + FileSystem fileSystem; + try + { + fileSystem = builder.Build(); + } + catch (Exception e) + { + throw new Exception($"Could not build RdaFileSystem at '{dataPath}'.", e); + } + + var loadedCount = builder.ArchiveFileNames.Count; + if (loadedCount == 0) + throw new Exception($"Could not find any .rda files at '{dataPath}'."); + + _logger.LogInformation($"Loaded {loadedCount} RDAs."); + return new RdaDataArchive(fileSystem); + } + + public IDataArchive CreateFolderDataArchive(string dataPath) + { + return new FolderDataArchive(dataPath); + } + } +} diff --git a/AnnoMapEditor/DataArchives/DataManager.cs b/AnnoMapEditor/DataArchives/DataManager.cs new file mode 100644 index 0000000..a6f0f7a --- /dev/null +++ b/AnnoMapEditor/DataArchives/DataManager.cs @@ -0,0 +1,116 @@ +using AnnoMapEditor.DataArchives.Assets.Models; +using AnnoMapEditor.DataArchives.Assets.Repositories; +using AnnoMapEditor.Utilities; +using System; +using System.Threading.Tasks; +using System.Windows; + +namespace AnnoMapEditor.DataArchives +{ + public class DataManager : ObservableBase + { + private const string NOT_INITIALIZED_MESSAGE = "DataManager has not yet been initialized."; + + public static readonly Logger _logger = new(); + + public static readonly DataManager Instance = new(); + + + public bool IsInitialized + { + get => _isInitialized; + private set => SetProperty(ref _isInitialized, value); + } + private bool _isInitialized = false; + + public bool IsInitializing + { + get => _isInitializing; + private set => SetProperty(ref _isInitializing, value); + } + private bool _isInitializing = false; + + public string? ErrorMessage + { + get => _errorMessage; + private set => SetProperty(ref _errorMessage, value); + } + private string? _errorMessage; + + + public IDataArchive DataArchive => _isInitialized && _dataArchive != null ? _dataArchive : throw new Exception(NOT_INITIALIZED_MESSAGE); + private IDataArchive? _dataArchive; + + public AssetRepository AssetRepository => _isInitialized && _assetRepository != null ? _assetRepository : throw new Exception(NOT_INITIALIZED_MESSAGE); + private AssetRepository? _assetRepository; + + public FixedIslandRepository FixedIslandRepository => _isInitialized && _fixedIslandRepository != null ? _fixedIslandRepository : throw new Exception(NOT_INITIALIZED_MESSAGE); + private FixedIslandRepository? _fixedIslandRepository; + + public IslandRepository IslandRepository => _isInitialized && _islandRepository != null ? _islandRepository : throw new Exception(NOT_INITIALIZED_MESSAGE); + private IslandRepository? _islandRepository; + + + private DataManager() + { + + } + + + public async Task TryInitializeAsync(string dataPath) + { + UpdateStatus(isInitializing: true, isInitialized: false); + _logger.LogInformation($"Initializing DataManager at '{dataPath}'."); + + try + { + DataArchiveFactory dataArchiveFactory = new(); + _dataArchive = dataArchiveFactory.CreateDataArchive(dataPath); + + _assetRepository = new AssetRepository(_dataArchive); + _assetRepository.Register(); + _assetRepository.Register(); + _assetRepository.Register(); + _assetRepository.Register(); + _assetRepository.Register(); + _assetRepository.Register(); + _assetRepository.Register(); + await _assetRepository.Initialize(); + + _fixedIslandRepository = new FixedIslandRepository(_dataArchive); + await _fixedIslandRepository.Initialize(); + + _islandRepository = new IslandRepository(_fixedIslandRepository, _assetRepository); + await _islandRepository.Initialize(); + } + catch (Exception ex) + { + UpdateStatus(isInitializing: false, isInitialized: false, errorMessage: ex.Message); + _logger.LogInformation($"Could not initialize DataManager at '{dataPath}'."); + return; + } + + UpdateStatus(isInitializing: false, isInitialized: true); + _logger.LogInformation($"Successfully initialized DataManager at '{dataPath}'."); + } + + + private void Dispatch(Action action) + { + if (Application.Current?.Dispatcher != null) + Application.Current.Dispatcher.Invoke(action); + else + action(); + } + + private void UpdateStatus(bool isInitializing, bool isInitialized, string? errorMessage = null) + { + Dispatch(() => + { + IsInitializing = isInitializing; + IsInitialized = isInitialized; + ErrorMessage = errorMessage; + }); + } + } +} diff --git a/AnnoMapEditor/DataArchives/FolderDataArchive.cs b/AnnoMapEditor/DataArchives/FolderDataArchive.cs index 301ac45..d25607d 100644 --- a/AnnoMapEditor/DataArchives/FolderDataArchive.cs +++ b/AnnoMapEditor/DataArchives/FolderDataArchive.cs @@ -1,50 +1,42 @@ using Microsoft.Extensions.FileSystemGlobbing; using Microsoft.Extensions.FileSystemGlobbing.Abstractions; -using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Threading.Tasks; namespace AnnoMapEditor.DataArchives { public class FolderDataArchive : DataArchive { - public override string DataPath { get; } + public string DataPath { get; } - public override bool IsValid { get; protected set; } = true; - - public FolderDataArchive(string folderPath) + public FolderDataArchive(string dataPath) { - DataPath = folderPath; + DataPath = dataPath; } public override Stream? OpenRead(string filePath) { - if (!IsValid) - return null; - Stream? stream = null; - try { - stream = File.OpenRead(System.IO.Path.Combine(DataPath, filePath)); + return File.OpenRead(Path.Combine(DataPath, filePath)); + } + catch + { + return null; } - catch { } - return stream; } public override IEnumerable Find(string pattern) { - if (!IsValid) - return Array.Empty(); - Matcher matcher = new(); matcher.AddIncludePatterns(new string[] { pattern }); PatternMatchingResult result = matcher.Execute( - new DirectoryInfoWrapper(new DirectoryInfo(DataPath))); + new DirectoryInfoWrapper(new DirectoryInfo(DataPath)) + ); return result.Files.Select(x => x.Path); } diff --git a/AnnoMapEditor/DataArchives/IDataArchive.cs b/AnnoMapEditor/DataArchives/IDataArchive.cs index 9d5581c..48f910f 100644 --- a/AnnoMapEditor/DataArchives/IDataArchive.cs +++ b/AnnoMapEditor/DataArchives/IDataArchive.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.Drawing; using System.IO; -using System.Threading.Tasks; using System.Windows.Media; using System.Windows.Media.Imaging; @@ -9,11 +8,6 @@ namespace AnnoMapEditor.DataArchives { public interface IDataArchive { - bool IsValid { get; } - - string DataPath { get; } - - Stream? OpenRead(string filePath); IEnumerable Find(string pattern); diff --git a/AnnoMapEditor/DataArchives/IDataArchiveFactory.cs b/AnnoMapEditor/DataArchives/IDataArchiveFactory.cs new file mode 100644 index 0000000..2096b2d --- /dev/null +++ b/AnnoMapEditor/DataArchives/IDataArchiveFactory.cs @@ -0,0 +1,7 @@ +namespace AnnoMapEditor.DataArchives +{ + public interface IDataArchiveFactory + { + IDataArchive CreateDataArchive(string dataPath); + } +} diff --git a/AnnoMapEditor/DataArchives/InvalidDataArchive.cs b/AnnoMapEditor/DataArchives/InvalidDataArchive.cs deleted file mode 100644 index b804987..0000000 --- a/AnnoMapEditor/DataArchives/InvalidDataArchive.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.IO; -using System.Threading.Tasks; -using System.Windows.Media; -using System.Windows.Media.Imaging; - -namespace AnnoMapEditor.DataArchives -{ - public class InvalidDataArchive : IDataArchive - { - public bool IsValid { get; } = false; - public Stream? OpenRead(string filePath) => null; - public string DataPath { get; } - - - public InvalidDataArchive(string path) - { - DataPath = path; - } - - - public Task LoadAsync() - { - return Task.Run(() => { }); - } - - public IEnumerable Find(string pattern) - { - return Array.Empty(); - } - - public BitmapImage? TryLoadPng(string pngPath) - { - throw new NotImplementedException(); - } - - public ImageSource? TryLoadIcon(string iconPath, Point? desiredSize = null) - { - throw new NotImplementedException(); - } - } -} diff --git a/AnnoMapEditor/DataArchives/RdaDataArchive.cs b/AnnoMapEditor/DataArchives/RdaDataArchive.cs index 7b3d692..72ad96b 100644 --- a/AnnoMapEditor/DataArchives/RdaDataArchive.cs +++ b/AnnoMapEditor/DataArchives/RdaDataArchive.cs @@ -1,91 +1,27 @@ using AnnoMapEditor.Utilities; using AnnoRDA; -using AnnoRDA.Builder; -using FileDBReader.src.XmlRepresentation; using System; using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Threading.Tasks; -using System.Windows; namespace AnnoMapEditor.DataArchives { - public class RdaDataArchive : DataArchive, IDataArchive + public class RdaDataArchive : DataArchive { private static readonly Logger _logger = new(); - public override string DataPath { get; } - public bool IsLoaded - { - get => _isLoading; - private set => SetProperty(ref _isLoading, value); - } - private bool _isLoading = false; - - public override bool IsValid - { - get => _isValid; - protected set => SetProperty(ref _isValid, value); - } - private bool _isValid = false; + private readonly FileSystem _fileSystem; - private FileSystem _fileSystem; - - public RdaDataArchive(string folderPath) + public RdaDataArchive(FileSystem fileSystem) { - DataPath = folderPath; - IsValid = false; + _fileSystem = fileSystem; } - - public async Task LoadAsync() - { - IsLoaded = false; - IsValid = false; - _logger.LogInformation($"Discovering RDA archives at '{DataPath}'."); - var builder = FileSystemBuilder.Create() - .FromPath(DataPath) - .OnlyArchivesMatchingWildcard("data*.rda") - .WithDefaultSorting() - .ConfigureLoadZeroByteFiles(false) - .AddWhitelisted("*.a7tinfo", "*.png", "*.a7minfo", "*.a7t", "*.a7te", "assets.xml", "*.a7m", "*.dds"); - - await Task.Run(() => - { - try - { - _fileSystem = builder.Build(); - } - catch (Exception e){ - _logger.LogError($"error loading RDAs from {DataPath}", e); - IsValid = false; - return; - } - }); - - var loadedCount = builder.ArchiveFileNames.Count(); - if (loadedCount == 0) - { - _logger.LogWarning($"No .rda files found at {DataPath}"); - MessageBox.Show($"Something went wrong opening the RDA files.\n\nDo you have another Editor or the RDAExplorer open by any chance?\n\nLog file: {Log.LogFilePath}", App.TitleShort, MessageBoxButton.OK, MessageBoxImage.Exclamation); - } - _logger.LogInformation($"Loaded {loadedCount} RDAs."); - IsLoaded = true; - IsValid = true; - } - public override Stream? OpenRead(string filePath) { - if (!IsValid) - { - _logger.LogWarning($"archive not ready: {filePath}"); - return null; - } - filePath = filePath.Replace("\\", "/"); try { diff --git a/AnnoMapEditor/MapTemplates/Models/FixedIslandElement.cs b/AnnoMapEditor/MapTemplates/Models/FixedIslandElement.cs index 9971bf6..8487693 100644 --- a/AnnoMapEditor/MapTemplates/Models/FixedIslandElement.cs +++ b/AnnoMapEditor/MapTemplates/Models/FixedIslandElement.cs @@ -1,4 +1,5 @@ using Anno.FileDBModels.Anno1800.MapTemplate; +using AnnoMapEditor.DataArchives; using AnnoMapEditor.DataArchives.Assets.Models; using AnnoMapEditor.DataArchives.Assets.Repositories; using AnnoMapEditor.MapTemplates.Enums; @@ -75,21 +76,6 @@ public bool RandomizeSlots public Dictionary SlotAssignments { get; init; } = new(); - /// - /// For tracking if the island needs yet to be loaded. - /// - public bool DelayedLoading - { - get => _delayedLoading; - private set => SetProperty(ref _delayedLoading, value); - } - private bool _delayedLoading = false; - - /// - /// Used for tracking the original label during delayed loading. - /// - private string? prevLabel; - public FixedIslandElement(IslandAsset islandAsset, IslandType islandType) : base(islandType) @@ -124,24 +110,7 @@ public FixedIslandElement(Element sourceElement) : base(sourceElement) _randomizeFertilities = sourceElement.RandomizeFertilities != false; _randomizeSlots = sourceElement.MineSlotMapping == null || sourceElement.MineSlotMapping.Count == 0; - if (Settings.Instance.IsLoading) - { - DelayedLoading = true; - //Show island file if not labelled, while still loading - prevLabel = Label; - if (string.IsNullOrEmpty(Label)) - { - Label = System.IO.Path.GetFileNameWithoutExtension(islandFilePath); - } - SetDummyAsset(sourceElement); - System.Diagnostics.Debug.WriteLine($"Queueing item {sourceElement!.MapFilePath} for LoadingFinished."); - Settings.Instance.LoadingFinished += DelayedLoadAssetData; - } - else - { - LoadIslandDataFromRepository(sourceElement, false); - } - + LoadIslandDataFromRepository(sourceElement); } /// @@ -149,19 +118,15 @@ public FixedIslandElement(Element sourceElement) : base(sourceElement) /// Should only be called when the IslandRepository is actually loaded, errors otherwise. /// /// The element from the template. - /// Whether this is a delayed loading call (which means cleanup needs to be done after loading). /// The sourceElement does not have a MapFilePath. This is forbidden on FixedIslands. - /// The IslandRepository either does not exist or has not finished loading yet. /// The given MapFilePath does not match any islands in the IslandRepository. [MemberNotNull(nameof(_islandAsset))] - private void LoadIslandDataFromRepository(Element sourceElement, bool delayed) + private void LoadIslandDataFromRepository(Element sourceElement) { string islandFilePath = sourceElement.MapFilePath ?? throw new ArgumentException($"Missing property '{nameof(Element.MapFilePath)}'."); - IslandRepository islandRepository = Settings.Instance.IslandRepository - ?? throw new Exception($"No {nameof(IslandRepository)} could be found."); - if (!islandRepository.IsLoaded) - throw new Exception($"The {nameof(IslandRepository)} has not been loaded."); + + IslandRepository islandRepository = DataManager.Instance.IslandRepository; if (!islandRepository.TryGetByFilePath(islandFilePath, out var islandAsset)) throw new NullReferenceException($"Unknown island '{islandFilePath}'."); @@ -169,8 +134,7 @@ private void LoadIslandDataFromRepository(Element sourceElement, bool delayed) IslandAsset = islandAsset; //Rotation is not asset bound, thus loaded in constructor - AssetRepository assetRepository = Settings.Instance.AssetRepository - ?? throw new Exception($"The {nameof(AssetRepository)} has not been loaded."); + AssetRepository assetRepository = DataManager.Instance.AssetRepository; // fertilities // _randomizeFertilities is loaded in constructor. @@ -230,19 +194,6 @@ private void LoadIslandDataFromRepository(Element sourceElement, bool delayed) }); } } - - //Deregister when delayed loading - if (delayed) - { - Settings.Instance.LoadingFinished -= DelayedLoadAssetData; - - if(Label != prevLabel) - { - Label = prevLabel; - } - - DelayedLoading = false; - } } /// @@ -276,11 +227,6 @@ private void SetDummyAsset(Element sourceElement) IslandAsset = dummyAsset; } - private void DelayedLoadAssetData(object? sender, EventArgs _) - { - LoadIslandDataFromRepository(_sourceElement!, true); - } - protected override void ToTemplate(Element resultElement) { base.ToTemplate(resultElement); diff --git a/AnnoMapEditor/MapTemplates/Serializing/MapTemplateReader.cs b/AnnoMapEditor/MapTemplates/Serializing/MapTemplateReader.cs index 8da09c9..056bbf6 100644 --- a/AnnoMapEditor/MapTemplates/Serializing/MapTemplateReader.cs +++ b/AnnoMapEditor/MapTemplates/Serializing/MapTemplateReader.cs @@ -1,4 +1,5 @@ using Anno.FileDBModels.Anno1800.MapTemplate; +using AnnoMapEditor.DataArchives; using AnnoMapEditor.DataArchives.Assets.Models; using AnnoMapEditor.Utilities; using System; @@ -13,7 +14,7 @@ public class MapTemplateReader public async Task FromDataArchiveAsync(string a7tinfoPath) { SessionAsset session = SessionAsset.DetectFromPath(a7tinfoPath); - Stream a7tinfoStream = Settings.Instance!.DataArchive.OpenRead(a7tinfoPath) + Stream a7tinfoStream = DataManager.Instance.DataArchive.OpenRead(a7tinfoPath) ?? throw new FileNotFoundException($"Could not find file \"{a7tinfoPath}\" in DataArchive."); return await FromBinaryStreamAsync(session, a7tinfoStream); diff --git a/AnnoMapEditor/Mods/Serialization/ModWriter.cs b/AnnoMapEditor/Mods/Serialization/ModWriter.cs index 9a16442..d424d28 100644 --- a/AnnoMapEditor/Mods/Serialization/ModWriter.cs +++ b/AnnoMapEditor/Mods/Serialization/ModWriter.cs @@ -1,4 +1,5 @@ using Anno.FileDBModels.Anno1800.Gamedata.Models.Shared; +using AnnoMapEditor.DataArchives; using AnnoMapEditor.DataArchives.Assets.Models; using AnnoMapEditor.DataArchives.Assets.Repositories; using AnnoMapEditor.MapTemplates.Serializing; @@ -34,8 +35,7 @@ public class ModWriter public ModWriter() { _mapTemplateWriter = new(); - _assetRepository = Settings.Instance.AssetRepository - ?? throw new Exception($"AssetRepository has not been initialized."); + _assetRepository = DataManager.Instance.AssetRepository; } diff --git a/AnnoMapEditor/UI/Controls/AddIsland/AddIslandViewModel.cs b/AnnoMapEditor/UI/Controls/AddIsland/AddIslandViewModel.cs index 2cfdf92..a04766b 100644 --- a/AnnoMapEditor/UI/Controls/AddIsland/AddIslandViewModel.cs +++ b/AnnoMapEditor/UI/Controls/AddIsland/AddIslandViewModel.cs @@ -65,10 +65,6 @@ public override void Move(Point delta) { EndDrag(); - // TODO: Implement proper loading! - if (Settings.Instance.IslandRepository == null) - return; - IslandAdded?.Invoke(this, new(MapElementType, IslandType, IslandSize, delta)); } } diff --git a/AnnoMapEditor/UI/Controls/MapTemplates/MapElementViewModel.cs b/AnnoMapEditor/UI/Controls/MapTemplates/MapElementViewModel.cs index 6d521b6..6419f18 100644 --- a/AnnoMapEditor/UI/Controls/MapTemplates/MapElementViewModel.cs +++ b/AnnoMapEditor/UI/Controls/MapTemplates/MapElementViewModel.cs @@ -22,7 +22,7 @@ public MapElementViewModel(MapElement element) } - public virtual void Move(Point delta) + public override void Move(Point delta) { Element.Position = new(Element.Position.X + (int)delta.X, Element.Position.Y + (int)delta.Y); } diff --git a/AnnoMapEditor/UI/Overlays/SelectFertilities/SelectFertilitiesViewModel.cs b/AnnoMapEditor/UI/Overlays/SelectFertilities/SelectFertilitiesViewModel.cs index 8de019d..b37fdd5 100644 --- a/AnnoMapEditor/UI/Overlays/SelectFertilities/SelectFertilitiesViewModel.cs +++ b/AnnoMapEditor/UI/Overlays/SelectFertilities/SelectFertilitiesViewModel.cs @@ -1,4 +1,5 @@ -using AnnoMapEditor.DataArchives.Assets.Models; +using AnnoMapEditor.DataArchives; +using AnnoMapEditor.DataArchives.Assets.Models; using AnnoMapEditor.DataArchives.Assets.Repositories; using AnnoMapEditor.MapTemplates.Models; using AnnoMapEditor.Utilities; @@ -55,7 +56,7 @@ public bool ShowRegionWarning public SelectFertilitiesViewModel(RegionAsset region, FixedIslandElement fixedIsland) { // TODO: Should this happen here? - AssetRepository assetRepository = Settings.Instance.AssetRepository!; + AssetRepository assetRepository = DataManager.Instance.AssetRepository; FertilityItems = new(assetRepository.GetAll() .SelectMany(r => r.AllowedFertilities) .Distinct() diff --git a/AnnoMapEditor/UI/Overlays/SelectIsland/SelectIslandViewModel.cs b/AnnoMapEditor/UI/Overlays/SelectIsland/SelectIslandViewModel.cs index 2897da5..f90e89a 100644 --- a/AnnoMapEditor/UI/Overlays/SelectIsland/SelectIslandViewModel.cs +++ b/AnnoMapEditor/UI/Overlays/SelectIsland/SelectIslandViewModel.cs @@ -1,4 +1,5 @@ -using AnnoMapEditor.DataArchives.Assets.Models; +using AnnoMapEditor.DataArchives; +using AnnoMapEditor.DataArchives.Assets.Models; using AnnoMapEditor.DataArchives.Assets.Repositories; using AnnoMapEditor.MapTemplates.Enums; using AnnoMapEditor.Utilities; @@ -14,7 +15,7 @@ public class SelectIslandViewModel : ObservableBase, IOverlayViewModel public event IslandSelectedEventHandler? IslandSelected; - public IslandRepository Islands { get; } = Settings.Instance.IslandRepository!; + public IslandRepository Islands { get; } = DataManager.Instance.IslandRepository; public string? PathFilter { diff --git a/AnnoMapEditor/UI/Windows/Main/MainWindow.xaml b/AnnoMapEditor/UI/Windows/Main/MainWindow.xaml index d2cebdd..d2d25db 100644 --- a/AnnoMapEditor/UI/Windows/Main/MainWindow.xaml +++ b/AnnoMapEditor/UI/Windows/Main/MainWindow.xaml @@ -66,8 +66,8 @@ - - + + diff --git a/AnnoMapEditor/UI/Windows/Main/MainWindow.xaml.cs b/AnnoMapEditor/UI/Windows/Main/MainWindow.xaml.cs index 576dccb..82db018 100644 --- a/AnnoMapEditor/UI/Windows/Main/MainWindow.xaml.cs +++ b/AnnoMapEditor/UI/Windows/Main/MainWindow.xaml.cs @@ -16,13 +16,16 @@ namespace AnnoMapEditor.UI.Windows.Main /// public partial class MainWindow : Window { - public MainWindowViewModel ViewModel { get; } = new MainWindowViewModel(Settings.Instance); + private MainWindowViewModel _viewModel => DataContext as MainWindowViewModel + ?? throw new Exception(); + private readonly string title; + public MainWindow() { InitializeComponent(); - DataContext = ViewModel; + DataContext = _viewModel; var exePath = Path.Join(AppContext.BaseDirectory, "AnnoMapEditor.exe"); var productVersion = ""; @@ -37,9 +40,9 @@ public MainWindow() title = $"{App.Title} {productVersion}"; Title = title; - ViewModel.PropertyChanged += ViewModel_PropertyChanged; + _viewModel.PropertyChanged += ViewModel_PropertyChanged; - CreateImportMenu(openMapMenu, ViewModel?.Maps); + CreateImportMenu(openMapMenu, _viewModel.Maps!); } private void ViewModel_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) @@ -47,14 +50,14 @@ private void ViewModel_PropertyChanged(object? sender, System.ComponentModel.Pro switch (e.PropertyName) { case nameof(MainWindowViewModel.MapTemplate): - if (ViewModel?.MapTemplate is not null) - Title = $"{title} - {Path.GetFileName(ViewModel.MapTemplateFilePath)}"; + if (_viewModel?.MapTemplate is not null) + Title = $"{title} - {Path.GetFileName(_viewModel.MapTemplateFilePath)}"; else Title = title; break; case nameof(MainWindowViewModel.Maps): - CreateImportMenu(openMapMenu, ViewModel?.Maps); + CreateImportMenu(openMapMenu, _viewModel.Maps!); break; } } @@ -68,16 +71,13 @@ private async void OpenFile_Click(object sender, RoutedEventArgs e) if (true == picker.ShowDialog()) { - if (!Settings.Instance.IsValidGamePath) - { - int end = picker.FileName.IndexOf(@"\data\session"); - if (end == -1) - end = picker.FileName.IndexOf(@"\data\dlc"); - if (end != -1) - Settings.Instance.GamePath = picker.FileName[..end]; - } + int end = picker.FileName.IndexOf(@"\data\session"); + if (end == -1) + end = picker.FileName.IndexOf(@"\data\dlc"); + if (end != -1) + Settings.Instance.GamePath = picker.FileName[..end]; - await ViewModel.OpenMap(picker.FileName); + await _viewModel.OpenMap(picker.FileName); } } @@ -103,7 +103,7 @@ private void AutoDetect_Click(object _, RoutedEventArgs _1) Settings.Instance.GamePath = Settings.GetInstallDirFromRegistry(); } - private void CreateImportMenu(ContextMenu parentMenu, List? mapGroups) + private void CreateImportMenu(ContextMenu parentMenu, List mapGroups) { parentMenu.Items.Clear(); @@ -117,14 +117,8 @@ private void CreateImportMenu(ContextMenu parentMenu, List? mapGroups) parentMenu.Items.Add(newFile); parentMenu.Items.Add(new Separator()); - if (mapGroups is null || mapGroups.Count == 0) - { - parentMenu.Items.Add(new MenuItem() { - Header = Settings.Instance.IsLoading ? "(loading RDA...)" : "Set game/RDA path to import", - IsEnabled = false - }); - return; - } + if (mapGroups.Count == 0) + throw new Exception($"Could not locate any maps."); foreach (var group in mapGroups) { @@ -146,7 +140,7 @@ private async void MapMenu_Click(object sender, RoutedEventArgs e) MapInfo? mapInfo = (sender as MenuItem)?.DataContext as MapInfo; if (mapInfo?.FileName is not null) { - await ViewModel.OpenMap(mapInfo.FileName, true); + await _viewModel.OpenMap(mapInfo.FileName, true); } } @@ -156,28 +150,28 @@ private async void ExportMap_Click(object sender, RoutedEventArgs e) { DefaultExt = ".a7tinfo", Filter = "Map template (*.a7tinfo)|*.a7tinfo|XML map template (*.xml)|*.xml", - FilterIndex = Path.GetExtension(ViewModel.MapTemplateFilePath)?.ToLower() == ".xml" ? 2 : 1, - FileName = Path.GetFileName(ViewModel.MapTemplateFilePath), + FilterIndex = Path.GetExtension(_viewModel.MapTemplateFilePath)?.ToLower() == ".xml" ? 2 : 1, + FileName = Path.GetFileName(_viewModel.MapTemplateFilePath), OverwritePrompt = true }; if (true == picker.ShowDialog() && picker.FileName is not null) { - await ViewModel.SaveMap(picker.FileName); + await _viewModel.SaveMap(picker.FileName); } } private void ExportMod_Click(object sender, RoutedEventArgs e) { - if (ViewModel.MapTemplate is null) + if (_viewModel.MapTemplate is null) return; - OverlayService.Instance.Show(new ExportAsModViewModel(ViewModel.MapTemplate)); + OverlayService.Instance.Show(new ExportAsModViewModel(_viewModel.MapTemplate)); } private void NewMapFile_Click(object sender, RoutedEventArgs e) { - ViewModel.CreateNewMap(); + _viewModel.CreateNewMap(); } private void Hyperlink_OpenBrowser(object sender, System.Windows.Navigation.RequestNavigateEventArgs e) diff --git a/AnnoMapEditor/UI/Windows/Main/MainWindowViewModel.cs b/AnnoMapEditor/UI/Windows/Main/MainWindowViewModel.cs index f68011d..f575f7c 100644 --- a/AnnoMapEditor/UI/Windows/Main/MainWindowViewModel.cs +++ b/AnnoMapEditor/UI/Windows/Main/MainWindowViewModel.cs @@ -7,22 +7,16 @@ using AnnoMapEditor.UI.Controls.MapTemplates; using AnnoMapEditor.UI.Overlays.SelectIsland; using AnnoMapEditor.Utilities; -using System; using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; using System.IO; using System.Linq; -using System.Text.RegularExpressions; using System.Threading.Tasks; -using System.Windows; -using System.Xml; namespace AnnoMapEditor.UI.Windows.Main { public class MainWindowViewModel : ObservableBase { - public MapTemplate? MapTemplate + public MapTemplate MapTemplate { get => _mapTemplate; private set @@ -32,23 +26,27 @@ private set SetProperty(ref _mapTemplate, value); SelectedIsland = null; - if(MapTemplateProperties is not null) - MapTemplateProperties.SelectedRegionChanged -= SelectedRegionChanged; - - MapTemplateProperties = value is null ? null : new(value); - OnPropertyChanged(nameof(MapTemplateProperties)); - - if(MapTemplateProperties is not null) - MapTemplateProperties.SelectedRegionChanged += SelectedRegionChanged; - - MapTemplateChecker = value is null ? null : new(value); - OnPropertyChanged(nameof(MapTemplateChecker)); + MapTemplateProperties = new(value); + MapTemplateChecker = new(value); } } } - private MapTemplate? _mapTemplate; - public MapTemplatePropertiesViewModel? MapTemplateProperties { get; private set; } - public MapTemplateCheckerViewModel? MapTemplateChecker { get; private set; } + private MapTemplate _mapTemplate; + + public MapTemplatePropertiesViewModel MapTemplateProperties + { + get => _mapTemplateProperties; + private set => SetProperty(ref _mapTemplateProperties, value); + } + private MapTemplatePropertiesViewModel _mapTemplateProperties; + + public MapTemplateCheckerViewModel MapTemplateChecker + { + get => _mapTemplateChecker; + private set => SetProperty(ref _mapTemplateChecker, value); + } + private MapTemplateCheckerViewModel _mapTemplateChecker; + public IslandElement? SelectedIsland { @@ -117,13 +115,6 @@ public GamePathStatus GamePathStatus } private GamePathStatus _gamePathStatus = new(); - public ExportStatus ExportStatus - { - get => _exportStatus; - private set => SetProperty(ref _exportStatus, value); - } - private ExportStatus _exportStatus = new(); - public List? Maps { get => _maps; @@ -133,26 +124,13 @@ public List? Maps public Settings Settings { get; private set; } - public MainWindowViewModel(Settings settings) + public MainWindowViewModel(MapTemplate mapTemplate) { - Settings = settings; - Settings.PropertyChanged += Settings_PropertyChanged; - + MapTemplate = mapTemplate; UpdateStatusAndMenus(); } - private void Settings_PropertyChanged(object? sender, PropertyChangedEventArgs e) - { - if (e.PropertyName == nameof(Settings.IsLoading)) - UpdateStatusAndMenus(); - } - - private void SelectedRegionChanged(object? sender, EventArgs _) - { - UpdateExportStatus(); - } - public async Task OpenMap(string a7tinfoPath, bool fromArchive = false) { MapTemplateFilePath = Path.GetFileName(a7tinfoPath); @@ -163,8 +141,6 @@ public async Task OpenMap(string a7tinfoPath, bool fromArchive = false) else MapTemplate = await mapTemplateReader.FromFileAsync(a7tinfoPath); - - UpdateExportStatus(); } public void CreateNewMap() @@ -175,8 +151,6 @@ public void CreateNewMap() MapTemplateFilePath = null; MapTemplate = new MapTemplate(DEFAULT_MAP_SIZE, DEFAULT_PLAYABLE_SIZE, SessionAsset.OldWorld); - - UpdateExportStatus(); } public async Task SaveMap(string filePath) @@ -193,114 +167,25 @@ public async Task SaveMap(string filePath) await mapTemplateWriter.WriteXmlAsync(MapTemplate, filePath); } - private void UpdateExportStatus() - { - if (Settings.IsLoading) - { - // still loading - ExportStatus = new ExportStatus() - { - CanExportAsMod = false, - ExportAsModText = "(loading RDA...)" - }; - } - else if (Settings.IsValidGamePath) - { - bool archiveReady = Settings.DataArchive is RdaDataArchive; - - ExportStatus = new ExportStatus() - { - CanExportAsMod = archiveReady, - ExportAsModText = archiveReady ? "As playable mod..." : "As mod: set game path to save" - }; - } - else - { - ExportStatus = new ExportStatus() - { - ExportAsModText = "As mod: set game path to save", - CanExportAsMod = false - }; - } - } - private void UpdateStatusAndMenus() { - if (Settings.IsLoading) - { - // still loading - GamePathStatus = new GamePathStatus() - { - Status = "loading RDA...", - ToolTip = "", - Configure = Visibility.Collapsed, - AutoDetect = Visibility.Collapsed, - }; - } - else if (Settings.IsValidGamePath) - { - GamePathStatus = new GamePathStatus() - { - Status = Settings.DataArchive is RdaDataArchive ? "Game path set ✔" : "Extracted RDA path set ✔", - ToolTip = Settings.DataArchive.DataPath, - ConfigureText = "Change...", - AutoDetect = Settings.DataArchive is RdaDataArchive ? Visibility.Collapsed : Visibility.Visible, - }; + // TODO: load from assets instead. + var mapTemplates = DataManager.Instance.DataArchive.Find("*.a7tinfo"); - Dictionary templateGroups = new() - { - ["DLCs"] = new(@"data\/(?!=sessions\/)([^\/]+)"), - ["Moderate"] = new(@"data\/sessions\/.+moderate"), - ["New World"] = new(@"data\/sessions\/.+colony01") - }; - - //load from assets instead. - var mapTemplates = Settings.DataArchive.Find("*.a7tinfo"); - - Maps = new() - { - new MapGroup("Campaign", mapTemplates.Where(x => x.StartsWith(@"data/sessions/maps/campaign")), new(@"\/campaign_([^\/]+)\.")), - new MapGroup("Moderate, Archipelago", mapTemplates.Where(x => x.StartsWith(@"data/sessions/maps/pool/moderate/moderate_archipel")), new(@"\/([^\/]+)\.")), - new MapGroup("Moderate, Atoll", mapTemplates.Where(x => x.StartsWith(@"data/sessions/maps/pool/moderate/moderate_atoll")), new(@"\/([^\/]+)\.")), - new MapGroup("Moderate, Corners", mapTemplates.Where(x => x.StartsWith(@"data/sessions/maps/pool/moderate/moderate_corners")), new(@"\/([^\/]+)\.")), - new MapGroup("Moderate, Island Arc", mapTemplates.Where(x => x.StartsWith(@"data/sessions/maps/pool/moderate/moderate_islandarc")), new(@"\/([^\/]+)\.")), - new MapGroup("Moderate, Snowflake", mapTemplates.Where(x => x.StartsWith(@"data/sessions/maps/pool/moderate/moderate_snowflake")), new(@"\/([^\/]+)\.")), - new MapGroup("New World, Large", mapTemplates.Where(x => x.StartsWith(@"data/sessions/maps/pool/colony01/colony01_l_")), new(@"\/([^\/]+)\.")), - new MapGroup("New World, Medium", mapTemplates.Where(x => x.StartsWith(@"data/sessions/maps/pool/colony01/colony01_m_")), new(@"\/([^\/]+)\.")), - new MapGroup("New World, Small", mapTemplates.Where(x => x.StartsWith(@"data/sessions/maps/pool/colony01/colony01_s_")), new(@"\/([^\/]+)\.")), - new MapGroup("DLCs", mapTemplates.Where(x => !x.StartsWith(@"data/sessions/")), new(@"data\/([^\/]+)\/.+\/maps\/([^\/]+)")) - //new MapGroup("Moderate", mapTemplates.Where(x => x.StartsWith(@"data/sessions/maps/pool/moderate")), new(@"\/([^\/]+)\.")) - }; - } - else + Maps = new() { - GamePathStatus = new GamePathStatus() - { - Status = "⚠ Game or RDA path not valid.", - ToolTip = null, - ConfigureText = "Select...", - AutoDetect = Visibility.Visible, - }; - - Maps = new(); - } - - UpdateExportStatus(); - } - - private IEnumerable? LoadMapsFromAssets() - { - const string assetsXmlPath = "data/config/export/main/asset/assets.xml"; - - Stopwatch stopwatch= Stopwatch.StartNew(); - using var assetStream = Settings.DataArchive.OpenRead(assetsXmlPath) - ?? throw new FileNotFoundException(assetsXmlPath); - XmlDocument doc = new XmlDocument(); - doc.Load(assetStream); - var nodes = doc.SelectNodes("//Asset[Template='MapTemplate']/Values/MapTemplate[TemplateRegion]/*[self::TemplateFilename or self::EnlargedTemplateFilename]"); - stopwatch.Stop(); - double i = stopwatch.Elapsed.TotalMilliseconds; - return nodes?.Cast().Select(x => Path.ChangeExtension(x.InnerText, "a7tinfo")); + new MapGroup("Campaign", mapTemplates.Where(x => x.StartsWith(@"data/sessions/maps/campaign")), new(@"\/campaign_([^\/]+)\.")), + new MapGroup("Moderate, Archipelago", mapTemplates.Where(x => x.StartsWith(@"data/sessions/maps/pool/moderate/moderate_archipel")), new(@"\/([^\/]+)\.")), + new MapGroup("Moderate, Atoll", mapTemplates.Where(x => x.StartsWith(@"data/sessions/maps/pool/moderate/moderate_atoll")), new(@"\/([^\/]+)\.")), + new MapGroup("Moderate, Corners", mapTemplates.Where(x => x.StartsWith(@"data/sessions/maps/pool/moderate/moderate_corners")), new(@"\/([^\/]+)\.")), + new MapGroup("Moderate, Island Arc", mapTemplates.Where(x => x.StartsWith(@"data/sessions/maps/pool/moderate/moderate_islandarc")), new(@"\/([^\/]+)\.")), + new MapGroup("Moderate, Snowflake", mapTemplates.Where(x => x.StartsWith(@"data/sessions/maps/pool/moderate/moderate_snowflake")), new(@"\/([^\/]+)\.")), + new MapGroup("New World, Large", mapTemplates.Where(x => x.StartsWith(@"data/sessions/maps/pool/colony01/colony01_l_")), new(@"\/([^\/]+)\.")), + new MapGroup("New World, Medium", mapTemplates.Where(x => x.StartsWith(@"data/sessions/maps/pool/colony01/colony01_m_")), new(@"\/([^\/]+)\.")), + new MapGroup("New World, Small", mapTemplates.Where(x => x.StartsWith(@"data/sessions/maps/pool/colony01/colony01_s_")), new(@"\/([^\/]+)\.")), + new MapGroup("DLCs", mapTemplates.Where(x => !x.StartsWith(@"data/sessions/")), new(@"data\/([^\/]+)\/.+\/maps\/([^\/]+)")) + //new MapGroup("Moderate", mapTemplates.Where(x => x.StartsWith(@"data/sessions/maps/pool/moderate")), new(@"\/([^\/]+)\.")) + }; } } } diff --git a/AnnoMapEditor/Utilities/Settings.cs b/AnnoMapEditor/Utilities/Settings.cs index dd9a315..bca13b7 100644 --- a/AnnoMapEditor/Utilities/Settings.cs +++ b/AnnoMapEditor/Utilities/Settings.cs @@ -18,39 +18,6 @@ public class Settings : ObservableBase public static Settings Instance { get; } = new(); - /// - /// Is Invoked when loading all repositories from the game path finishes. - /// - public event EventHandler? LoadingFinished; - - public IDataArchive DataArchive - { - get => _dataArchive; - private set - { - if (_dataArchive is System.IDisposable disposable) - disposable.Dispose(); - SetProperty(ref _dataArchive, value); - OnPropertyChanged(nameof(GamePath)); - } - } - - private IDataArchive _dataArchive = DataArchives.DataArchive.Default; - - public IslandRepository? IslandRepository - { - get => _islandRepository; - private set => SetProperty(ref _islandRepository, value); - } - private IslandRepository? _islandRepository; - - public AssetRepository? AssetRepository - { - get => _assetRepository; - private set => SetProperty(ref _assetRepository, value); - } - private AssetRepository? _assetRepository; - public string? GamePath { @@ -78,8 +45,6 @@ public string? DataPath UserSettings.Default.DataPath = value; UserSettings.Default.Save(); OnPropertyChanged(nameof(DataPath)); - - LoadDataPath(value); } } } @@ -99,183 +64,12 @@ public string? ModsPath } - public bool IsValidGamePath - { - get => _isValidGamePath; - private set => SetProperty(ref _isValidGamePath, value); - } - private bool _isValidGamePath = false; - - public bool IsLoading - { - get => _isLoading; - private set => SetProperty(ref _isLoading, value); - } - private bool _isLoading; - - private Task? _loadingTask; - - /// - /// Useful if code needs to wait for a Loading Process to finish somewhere. - /// Like unit test setups for example. - /// - private ManualResetEvent LoadingDoneTrigger { get; init; } - - /// - /// Private Constructor - only used for Singleton. - /// private Settings() { - - - //We set the LoadingDoneTrigger as Reset by default, so anything that waits for the setup to finish - //has to actually wait for the SetupAsync called in Initialize to finish. - LoadingDoneTrigger = new ManualResetEvent(false); - Initialize(); - } - - /// - /// Starts the asynchronous Setup on the Tread Pool, so it doesn't block. - /// - private void Initialize() - { - IsLoading = true; - - _loadingTask = Task.Run(async () => { - await SetupAsync(true); - }); - } - - /// - /// This avoids direct writes to the path properties that would run their - /// setters on the thread pool, making proper awaiting of the setup impossible. - /// - /// If the UserSettings should be overwritten by a valid result. - /// - private async Task SetupAsync(bool updateInvalidUserSettings) - { - LoadingDoneTrigger.Reset(); - - if (!string.IsNullOrEmpty(UserSettings.Default.DataPath)) - { - await LoadDataPathAsync(UserSettings.Default.DataPath); - OnPropertyChanged(nameof(DataPath)); - } - - if (DataArchive?.IsValid != true) - { - // auto detect on start-up if not valid - await LoadDataPathAsync(GetInstallDirFromRegistry()); - - OnPropertyChanged(nameof(DataPath)); - } - - if (DataArchive?.IsValid == true && updateInvalidUserSettings) - { - DataPath = DataArchive.DataPath; - } - - LoadingDoneTrigger.Set(); - } - - /// - /// Used to set DataPath with a synchronous method call - /// by running the actual async code on the tread pool. - /// - /// The DataPath to load. - private void LoadDataPath(string? dataPath) - { - IsLoading = true; - - _loadingTask = Task.Run(async () => { - await LoadDataPathAsync(dataPath); - }); - } - - /// - /// Asynchronously sets the DataPath and reads all the Repositories. - /// - /// The DataPath to load. - /// - private async Task LoadDataPathAsync(string? dataPath) - { - //Only use the LoadingDoneTrigger, if it is not already Reset. - //A reset LoadingDoneTrigger here means, that the Setup is using it. - bool loadingDoneTriggerNotBlocked = LoadingDoneTrigger.WaitOne(0); - - if (loadingDoneTriggerNotBlocked) - { - LoadingDoneTrigger.Reset(); - } - - Dispatch(() => - { - IsLoading = true; - }); - - var archive = await DataArchives.DataArchive.OpenAsync(dataPath); - - try - { - AssetRepository assetRepository = new(archive); - assetRepository.Register(); - assetRepository.Register(); - assetRepository.Register(); - assetRepository.Register(); - assetRepository.Register(); - assetRepository.Register(); - assetRepository.Register(); - await assetRepository.LoadAsync(); - - FixedIslandRepository fixedIslandRepository = new(archive); - await fixedIslandRepository.AwaitLoadingAsync(); - - IslandRepository islandRepository = new(fixedIslandRepository, assetRepository); - await islandRepository.AwaitLoadingAsync(); - - Dispatch(() => - { - DataArchive = archive; - AssetRepository = assetRepository; - IslandRepository = islandRepository; - IsValidGamePath = true; - }); - } - catch (Exception ex) - { - _logger.LogError($"An error occured during setup.", ex); - Dispatch(() => - { - IsValidGamePath = false; - }); - } - finally - { - Dispatch(() => - { - IsLoading = false; - }); - - if (loadingDoneTriggerNotBlocked) - { - LoadingDoneTrigger.Set(); - } - - //If not Dispatched to UI thread, only the first item will be invoked somehow? - Dispatch(() => - { - LoadingFinished?.Invoke(this, EventArgs.Empty); - }); - } + if (GamePath == null) + GamePath = GetInstallDirFromRegistry(); } - private void Dispatch(Action action) - { - if (Application.Current?.Dispatcher != null) - Application.Current.Dispatcher.Invoke(action); - else - action(); - } public static string? GetInstallDirFromRegistry() { @@ -294,26 +88,5 @@ private void Dispatch(Action action) else return installDir; } - - /// - /// Waits until the current _loadingTask is finished. - /// - /// - /// Thrown when no loading Task has been set yet. - public async Task AwaitLoadingAsync() - { - if (_loadingTask != null) - await _loadingTask; - else - throw new Exception($"LoadAsync has not been called."); - } - - /// - /// Waits until the current loading Task is finished by blocking until the LoadingDoneTrigger is set. - /// - public void WaitForLoadingBlocking() - { - LoadingDoneTrigger.WaitOne(); - } } } From 2ad1bc296cf2323d4fa69eb1fed24d4462e02ed7 Mon Sep 17 00:00:00 2001 From: Christopher Cyclonit Klinge Date: Wed, 14 Jun 2023 16:33:58 +0200 Subject: [PATCH 09/16] Add StartWindow. Add EnableExpertMode setting. --- AnnoMapEditor/App.xaml | 2 +- .../Assets/Repositories/AssetRepository.cs | 4 +- .../Repositories/FixedIslandRepository.cs | 2 +- .../Assets/Repositories/IslandRepository.cs | 2 +- .../Assets/Repositories/MapGroupRepository.cs | 49 +++++ .../Assets/Repositories/Repository.cs | 7 +- AnnoMapEditor/DataArchives/DataManager.cs | 15 +- AnnoMapEditor/UI/Controls/LoadingSpinner.xaml | 59 ++++++ .../UI/Controls/LoadingSpinner.xaml.cs | 15 ++ .../UI/Controls/StatusIndicator.xaml | 33 ++++ .../UI/Controls/StatusIndicator.xaml.cs | 47 +++++ .../Converters/BoolToGridLengthConverter.cs | 39 ++++ AnnoMapEditor/UI/Converters/ObjectToBool.cs | 27 +++ AnnoMapEditor/UI/Resources/Styles/Colors.xaml | 2 + AnnoMapEditor/UI/Resources/Styles/Label.xaml | 20 +- AnnoMapEditor/UI/Windows/Main/MainWindow.xaml | 30 +-- .../UI/Windows/Main/MainWindow.xaml.cs | 114 +----------- .../UI/Windows/Main/MainWindowViewModel.cs | 83 +++++---- AnnoMapEditor/UI/Windows/Main/MapInfo.cs | 4 +- .../UI/Windows/Start/StartWindow.xaml | 171 ++++++++++++++++++ .../UI/Windows/Start/StartWindow.xaml.cs | 52 ++++++ .../UI/Windows/Start/StartWindowViewModel.cs | 152 ++++++++++++++++ AnnoMapEditor/UserSettings.Designer.cs | 12 ++ AnnoMapEditor/UserSettings.settings | 3 + AnnoMapEditor/Utilities/Settings.cs | 39 ++-- 25 files changed, 776 insertions(+), 207 deletions(-) create mode 100644 AnnoMapEditor/DataArchives/Assets/Repositories/MapGroupRepository.cs create mode 100644 AnnoMapEditor/UI/Controls/LoadingSpinner.xaml create mode 100644 AnnoMapEditor/UI/Controls/LoadingSpinner.xaml.cs create mode 100644 AnnoMapEditor/UI/Controls/StatusIndicator.xaml create mode 100644 AnnoMapEditor/UI/Controls/StatusIndicator.xaml.cs create mode 100644 AnnoMapEditor/UI/Converters/BoolToGridLengthConverter.cs create mode 100644 AnnoMapEditor/UI/Converters/ObjectToBool.cs create mode 100644 AnnoMapEditor/UI/Windows/Start/StartWindow.xaml create mode 100644 AnnoMapEditor/UI/Windows/Start/StartWindow.xaml.cs create mode 100644 AnnoMapEditor/UI/Windows/Start/StartWindowViewModel.cs diff --git a/AnnoMapEditor/App.xaml b/AnnoMapEditor/App.xaml index 96f7a8f..9d091b6 100644 --- a/AnnoMapEditor/App.xaml +++ b/AnnoMapEditor/App.xaml @@ -2,7 +2,7 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:AnnoMapEditor" - StartupUri="UI/Windows/Main/MainWindow.xaml"> + StartupUri="UI/Windows/Start/StartWindow.xaml"> diff --git a/AnnoMapEditor/DataArchives/Assets/Repositories/AssetRepository.cs b/AnnoMapEditor/DataArchives/Assets/Repositories/AssetRepository.cs index 2928380..ebde26a 100644 --- a/AnnoMapEditor/DataArchives/Assets/Repositories/AssetRepository.cs +++ b/AnnoMapEditor/DataArchives/Assets/Repositories/AssetRepository.cs @@ -103,7 +103,7 @@ private void RenewCache(IEnumerable assets) doc.Save(File.Create(CachedAssetsXml)); } - public override async Task Initialize() + public override async Task InitializeAsync() { _logger.LogInformation($"Loading assets..."); @@ -172,6 +172,8 @@ public override async Task Initialize() resolver(asset); } + InitializeStaticAssets(); + watch.Stop(); _logger.LogInformation($"Finished loading {_assets.Count} assets at {watch.Elapsed.TotalMilliseconds} ms."); assetsXmlStream.Dispose(); diff --git a/AnnoMapEditor/DataArchives/Assets/Repositories/FixedIslandRepository.cs b/AnnoMapEditor/DataArchives/Assets/Repositories/FixedIslandRepository.cs index a414581..7b37bd2 100644 --- a/AnnoMapEditor/DataArchives/Assets/Repositories/FixedIslandRepository.cs +++ b/AnnoMapEditor/DataArchives/Assets/Repositories/FixedIslandRepository.cs @@ -35,7 +35,7 @@ public FixedIslandRepository(IDataArchive dataArchive) } - public override async Task Initialize() + public override async Task InitializeAsync() { _logger.LogInformation($"Begin loading fixed islands."); Stopwatch watch = Stopwatch.StartNew(); diff --git a/AnnoMapEditor/DataArchives/Assets/Repositories/IslandRepository.cs b/AnnoMapEditor/DataArchives/Assets/Repositories/IslandRepository.cs index ee38b96..47d784d 100644 --- a/AnnoMapEditor/DataArchives/Assets/Repositories/IslandRepository.cs +++ b/AnnoMapEditor/DataArchives/Assets/Repositories/IslandRepository.cs @@ -65,7 +65,7 @@ public bool TryGetByFilePath(string mapFilePath, [NotNullWhen(false)] out Island => _byFilePath.TryGetValue(mapFilePath, out islandAsset); #pragma warning restore CS8762 // Parameter must have a non-null value when exiting in some condition. - public override Task Initialize() + public override Task InitializeAsync() { _logger.LogInformation($"Begin loading islands."); diff --git a/AnnoMapEditor/DataArchives/Assets/Repositories/MapGroupRepository.cs b/AnnoMapEditor/DataArchives/Assets/Repositories/MapGroupRepository.cs new file mode 100644 index 0000000..492af2d --- /dev/null +++ b/AnnoMapEditor/DataArchives/Assets/Repositories/MapGroupRepository.cs @@ -0,0 +1,49 @@ +using AnnoMapEditor.UI.Windows.Main; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace AnnoMapEditor.DataArchives.Assets.Repositories +{ + public class MapGroupRepository : Repository + { + public IEnumerable MapGroups + { + get => _mapGroups ?? throw new Exception("MapRepository has not yet been initialized."); + private set => SetProperty(ref _mapGroups, value); + } + private IEnumerable? _mapGroups; + + private readonly IDataArchive _dataArchive; + + + public MapGroupRepository(IDataArchive dataArchive) + { + _dataArchive = dataArchive; + } + + + public override Task InitializeAsync() + { + IEnumerable mapTemplatePaths = _dataArchive.Find("*.a7tinfo"); + + MapGroups = new MapGroup[] + { + new MapGroup("Campaign", mapTemplatePaths.Where(x => x.StartsWith(@"data/sessions/maps/campaign")), new(@"\/campaign_([^\/]+)\.")), + new MapGroup("Moderate, Archipelago", mapTemplatePaths.Where(x => x.StartsWith(@"data/sessions/maps/pool/moderate/moderate_archipel")), new(@"\/([^\/]+)\.")), + new MapGroup("Moderate, Atoll", mapTemplatePaths.Where(x => x.StartsWith(@"data/sessions/maps/pool/moderate/moderate_atoll")), new(@"\/([^\/]+)\.")), + new MapGroup("Moderate, Corners", mapTemplatePaths.Where(x => x.StartsWith(@"data/sessions/maps/pool/moderate/moderate_corners")), new(@"\/([^\/]+)\.")), + new MapGroup("Moderate, Island Arc", mapTemplatePaths.Where(x => x.StartsWith(@"data/sessions/maps/pool/moderate/moderate_islandarc")), new(@"\/([^\/]+)\.")), + new MapGroup("Moderate, Snowflake", mapTemplatePaths.Where(x => x.StartsWith(@"data/sessions/maps/pool/moderate/moderate_snowflake")), new(@"\/([^\/]+)\.")), + new MapGroup("New World, Large", mapTemplatePaths.Where(x => x.StartsWith(@"data/sessions/maps/pool/colony01/colony01_l_")), new(@"\/([^\/]+)\.")), + new MapGroup("New World, Medium", mapTemplatePaths.Where(x => x.StartsWith(@"data/sessions/maps/pool/colony01/colony01_m_")), new(@"\/([^\/]+)\.")), + new MapGroup("New World, Small", mapTemplatePaths.Where(x => x.StartsWith(@"data/sessions/maps/pool/colony01/colony01_s_")), new(@"\/([^\/]+)\.")), + new MapGroup("DLCs", mapTemplatePaths.Where(x => !x.StartsWith(@"data/sessions/")), new(@"data\/([^\/]+)\/.+\/maps\/([^\/]+)")) + //new MapGroup("Moderate", mapTemplates.Where(x => x.StartsWith(@"data/sessions/maps/pool/moderate")), new(@"\/([^\/]+)\.")) + }; + + return Task.CompletedTask; + } + } +} diff --git a/AnnoMapEditor/DataArchives/Assets/Repositories/Repository.cs b/AnnoMapEditor/DataArchives/Assets/Repositories/Repository.cs index 85d4c3b..ee58809 100644 --- a/AnnoMapEditor/DataArchives/Assets/Repositories/Repository.cs +++ b/AnnoMapEditor/DataArchives/Assets/Repositories/Repository.cs @@ -1,14 +1,15 @@ -using System.Threading.Tasks; +using AnnoMapEditor.Utilities; +using System.Threading.Tasks; namespace AnnoMapEditor.DataArchives.Assets.Repositories { - public abstract class Repository + public abstract class Repository : ObservableBase { public Repository() { } - public abstract Task Initialize(); + public abstract Task InitializeAsync(); } } diff --git a/AnnoMapEditor/DataArchives/DataManager.cs b/AnnoMapEditor/DataArchives/DataManager.cs index a6f0f7a..0516bad 100644 --- a/AnnoMapEditor/DataArchives/DataManager.cs +++ b/AnnoMapEditor/DataArchives/DataManager.cs @@ -37,6 +37,8 @@ public string? ErrorMessage } private string? _errorMessage; + public bool HasError => _errorMessage != null; + public IDataArchive DataArchive => _isInitialized && _dataArchive != null ? _dataArchive : throw new Exception(NOT_INITIALIZED_MESSAGE); private IDataArchive? _dataArchive; @@ -50,6 +52,9 @@ public string? ErrorMessage public IslandRepository IslandRepository => _isInitialized && _islandRepository != null ? _islandRepository : throw new Exception(NOT_INITIALIZED_MESSAGE); private IslandRepository? _islandRepository; + public MapGroupRepository MapGroupRepository => _isInitialized && _mapGroupRepository != null ? _mapGroupRepository : throw new Exception(NOT_INITIALIZED_MESSAGE); + private MapGroupRepository? _mapGroupRepository; + private DataManager() { @@ -75,13 +80,16 @@ public async Task TryInitializeAsync(string dataPath) _assetRepository.Register(); _assetRepository.Register(); _assetRepository.Register(); - await _assetRepository.Initialize(); + await _assetRepository.InitializeAsync(); _fixedIslandRepository = new FixedIslandRepository(_dataArchive); - await _fixedIslandRepository.Initialize(); + await _fixedIslandRepository.InitializeAsync(); _islandRepository = new IslandRepository(_fixedIslandRepository, _assetRepository); - await _islandRepository.Initialize(); + await _islandRepository.InitializeAsync(); + + _mapGroupRepository = new MapGroupRepository(_dataArchive); + _mapGroupRepository.InitializeAsync(); } catch (Exception ex) { @@ -110,6 +118,7 @@ private void UpdateStatus(bool isInitializing, bool isInitialized, string? error IsInitializing = isInitializing; IsInitialized = isInitialized; ErrorMessage = errorMessage; + OnPropertyChanged(nameof(HasError)); }); } } diff --git a/AnnoMapEditor/UI/Controls/LoadingSpinner.xaml b/AnnoMapEditor/UI/Controls/LoadingSpinner.xaml new file mode 100644 index 0000000..8a6176d --- /dev/null +++ b/AnnoMapEditor/UI/Controls/LoadingSpinner.xaml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AnnoMapEditor/UI/Controls/LoadingSpinner.xaml.cs b/AnnoMapEditor/UI/Controls/LoadingSpinner.xaml.cs new file mode 100644 index 0000000..ac48fef --- /dev/null +++ b/AnnoMapEditor/UI/Controls/LoadingSpinner.xaml.cs @@ -0,0 +1,15 @@ +using System.Windows.Controls; + +namespace AnnoMapEditor.UI.Controls +{ + /// + /// Interaction logic for LoadingSpinner.xaml + /// + public partial class LoadingSpinner : UserControl + { + public LoadingSpinner() + { + InitializeComponent(); + } + } +} diff --git a/AnnoMapEditor/UI/Controls/StatusIndicator.xaml b/AnnoMapEditor/UI/Controls/StatusIndicator.xaml new file mode 100644 index 0000000..e9ad1c0 --- /dev/null +++ b/AnnoMapEditor/UI/Controls/StatusIndicator.xaml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + diff --git a/AnnoMapEditor/UI/Controls/StatusIndicator.xaml.cs b/AnnoMapEditor/UI/Controls/StatusIndicator.xaml.cs new file mode 100644 index 0000000..4815fe4 --- /dev/null +++ b/AnnoMapEditor/UI/Controls/StatusIndicator.xaml.cs @@ -0,0 +1,47 @@ +using System.Windows; +using System.Windows.Controls; + +namespace AnnoMapEditor.UI.Controls +{ + /// + /// Interaction logic for StatusIndicator.xaml + /// + public partial class StatusIndicator : UserControl + { + public static readonly DependencyProperty IsOkProperty = + DependencyProperty.Register("IsOk", typeof(bool), + typeof(StatusIndicator), new FrameworkPropertyMetadata(false)); + + public bool IsOk + { + get { return (bool)GetValue(IsOkProperty); } + set { SetValue(IsOkProperty, value); } + } + + public static readonly DependencyProperty IsErrorProperty = + DependencyProperty.Register("IsError", typeof(bool), + typeof(StatusIndicator), new FrameworkPropertyMetadata(false)); + + public bool IsError + { + get { return (bool)GetValue(IsErrorProperty); } + set { SetValue(IsErrorProperty, value); } + } + + public static readonly DependencyProperty IsLoadingProperty = + DependencyProperty.Register("IsLoading", typeof(bool), + typeof(StatusIndicator), new FrameworkPropertyMetadata(false)); + + public bool IsLoading + { + get { return (bool) GetValue(IsLoadingProperty); } + set { SetValue(IsLoadingProperty, value); } + } + + + public StatusIndicator() + { + InitializeComponent(); + } + } +} diff --git a/AnnoMapEditor/UI/Converters/BoolToGridLengthConverter.cs b/AnnoMapEditor/UI/Converters/BoolToGridLengthConverter.cs new file mode 100644 index 0000000..16b2a1a --- /dev/null +++ b/AnnoMapEditor/UI/Converters/BoolToGridLengthConverter.cs @@ -0,0 +1,39 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Data; + +namespace AnnoMapEditor.UI.Converters +{ + [ValueConversion(typeof(bool), typeof(GridLength))] + public class BoolToGridLengthConverter : IValueConverter + { + public GridLength OnTrue { get; set; } + + public GridLength OnFalse { get; set; } + + + public BoolToGridLengthConverter() + { + OnTrue = GridLength.Auto; + OnFalse = new(0); + } + + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is bool boolValue) + return boolValue ? OnTrue : OnFalse; + + throw new ArgumentException("Input was not a bool."); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is GridLength gridLength) + return gridLength.Value > 0; + + throw new ArgumentException("Input was not a GridLength."); + } + } +} diff --git a/AnnoMapEditor/UI/Converters/ObjectToBool.cs b/AnnoMapEditor/UI/Converters/ObjectToBool.cs new file mode 100644 index 0000000..d529240 --- /dev/null +++ b/AnnoMapEditor/UI/Converters/ObjectToBool.cs @@ -0,0 +1,27 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace AnnoMapEditor.UI.Converters +{ + [ValueConversion(typeof(object), typeof(bool))] + public class ObjectToBool : IValueConverter + { + public bool OnNull { get; set; } + + public ObjectToBool() + { + OnNull = false; + } + + public object? Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return value is null ? OnNull : !OnNull; + } + + public object? ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/AnnoMapEditor/UI/Resources/Styles/Colors.xaml b/AnnoMapEditor/UI/Resources/Styles/Colors.xaml index d042621..980fe8e 100644 --- a/AnnoMapEditor/UI/Resources/Styles/Colors.xaml +++ b/AnnoMapEditor/UI/Resources/Styles/Colors.xaml @@ -9,4 +9,6 @@ #8024415F #708090 #ef182B3F + #ee0000 + #00aa00 \ No newline at end of file diff --git a/AnnoMapEditor/UI/Resources/Styles/Label.xaml b/AnnoMapEditor/UI/Resources/Styles/Label.xaml index 599ec75..9a9b9b6 100644 --- a/AnnoMapEditor/UI/Resources/Styles/Label.xaml +++ b/AnnoMapEditor/UI/Resources/Styles/Label.xaml @@ -13,30 +13,22 @@ - - + - diff --git a/AnnoMapEditor/UI/Windows/Main/MainWindow.xaml b/AnnoMapEditor/UI/Windows/Main/MainWindow.xaml index d2d25db..1067820 100644 --- a/AnnoMapEditor/UI/Windows/Main/MainWindow.xaml +++ b/AnnoMapEditor/UI/Windows/Main/MainWindow.xaml @@ -33,22 +33,19 @@ - - + + DataContext="{Binding MapTemplate, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}" /> - + - - - - - - + - + diff --git a/AnnoMapEditor/UI/Windows/Main/MainWindow.xaml.cs b/AnnoMapEditor/UI/Windows/Main/MainWindow.xaml.cs index 82db018..071b489 100644 --- a/AnnoMapEditor/UI/Windows/Main/MainWindow.xaml.cs +++ b/AnnoMapEditor/UI/Windows/Main/MainWindow.xaml.cs @@ -1,13 +1,10 @@ using AnnoMapEditor.UI.Overlays; using AnnoMapEditor.UI.Overlays.ExportAsMod; -using AnnoMapEditor.Utilities; using Microsoft.Win32; using System; -using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Windows; -using System.Windows.Controls; namespace AnnoMapEditor.UI.Windows.Main { @@ -16,16 +13,16 @@ namespace AnnoMapEditor.UI.Windows.Main /// public partial class MainWindow : Window { - private MainWindowViewModel _viewModel => DataContext as MainWindowViewModel - ?? throw new Exception(); + private readonly MainWindowViewModel _viewModel; private readonly string title; - public MainWindow() + public MainWindow(MainWindowViewModel viewModel) { + _viewModel = viewModel; + InitializeComponent(); - DataContext = _viewModel; var exePath = Path.Join(AppContext.BaseDirectory, "AnnoMapEditor.exe"); var productVersion = ""; @@ -41,8 +38,7 @@ public MainWindow() Title = title; _viewModel.PropertyChanged += ViewModel_PropertyChanged; - - CreateImportMenu(openMapMenu, _viewModel.Maps!); + _viewModel.PopulateOpenMapMenu(openMapMenu); } private void ViewModel_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) @@ -55,92 +51,6 @@ private void ViewModel_PropertyChanged(object? sender, System.ComponentModel.Pro else Title = title; break; - - case nameof(MainWindowViewModel.Maps): - CreateImportMenu(openMapMenu, _viewModel.Maps!); - break; - } - } - - private async void OpenFile_Click(object sender, RoutedEventArgs e) - { - var picker = new OpenFileDialog - { - Filter = "Map templates (*.a7tinfo, *.xml)|*.a7tinfo;*.xml" - }; - - if (true == picker.ShowDialog()) - { - int end = picker.FileName.IndexOf(@"\data\session"); - if (end == -1) - end = picker.FileName.IndexOf(@"\data\dlc"); - if (end != -1) - Settings.Instance.GamePath = picker.FileName[..end]; - - await _viewModel.OpenMap(picker.FileName); - } - } - - private void Configure_Click(object _, RoutedEventArgs _1) - { - var picker = new Ookii.Dialogs.Wpf.VistaFolderBrowserDialog - { - UseDescriptionForTitle = true, - Description = "Select your game (i.e. \"Anno 1800/\") folder or a folder where all .rda files are extracted into" - }; - - if (Settings.Instance.GamePath != null) - picker.SelectedPath = Settings.Instance.GamePath; - - if (true == picker.ShowDialog()) - { - Settings.Instance.GamePath = picker.SelectedPath; - } - } - - private void AutoDetect_Click(object _, RoutedEventArgs _1) - { - Settings.Instance.GamePath = Settings.GetInstallDirFromRegistry(); - } - - private void CreateImportMenu(ContextMenu parentMenu, List mapGroups) - { - parentMenu.Items.Clear(); - - MenuItem openFile = new() { Header = "Open file..." }; - openFile.Click += OpenFile_Click; - parentMenu.Items.Add(openFile); - parentMenu.Items.Add(new Separator()); - - MenuItem newFile = new() { Header = "New Map file" }; - newFile.Click += NewMapFile_Click; - parentMenu.Items.Add(newFile); - parentMenu.Items.Add(new Separator()); - - if (mapGroups.Count == 0) - throw new Exception($"Could not locate any maps."); - - foreach (var group in mapGroups) - { - MenuItem groupMenu = new() { Header = group.Name }; - - foreach (var map in group.Maps) - { - MenuItem mapMenu = new() { Header = map.Name, DataContext = map }; - mapMenu.Click += MapMenu_Click; - groupMenu.Items.Add(mapMenu); - } - - parentMenu.Items.Add(groupMenu); - } - } - - private async void MapMenu_Click(object sender, RoutedEventArgs e) - { - MapInfo? mapInfo = (sender as MenuItem)?.DataContext as MapInfo; - if (mapInfo?.FileName is not null) - { - await _viewModel.OpenMap(mapInfo.FileName, true); } } @@ -168,19 +78,5 @@ private void ExportMod_Click(object sender, RoutedEventArgs e) OverlayService.Instance.Show(new ExportAsModViewModel(_viewModel.MapTemplate)); } - - private void NewMapFile_Click(object sender, RoutedEventArgs e) - { - _viewModel.CreateNewMap(); - } - - private void Hyperlink_OpenBrowser(object sender, System.Windows.Navigation.RequestNavigateEventArgs e) - { - var info = new ProcessStartInfo(e.Uri.AbsoluteUri) - { - UseShellExecute = true, - }; - Process.Start(info); - } } } diff --git a/AnnoMapEditor/UI/Windows/Main/MainWindowViewModel.cs b/AnnoMapEditor/UI/Windows/Main/MainWindowViewModel.cs index f575f7c..6a3e848 100644 --- a/AnnoMapEditor/UI/Windows/Main/MainWindowViewModel.cs +++ b/AnnoMapEditor/UI/Windows/Main/MainWindowViewModel.cs @@ -7,10 +7,10 @@ using AnnoMapEditor.UI.Controls.MapTemplates; using AnnoMapEditor.UI.Overlays.SelectIsland; using AnnoMapEditor.Utilities; -using System.Collections.Generic; +using Microsoft.Win32; using System.IO; -using System.Linq; using System.Threading.Tasks; +using System.Windows.Controls; namespace AnnoMapEditor.UI.Windows.Main { @@ -108,29 +108,18 @@ public string? MapTemplateFilePath } private string? _mapTemplateFilePath; - public GamePathStatus GamePathStatus - { - get => _gamePathStatus; - private set => SetProperty(ref _gamePathStatus, value); - } - private GamePathStatus _gamePathStatus = new(); - - public List? Maps - { - get => _maps; - private set => SetProperty(ref _maps, value); - } - private List? _maps; - - public Settings Settings { get; private set; } public MainWindowViewModel(MapTemplate mapTemplate) { MapTemplate = mapTemplate; + } - UpdateStatusAndMenus(); + public MainWindowViewModel() + { + CreateNewMap(); } + public async Task OpenMap(string a7tinfoPath, bool fromArchive = false) { MapTemplateFilePath = Path.GetFileName(a7tinfoPath); @@ -149,7 +138,6 @@ public void CreateNewMap() const int DEFAULT_PLAYABLE_SIZE = 2160; MapTemplateFilePath = null; - MapTemplate = new MapTemplate(DEFAULT_MAP_SIZE, DEFAULT_PLAYABLE_SIZE, SessionAsset.OldWorld); } @@ -167,25 +155,52 @@ public async Task SaveMap(string filePath) await mapTemplateWriter.WriteXmlAsync(MapTemplate, filePath); } - private void UpdateStatusAndMenus() + public void PopulateOpenMapMenu(ContextMenu menu) { - // TODO: load from assets instead. - var mapTemplates = DataManager.Instance.DataArchive.Find("*.a7tinfo"); + menu.Items.Clear(); - Maps = new() + MenuItem openMapFile = new() { Header = "Open file..." }; + openMapFile.Click += (_, _) => OpenMapFileDialog(); + menu.Items.Add(openMapFile); + menu.Items.Add(new Separator()); + + MenuItem newFile = new() { Header = "New Map file" }; + newFile.Click += (_, _) => CreateNewMap(); + menu.Items.Add(newFile); + menu.Items.Add(new Separator()); + + foreach (MapGroup group in DataManager.Instance.MapGroupRepository.MapGroups) { - new MapGroup("Campaign", mapTemplates.Where(x => x.StartsWith(@"data/sessions/maps/campaign")), new(@"\/campaign_([^\/]+)\.")), - new MapGroup("Moderate, Archipelago", mapTemplates.Where(x => x.StartsWith(@"data/sessions/maps/pool/moderate/moderate_archipel")), new(@"\/([^\/]+)\.")), - new MapGroup("Moderate, Atoll", mapTemplates.Where(x => x.StartsWith(@"data/sessions/maps/pool/moderate/moderate_atoll")), new(@"\/([^\/]+)\.")), - new MapGroup("Moderate, Corners", mapTemplates.Where(x => x.StartsWith(@"data/sessions/maps/pool/moderate/moderate_corners")), new(@"\/([^\/]+)\.")), - new MapGroup("Moderate, Island Arc", mapTemplates.Where(x => x.StartsWith(@"data/sessions/maps/pool/moderate/moderate_islandarc")), new(@"\/([^\/]+)\.")), - new MapGroup("Moderate, Snowflake", mapTemplates.Where(x => x.StartsWith(@"data/sessions/maps/pool/moderate/moderate_snowflake")), new(@"\/([^\/]+)\.")), - new MapGroup("New World, Large", mapTemplates.Where(x => x.StartsWith(@"data/sessions/maps/pool/colony01/colony01_l_")), new(@"\/([^\/]+)\.")), - new MapGroup("New World, Medium", mapTemplates.Where(x => x.StartsWith(@"data/sessions/maps/pool/colony01/colony01_m_")), new(@"\/([^\/]+)\.")), - new MapGroup("New World, Small", mapTemplates.Where(x => x.StartsWith(@"data/sessions/maps/pool/colony01/colony01_s_")), new(@"\/([^\/]+)\.")), - new MapGroup("DLCs", mapTemplates.Where(x => !x.StartsWith(@"data/sessions/")), new(@"data\/([^\/]+)\/.+\/maps\/([^\/]+)")) - //new MapGroup("Moderate", mapTemplates.Where(x => x.StartsWith(@"data/sessions/maps/pool/moderate")), new(@"\/([^\/]+)\.")) + MenuItem groupMenu = new() { Header = group.Name }; + + foreach (MapInfo map in group.Maps) + { + MenuItem mapMenu = new() { Header = map.Name, DataContext = map }; + mapMenu.Click += (_, _) => _ = OpenMap(map.FileName!, true); + groupMenu.Items.Add(mapMenu); + } + + menu.Items.Add(groupMenu); + } + } + + public async void OpenMapFileDialog() + { + var picker = new OpenFileDialog + { + Filter = "Map templates (*.a7tinfo, *.xml)|*.a7tinfo;*.xml" }; + + if (true == picker.ShowDialog()) + { + int end = picker.FileName.IndexOf(@"\data\session"); + if (end == -1) + end = picker.FileName.IndexOf(@"\data\dlc"); + if (end != -1) + Settings.Instance.GamePath = picker.FileName[..end]; + + await OpenMap(picker.FileName); + } } } } diff --git a/AnnoMapEditor/UI/Windows/Main/MapInfo.cs b/AnnoMapEditor/UI/Windows/Main/MapInfo.cs index 655662e..041c090 100644 --- a/AnnoMapEditor/UI/Windows/Main/MapInfo.cs +++ b/AnnoMapEditor/UI/Windows/Main/MapInfo.cs @@ -2,8 +2,8 @@ { public class MapInfo { - public string? Name; + public string Name; - public string? FileName; + public string FileName; } } diff --git a/AnnoMapEditor/UI/Windows/Start/StartWindow.xaml b/AnnoMapEditor/UI/Windows/Start/StartWindow.xaml new file mode 100644 index 0000000..cb530d1 --- /dev/null +++ b/AnnoMapEditor/UI/Windows/Start/StartWindow.xaml @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + - + diff --git a/AnnoMapEditor/UI/Windows/Main/MainWindow.xaml.cs b/AnnoMapEditor/UI/Windows/Main/MainWindow.xaml.cs index 82db018..071b489 100644 --- a/AnnoMapEditor/UI/Windows/Main/MainWindow.xaml.cs +++ b/AnnoMapEditor/UI/Windows/Main/MainWindow.xaml.cs @@ -1,13 +1,10 @@ using AnnoMapEditor.UI.Overlays; using AnnoMapEditor.UI.Overlays.ExportAsMod; -using AnnoMapEditor.Utilities; using Microsoft.Win32; using System; -using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Windows; -using System.Windows.Controls; namespace AnnoMapEditor.UI.Windows.Main { @@ -16,16 +13,16 @@ namespace AnnoMapEditor.UI.Windows.Main /// public partial class MainWindow : Window { - private MainWindowViewModel _viewModel => DataContext as MainWindowViewModel - ?? throw new Exception(); + private readonly MainWindowViewModel _viewModel; private readonly string title; - public MainWindow() + public MainWindow(MainWindowViewModel viewModel) { + _viewModel = viewModel; + InitializeComponent(); - DataContext = _viewModel; var exePath = Path.Join(AppContext.BaseDirectory, "AnnoMapEditor.exe"); var productVersion = ""; @@ -41,8 +38,7 @@ public MainWindow() Title = title; _viewModel.PropertyChanged += ViewModel_PropertyChanged; - - CreateImportMenu(openMapMenu, _viewModel.Maps!); + _viewModel.PopulateOpenMapMenu(openMapMenu); } private void ViewModel_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) @@ -55,92 +51,6 @@ private void ViewModel_PropertyChanged(object? sender, System.ComponentModel.Pro else Title = title; break; - - case nameof(MainWindowViewModel.Maps): - CreateImportMenu(openMapMenu, _viewModel.Maps!); - break; - } - } - - private async void OpenFile_Click(object sender, RoutedEventArgs e) - { - var picker = new OpenFileDialog - { - Filter = "Map templates (*.a7tinfo, *.xml)|*.a7tinfo;*.xml" - }; - - if (true == picker.ShowDialog()) - { - int end = picker.FileName.IndexOf(@"\data\session"); - if (end == -1) - end = picker.FileName.IndexOf(@"\data\dlc"); - if (end != -1) - Settings.Instance.GamePath = picker.FileName[..end]; - - await _viewModel.OpenMap(picker.FileName); - } - } - - private void Configure_Click(object _, RoutedEventArgs _1) - { - var picker = new Ookii.Dialogs.Wpf.VistaFolderBrowserDialog - { - UseDescriptionForTitle = true, - Description = "Select your game (i.e. \"Anno 1800/\") folder or a folder where all .rda files are extracted into" - }; - - if (Settings.Instance.GamePath != null) - picker.SelectedPath = Settings.Instance.GamePath; - - if (true == picker.ShowDialog()) - { - Settings.Instance.GamePath = picker.SelectedPath; - } - } - - private void AutoDetect_Click(object _, RoutedEventArgs _1) - { - Settings.Instance.GamePath = Settings.GetInstallDirFromRegistry(); - } - - private void CreateImportMenu(ContextMenu parentMenu, List mapGroups) - { - parentMenu.Items.Clear(); - - MenuItem openFile = new() { Header = "Open file..." }; - openFile.Click += OpenFile_Click; - parentMenu.Items.Add(openFile); - parentMenu.Items.Add(new Separator()); - - MenuItem newFile = new() { Header = "New Map file" }; - newFile.Click += NewMapFile_Click; - parentMenu.Items.Add(newFile); - parentMenu.Items.Add(new Separator()); - - if (mapGroups.Count == 0) - throw new Exception($"Could not locate any maps."); - - foreach (var group in mapGroups) - { - MenuItem groupMenu = new() { Header = group.Name }; - - foreach (var map in group.Maps) - { - MenuItem mapMenu = new() { Header = map.Name, DataContext = map }; - mapMenu.Click += MapMenu_Click; - groupMenu.Items.Add(mapMenu); - } - - parentMenu.Items.Add(groupMenu); - } - } - - private async void MapMenu_Click(object sender, RoutedEventArgs e) - { - MapInfo? mapInfo = (sender as MenuItem)?.DataContext as MapInfo; - if (mapInfo?.FileName is not null) - { - await _viewModel.OpenMap(mapInfo.FileName, true); } } @@ -168,19 +78,5 @@ private void ExportMod_Click(object sender, RoutedEventArgs e) OverlayService.Instance.Show(new ExportAsModViewModel(_viewModel.MapTemplate)); } - - private void NewMapFile_Click(object sender, RoutedEventArgs e) - { - _viewModel.CreateNewMap(); - } - - private void Hyperlink_OpenBrowser(object sender, System.Windows.Navigation.RequestNavigateEventArgs e) - { - var info = new ProcessStartInfo(e.Uri.AbsoluteUri) - { - UseShellExecute = true, - }; - Process.Start(info); - } } } diff --git a/AnnoMapEditor/UI/Windows/Main/MainWindowViewModel.cs b/AnnoMapEditor/UI/Windows/Main/MainWindowViewModel.cs index f575f7c..6a3e848 100644 --- a/AnnoMapEditor/UI/Windows/Main/MainWindowViewModel.cs +++ b/AnnoMapEditor/UI/Windows/Main/MainWindowViewModel.cs @@ -7,10 +7,10 @@ using AnnoMapEditor.UI.Controls.MapTemplates; using AnnoMapEditor.UI.Overlays.SelectIsland; using AnnoMapEditor.Utilities; -using System.Collections.Generic; +using Microsoft.Win32; using System.IO; -using System.Linq; using System.Threading.Tasks; +using System.Windows.Controls; namespace AnnoMapEditor.UI.Windows.Main { @@ -108,29 +108,18 @@ public string? MapTemplateFilePath } private string? _mapTemplateFilePath; - public GamePathStatus GamePathStatus - { - get => _gamePathStatus; - private set => SetProperty(ref _gamePathStatus, value); - } - private GamePathStatus _gamePathStatus = new(); - - public List? Maps - { - get => _maps; - private set => SetProperty(ref _maps, value); - } - private List? _maps; - - public Settings Settings { get; private set; } public MainWindowViewModel(MapTemplate mapTemplate) { MapTemplate = mapTemplate; + } - UpdateStatusAndMenus(); + public MainWindowViewModel() + { + CreateNewMap(); } + public async Task OpenMap(string a7tinfoPath, bool fromArchive = false) { MapTemplateFilePath = Path.GetFileName(a7tinfoPath); @@ -149,7 +138,6 @@ public void CreateNewMap() const int DEFAULT_PLAYABLE_SIZE = 2160; MapTemplateFilePath = null; - MapTemplate = new MapTemplate(DEFAULT_MAP_SIZE, DEFAULT_PLAYABLE_SIZE, SessionAsset.OldWorld); } @@ -167,25 +155,52 @@ public async Task SaveMap(string filePath) await mapTemplateWriter.WriteXmlAsync(MapTemplate, filePath); } - private void UpdateStatusAndMenus() + public void PopulateOpenMapMenu(ContextMenu menu) { - // TODO: load from assets instead. - var mapTemplates = DataManager.Instance.DataArchive.Find("*.a7tinfo"); + menu.Items.Clear(); - Maps = new() + MenuItem openMapFile = new() { Header = "Open file..." }; + openMapFile.Click += (_, _) => OpenMapFileDialog(); + menu.Items.Add(openMapFile); + menu.Items.Add(new Separator()); + + MenuItem newFile = new() { Header = "New Map file" }; + newFile.Click += (_, _) => CreateNewMap(); + menu.Items.Add(newFile); + menu.Items.Add(new Separator()); + + foreach (MapGroup group in DataManager.Instance.MapGroupRepository.MapGroups) { - new MapGroup("Campaign", mapTemplates.Where(x => x.StartsWith(@"data/sessions/maps/campaign")), new(@"\/campaign_([^\/]+)\.")), - new MapGroup("Moderate, Archipelago", mapTemplates.Where(x => x.StartsWith(@"data/sessions/maps/pool/moderate/moderate_archipel")), new(@"\/([^\/]+)\.")), - new MapGroup("Moderate, Atoll", mapTemplates.Where(x => x.StartsWith(@"data/sessions/maps/pool/moderate/moderate_atoll")), new(@"\/([^\/]+)\.")), - new MapGroup("Moderate, Corners", mapTemplates.Where(x => x.StartsWith(@"data/sessions/maps/pool/moderate/moderate_corners")), new(@"\/([^\/]+)\.")), - new MapGroup("Moderate, Island Arc", mapTemplates.Where(x => x.StartsWith(@"data/sessions/maps/pool/moderate/moderate_islandarc")), new(@"\/([^\/]+)\.")), - new MapGroup("Moderate, Snowflake", mapTemplates.Where(x => x.StartsWith(@"data/sessions/maps/pool/moderate/moderate_snowflake")), new(@"\/([^\/]+)\.")), - new MapGroup("New World, Large", mapTemplates.Where(x => x.StartsWith(@"data/sessions/maps/pool/colony01/colony01_l_")), new(@"\/([^\/]+)\.")), - new MapGroup("New World, Medium", mapTemplates.Where(x => x.StartsWith(@"data/sessions/maps/pool/colony01/colony01_m_")), new(@"\/([^\/]+)\.")), - new MapGroup("New World, Small", mapTemplates.Where(x => x.StartsWith(@"data/sessions/maps/pool/colony01/colony01_s_")), new(@"\/([^\/]+)\.")), - new MapGroup("DLCs", mapTemplates.Where(x => !x.StartsWith(@"data/sessions/")), new(@"data\/([^\/]+)\/.+\/maps\/([^\/]+)")) - //new MapGroup("Moderate", mapTemplates.Where(x => x.StartsWith(@"data/sessions/maps/pool/moderate")), new(@"\/([^\/]+)\.")) + MenuItem groupMenu = new() { Header = group.Name }; + + foreach (MapInfo map in group.Maps) + { + MenuItem mapMenu = new() { Header = map.Name, DataContext = map }; + mapMenu.Click += (_, _) => _ = OpenMap(map.FileName!, true); + groupMenu.Items.Add(mapMenu); + } + + menu.Items.Add(groupMenu); + } + } + + public async void OpenMapFileDialog() + { + var picker = new OpenFileDialog + { + Filter = "Map templates (*.a7tinfo, *.xml)|*.a7tinfo;*.xml" }; + + if (true == picker.ShowDialog()) + { + int end = picker.FileName.IndexOf(@"\data\session"); + if (end == -1) + end = picker.FileName.IndexOf(@"\data\dlc"); + if (end != -1) + Settings.Instance.GamePath = picker.FileName[..end]; + + await OpenMap(picker.FileName); + } } } } diff --git a/AnnoMapEditor/UI/Windows/Main/MapInfo.cs b/AnnoMapEditor/UI/Windows/Main/MapInfo.cs index 655662e..041c090 100644 --- a/AnnoMapEditor/UI/Windows/Main/MapInfo.cs +++ b/AnnoMapEditor/UI/Windows/Main/MapInfo.cs @@ -2,8 +2,8 @@ { public class MapInfo { - public string? Name; + public string Name; - public string? FileName; + public string FileName; } } diff --git a/AnnoMapEditor/UI/Windows/Start/StartWindow.xaml b/AnnoMapEditor/UI/Windows/Start/StartWindow.xaml new file mode 100644 index 0000000..cb530d1 --- /dev/null +++ b/AnnoMapEditor/UI/Windows/Start/StartWindow.xaml @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +