Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AnnoMapEditor.Tests/Utils/TheoryWithGameFilesAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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...";
Expand Down
30 changes: 30 additions & 0 deletions AnnoMapEditor/App.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
<section name="AnnoMapEditor.UserSettings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
</sectionGroup>
</configSections>
<userSettings>
<AnnoMapEditor.UserSettings>
<setting name="GamePath" serializeAs="String">
<value />
</setting>
<setting name="DataPath" serializeAs="String">
<value />
</setting>
<setting name="ModsPath" serializeAs="String">
<value />
</setting>
<setting name="EnableExpertMode" serializeAs="String">
<value>False</value>
</setting>
<setting name="AssetsHash" serializeAs="String">
<value />
</setting>
<setting name="Xpath" serializeAs="String">
<value />
</setting>
</AnnoMapEditor.UserSettings>
</userSettings>
</configuration>
2 changes: 1 addition & 1 deletion AnnoMapEditor/App.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -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">
<Application.Resources>
<ResourceDictionary Source="UI/Resources/Styles/Colors.xaml" />
</Application.Resources>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;

namespace AnnoMapEditor.DataArchives.Assets.Deserialization
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class AssetTemplateAttribute : Attribute
{
public string[] TemplateNames { get; init; }


public AssetTemplateAttribute(params string[] templateNames)
{
TemplateNames = templateNames;
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<GuidReferenceResolverFactory> _logger;


public GuidReferenceResolverFactory(AssetRepository assetRepository)
{
_assetRepository = assetRepository;
_logger = new Logger<GuidReferenceResolverFactory>();
}


public Action<object>? CreateResolver<TAsset>(PropertyInfo referenceProperty)
where TAsset : StandardAsset
{
// determine if this property is a reference to another asset
GuidReferenceAttribute? referenceAttribute = referenceProperty.GetCustomAttribute<GuidReferenceAttribute>();
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<TAsset>(referenceProperty, guidProperty);

else if (guidProperty.PropertyType == typeof(IEnumerable<long>))
return CreateEnumerableResolver<TAsset>(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<long>).FullName}.");
}

private Action<object> CreateSingleResolver<TAsset>(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<object> CreateEnumerableResolver<TAsset>(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<long>? guids = guidProperty.GetValue(asset) as IEnumerable<long>;
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;
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
Original file line number Diff line number Diff line change
@@ -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<RegionIdReferenceResolverFactory> _logger;


public RegionIdReferenceResolverFactory(AssetRepository assetRepository)
{
_assetRepository = assetRepository;
_logger = new Logger<RegionIdReferenceResolverFactory>();
}


public Action<object>? CreateResolver<TAsset>(PropertyInfo referenceProperty)
where TAsset : StandardAsset
{
// determine if this property is a reference to a RegionAsset
RegionIdReferenceAttribute? referenceAttribute = referenceProperty.GetCustomAttribute<RegionIdReferenceAttribute>();
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<object> resolver;
if (regionIdProperty.PropertyType == typeof(string))
return CreateSingleResolver<TAsset>(referenceProperty, regionIdProperty);

else if (regionIdProperty.PropertyType == typeof(IEnumerable<string>))
return CreateEnumerableResolver<TAsset>(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<string>).FullName}.");
}

private Action<object> CreateSingleResolver<TAsset>(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<RegionAsset>().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<object> CreateEnumerableResolver<TAsset>(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<string>? regionIds = regionIdProperty.GetValue(asset) as IEnumerable<string>;
if (regionIds != null)
{
List<RegionAsset> regionAssets = _assetRepository.GetAll<RegionAsset>().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);
}
};
}
}
}
8 changes: 5 additions & 3 deletions AnnoMapEditor/DataArchives/Assets/Models/FertilityAsset.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
using AnnoMapEditor.DataArchives.Assets.Attributes;
using System.Windows.Media;
using AnnoMapEditor.DataArchives.Assets.Deserialization;
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; }


Expand Down
Loading