Skip to content

Commit

Permalink
Merge pull request #405 from MUnique/custom-plugin-config-ext
Browse files Browse the repository at this point in the history
Support references to config objects in custom plugin configurations
  • Loading branch information
sven-n authored May 6, 2024
2 parents 0939e5a + 060a39d commit de77e18
Show file tree
Hide file tree
Showing 25 changed files with 263 additions and 88 deletions.
2 changes: 1 addition & 1 deletion src/ChatServer/ExDbConnector/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ internal static async Task Main(string[] args)
// To make the chat server use our configured encryption key, we need to trick a bit. We add an endpoint with a special client version which is defined in the plugin.
var configuration = new ChatServerSettings();
configuration.Endpoints.Add(new ChatServerEndpoint { ClientVersion = ConfigurableNetworkEncryptionPlugIn.Version, NetworkPort = chatServerListenerPort });
var pluginManager = new PlugInManager(null, loggerFactory, serviceContainer);
var pluginManager = new PlugInManager(null, loggerFactory, serviceContainer, null);
pluginManager.DiscoverAndRegisterPlugInsOf<INetworkEncryptionFactoryPlugIn>();
var chatServer = new ChatServer(addressResolver, loggerFactory, pluginManager);
chatServer.Initialize(configuration);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,26 @@ namespace MUnique.OpenMU.GameLogic.PlayerActions.ItemConsumeActions;
/// </summary>
[Guid("E95A0292-B3B4-4E8C-AC5A-7F3DB4F01A37")]
[PlugIn(nameof(BlessJewelConsumeHandlerPlugIn), "Plugin which handles the jewel of bless consumption.")]
public class BlessJewelConsumeHandlerPlugIn : ItemModifyConsumeHandlerPlugIn
public class BlessJewelConsumeHandlerPlugIn : ItemModifyConsumeHandlerPlugIn, ISupportCustomConfiguration<BlessJewelConsumeHandlerPlugInConfiguration>
{
/// <inheritdoc />
public override ItemIdentifier Key => ItemConstants.JewelOfBless;

/// <summary>
/// Gets or sets the configuration.
/// </summary>
public BlessJewelConsumeHandlerPlugInConfiguration? Configuration { get; set; }

/// <inheritdoc/>
protected override bool ModifyItem(Item item, IContext persistenceContext)
{
if (this.Configuration?.RepairTargetItems.Contains(item.Definition!) is true
&& item.Durability < item.GetMaximumDurabilityOfOnePiece())
{
item.Durability = item.GetMaximumDurabilityOfOnePiece();
return true;
}

if (!item.CanLevelBeUpgraded())
{
return false;
Expand All @@ -37,6 +49,7 @@ protected override bool ModifyItem(Item item, IContext persistenceContext)

level++;
item.Level = level;
item.Durability = item.GetMaximumDurabilityOfOnePiece();
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// <copyright file="BlessJewelConsumeHandlerPlugInConfiguration.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>

namespace MUnique.OpenMU.GameLogic.PlayerActions.ItemConsumeActions;

using MUnique.OpenMU.DataModel.Configuration.Items;

/// <summary>
/// The configuration for the <see cref="BlessJewelConsumeHandlerPlugIn"/>.
/// </summary>
public class BlessJewelConsumeHandlerPlugInConfiguration
{
/// <summary>
/// Gets or sets the items which can be repaired by consuming a bless on them.
/// </summary>
public ICollection<ItemDefinition> RepairTargetItems { get; set; } = new List<ItemDefinition>();
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ protected override bool ModifyItem(Item item, IContext persistenceContext)
if (this._randomizer.NextRandomBool(percent))
{
item.Level++;
item.Durability = item.GetMaximumDurabilityOfOnePiece();
return true; // true doesn't mean that it was successful, just that the consumption happened.
}

Expand Down
2 changes: 1 addition & 1 deletion src/Network/Analyzer/MainForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public MainForm()
this.InitializeComponent();
var serviceContainer = new ServiceContainer();
serviceContainer.AddService(typeof(ILoggerFactory), new NullLoggerFactory());
this._plugInManager = new PlugInManager(null, new NullLoggerFactory(), serviceContainer);
this._plugInManager = new PlugInManager(null, new NullLoggerFactory(), serviceContainer, null);
this._plugInManager.DiscoverAndRegisterPlugIns();
this.clientBindingSource.DataSource = this._proxiedConnections;
this.connectedClientsListBox.DisplayMember = nameof(ICapturedConnection.Name);
Expand Down
34 changes: 34 additions & 0 deletions src/Persistence/ByDataSourceReferenceHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// <copyright file="ByDataSourceReferenceHandler.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>

namespace MUnique.OpenMU.Persistence;

using System.Text.Json.Serialization;
using MUnique.OpenMU.DataModel.Configuration;

/// <summary>
/// A reference handler which uses a <see cref="IDataSource{T}"/> to resolve references.
/// </summary>
public class ByDataSourceReferenceHandler : ReferenceHandler
{
/// <summary>
/// The data source.
/// </summary>
private readonly IDataSource<GameConfiguration> _dataSource;

/// <summary>
/// Initializes a new instance of the <see cref="ByDataSourceReferenceHandler"/> class.
/// </summary>
/// <param name="dataSource">The data source.</param>
public ByDataSourceReferenceHandler(IDataSource<GameConfiguration> dataSource)
{
this._dataSource = dataSource;
}

/// <inheritdoc />
public override ReferenceResolver CreateResolver()
{
return new ByDataSourceReferenceResolver(this._dataSource);
}
}
54 changes: 54 additions & 0 deletions src/Persistence/ByDataSourceReferenceResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// <copyright file="ByDataSourceReferenceResolver.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>

namespace MUnique.OpenMU.Persistence;

using System.Text.Json.Serialization;
using MUnique.OpenMU.DataModel.Configuration;

/// <summary>
/// A reference resolver which uses a <see cref="IDataSource{T}"/> to resolve references.
/// </summary>
public class ByDataSourceReferenceResolver : ReferenceResolver
{
/// <summary>
/// The data source.
/// </summary>
private readonly IDataSource<GameConfiguration> _dataSource;

/// <summary>
/// Initializes a new instance of the <see cref="ByDataSourceReferenceResolver"/> class.
/// </summary>
/// <param name="dataSource">The data source.</param>
public ByDataSourceReferenceResolver(IDataSource<GameConfiguration> dataSource)
{
this._dataSource = dataSource;
}

/// <inheritdoc />
public override void AddReference(string referenceId, object value)
{
// do nothing here, because the data source is the source of truth.
}

/// <inheritdoc />
public override string GetReference(object value, out bool alreadyExists)
{
if (value is IIdentifiable identifiable)
{
alreadyExists = this._dataSource.Get(identifiable.Id) is { };
return identifiable.Id.ToString();
}

alreadyExists = false;
return string.Empty;
}

/// <inheritdoc />
public override object ResolveReference(string referenceId)
{
var id = Guid.Parse(referenceId);
return this._dataSource.Get(id) ?? throw new KeyNotFoundException($"Reference with id '{referenceId}' not found.");
}
}
15 changes: 8 additions & 7 deletions src/Persistence/Initialization/DataInitializationBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,10 @@ public async Task CreateInitialDataAsync(byte numberOfGameServers, bool createTe
// should never happen, but the access to the GameServer type is a trick to load the assembly into the current domain.
}

var referenceHandler = new ByDataSourceReferenceHandler(new GameConfigurationDataSource(this._loggerFactory.CreateLogger<GameConfigurationDataSource>(), this._persistenceContextProvider));
var serviceContainer = new ServiceContainer();
serviceContainer.AddService(typeof(IPersistenceContextProvider), this._persistenceContextProvider);
var plugInManager = new PlugInManager(null, this._loggerFactory, serviceContainer);
var plugInManager = new PlugInManager(null, this._loggerFactory, serviceContainer, referenceHandler);
plugInManager.DiscoverAndRegisterPlugIns();
plugInManager.KnownPlugInTypes.ForEach(plugInType =>
{
Expand All @@ -136,32 +137,32 @@ public async Task CreateInitialDataAsync(byte numberOfGameServers, bool createTe
if (plugInType == typeof(ResetFeaturePlugIn))
{
plugInConfiguration.IsActive = false;
plugInConfiguration.SetConfiguration(new ResetConfiguration());
plugInConfiguration.SetConfiguration(new ResetConfiguration(), referenceHandler);
}

if (plugInType == typeof(ChaosCastleStartPlugIn))
{
plugInConfiguration.SetConfiguration(ChaosCastleStartConfiguration.Default);
plugInConfiguration.SetConfiguration(ChaosCastleStartConfiguration.Default, referenceHandler);
}

if (plugInType == typeof(GoldenInvasionPlugIn))
{
plugInConfiguration.SetConfiguration(PeriodicInvasionConfiguration.DefaultGoldenInvasion);
plugInConfiguration.SetConfiguration(PeriodicInvasionConfiguration.DefaultGoldenInvasion, referenceHandler);
}

if (plugInType == typeof(RedDragonInvasionPlugIn))
{
plugInConfiguration.SetConfiguration(PeriodicInvasionConfiguration.DefaultRedDragonInvasion);
plugInConfiguration.SetConfiguration(PeriodicInvasionConfiguration.DefaultRedDragonInvasion, referenceHandler);
}

if (plugInType == typeof(HappyHourPlugIn))
{
plugInConfiguration.SetConfiguration(HappyHourConfiguration.Default);
plugInConfiguration.SetConfiguration(HappyHourConfiguration.Default, referenceHandler);
}

if (plugInType == typeof(MuHelperFeaturePlugIn))
{
plugInConfiguration.SetConfiguration(new MuHelperConfiguration());
plugInConfiguration.SetConfiguration(new MuHelperConfiguration(), referenceHandler);
}

// We don't move the player anymore by his request. This was usually requested after a player performed a skill.
Expand Down
34 changes: 26 additions & 8 deletions src/PlugIns/PlugInConfigurationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace MUnique.OpenMU.PlugIns;

using System.Text.Json;
using System.Text.Json.Serialization;

/// <summary>
/// Extension methods for the plugin configuration.
Expand All @@ -16,36 +17,48 @@ public static class PlugInConfigurationExtensions
/// </summary>
/// <typeparam name="T">The custom configuration type.</typeparam>
/// <param name="configuration">The configuration.</param>
/// <param name="referenceHandler">The reference handler.</param>
/// <returns>
/// The custom configuration as <typeparamref name="T" />.
/// </returns>
public static T? GetConfiguration<T>(this PlugInConfiguration configuration)
public static T? GetConfiguration<T>(this PlugInConfiguration configuration, ReferenceHandler? referenceHandler)
where T : class
{
if (string.IsNullOrWhiteSpace(configuration.CustomConfiguration))
{
return default;
}

return JsonSerializer.Deserialize<T>(configuration.CustomConfiguration);
var options = new JsonSerializerOptions
{
ReferenceHandler = referenceHandler,
};

return JsonSerializer.Deserialize<T>(configuration.CustomConfiguration, options);
}

/// <summary>
/// Gets the configuration.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="configurationType">Type of the configuration.</param>
/// <param name="referenceHandler">The reference handler.</param>
/// <returns>
/// The custom configuration as the given specified type.
/// </returns>
public static object? GetConfiguration(this PlugInConfiguration configuration, Type configurationType)
public static object? GetConfiguration(this PlugInConfiguration configuration, Type configurationType, ReferenceHandler? referenceHandler)
{
if (string.IsNullOrWhiteSpace(configuration.CustomConfiguration))
{
return default;
}

return JsonSerializer.Deserialize(configuration.CustomConfiguration, configurationType);
var options = new JsonSerializerOptions
{
ReferenceHandler = referenceHandler,
};

return JsonSerializer.Deserialize(configuration.CustomConfiguration, configurationType, options);
}

/// <summary>
Expand All @@ -54,18 +67,23 @@ public static class PlugInConfigurationExtensions
/// <typeparam name="T">The custom configuration type.</typeparam>
/// <param name="plugInConfiguration">The plug in configuration.</param>
/// <param name="configuration">The configuration.</param>
public static void SetConfiguration<T>(this PlugInConfiguration plugInConfiguration, T configuration)
/// <param name="referenceHandler">The reference handler.</param>
public static void SetConfiguration<T>(this PlugInConfiguration plugInConfiguration, T configuration, ReferenceHandler? referenceHandler)
{
plugInConfiguration.CustomConfiguration = JsonSerializer.Serialize(configuration, new JsonSerializerOptions { WriteIndented = true });
plugInConfiguration.CustomConfiguration = JsonSerializer.Serialize(configuration, new JsonSerializerOptions { WriteIndented = true, ReferenceHandler = referenceHandler });
}

/// <summary>
/// Sets the configuration.
/// </summary>
/// <param name="plugInConfiguration">The plug in configuration.</param>
/// <param name="configuration">The configuration.</param>
public static void SetConfiguration(this PlugInConfiguration plugInConfiguration, object configuration)
/// <param name="referenceHandler">The reference handler.</param>
public static void SetConfiguration(this PlugInConfiguration plugInConfiguration, object configuration, ReferenceHandler? referenceHandler)
{
plugInConfiguration.CustomConfiguration = JsonSerializer.Serialize(configuration, configuration.GetType(), new JsonSerializerOptions { WriteIndented = true });
plugInConfiguration.CustomConfiguration = JsonSerializer.Serialize(
configuration,
configuration.GetType(),
new JsonSerializerOptions { WriteIndented = true, ReferenceHandler = referenceHandler });
}
}
2 changes: 1 addition & 1 deletion src/PlugIns/PlugInContainerBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ private void OnPlugInConfigurationChanged(object? sender, PlugInConfigurationCha
is { } configSupportInterface)
{
var configType = configSupportInterface.GenericTypeArguments[0];
var typedCustomConfiguration = e.Configuration.GetConfiguration(configType);
var typedCustomConfiguration = e.Configuration.GetConfiguration(configType, this.Manager.CustomConfigReferenceHandler);
configSupportInterface
.GetProperty(nameof(ISupportCustomConfiguration<object>.Configuration))
?.SetMethod
Expand Down
10 changes: 9 additions & 1 deletion src/PlugIns/PlugInManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace MUnique.OpenMU.PlugIns;
using System.ComponentModel.Design;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text.Json.Serialization;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
Expand All @@ -32,7 +33,7 @@ public class PlugInManager
/// <param name="configurations">The configurations.</param>
/// <param name="loggerFactory">The logger factory.</param>
/// <param name="serviceProvider">The service provider.</param>
public PlugInManager(ICollection<PlugInConfiguration>? configurations, ILoggerFactory loggerFactory, IServiceProvider? serviceProvider)
public PlugInManager(ICollection<PlugInConfiguration>? configurations, ILoggerFactory loggerFactory, IServiceProvider? serviceProvider, ReferenceHandler? customConfigReferenceHandler)

Check warning on line 36 in src/PlugIns/PlugInManager.cs

View workflow job for this annotation

GitHub Actions / build

Parameter 'customConfigReferenceHandler' has no matching param tag in the XML comment for 'PlugInManager.PlugInManager(ICollection<PlugInConfiguration>?, ILoggerFactory, IServiceProvider?, ReferenceHandler?)' (but other parameters do)
{
_ = typeof(Nito.AsyncEx.AsyncReaderWriterLock); // Ensure Nito.AsyncEx.Coordination is loaded so it will be available in proxy generation.

Expand All @@ -41,6 +42,8 @@ public PlugInManager(ICollection<PlugInConfiguration>? configurations, ILoggerFa
this._serviceContainer.AddService(typeof(PlugInManager), this);
this._serviceContainer.AddService(typeof(ILoggerFactory), loggerFactory);

this.CustomConfigReferenceHandler = customConfigReferenceHandler;

if (configurations is not null)
{
this.DiscoverAndRegisterPlugIns();
Expand Down Expand Up @@ -75,6 +78,11 @@ public PlugInManager(ICollection<PlugInConfiguration>? configurations, ILoggerFa
/// </value>
public IEnumerable<Type> KnownPlugInTypes => this._knownPlugIns.Values;

/// <summary>
/// Gets the reference handler for references in custom plugin configurations.
/// </summary>
public ReferenceHandler? CustomConfigReferenceHandler { get; }

/// <summary>
/// Discovers and registers all plug ins of all loaded assemblies.
/// </summary>
Expand Down
Loading

0 comments on commit de77e18

Please sign in to comment.