Files
AdamuSw/src/Persistence/Initialization/DataInitializationBase.cs
2026-07-14 19:00:35 +03:00

312 lines
13 KiB
C#

// <copyright file="DataInitializationBase.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.Persistence.Initialization;
using System.ComponentModel.Design;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Logging;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.GameLogic.PlayerActions.ItemConsumeActions;
using MUnique.OpenMU.GameLogic.Resets;
using MUnique.OpenMU.GameServer.MessageHandler;
using MUnique.OpenMU.Network;
using MUnique.OpenMU.Network.PlugIns;
using MUnique.OpenMU.Persistence.Initialization.Updates;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Class to manage data initialization.
/// </summary>
public abstract class DataInitializationBase : IDataInitializationPlugIn
{
private readonly IPersistenceContextProvider _persistenceContextProvider;
private readonly ILoggerFactory _loggerFactory;
private GameConfiguration? _gameConfiguration;
private IContext? _context;
/// <summary>
/// Initializes a new instance of the <see cref="DataInitializationBase" /> class.
/// </summary>
/// <param name="persistenceContextProvider">The persistence context provider.</param>
/// <param name="loggerFactory">The logger factory.</param>
protected DataInitializationBase(IPersistenceContextProvider persistenceContextProvider, ILoggerFactory loggerFactory)
{
this._persistenceContextProvider = persistenceContextProvider;
this._loggerFactory = loggerFactory;
}
/// <inheritdoc />
public abstract string Key { get; }
/// <inheritdoc />
public abstract string Caption { get; }
/// <summary>
/// Gets or sets the game configuration.
/// </summary>
/// <exception cref="InvalidOperationException">not initialized yet.</exception>
protected GameConfiguration GameConfiguration
{
get => this._gameConfiguration ?? throw new InvalidOperationException("not initialized yet.");
set => this._gameConfiguration = value;
}
/// <summary>
/// Gets or sets the context.
/// </summary>
/// <exception cref="InvalidOperationException">not initialized yet.</exception>
protected IContext Context
{
get => this._context ?? throw new InvalidOperationException("not initialized yet.");
set => this._context = value;
}
/// <summary>
/// Gets the game configuration initializer.
/// </summary>
protected abstract IInitializer GameConfigurationInitializer { get; }
/// <summary>
/// Gets the maps initializer.
/// </summary>
protected abstract IGameMapsInitializer GameMapsInitializer { get; }
/// <summary>
/// Gets the test accounts initializer.
/// </summary>
protected abstract IInitializer? TestAccountsInitializer { get; }
/// <summary>
/// Creates the initial data for a server.
/// </summary>
/// <param name="numberOfGameServers">The number of game servers.</param>
/// <param name="createTestAccounts">If set to <c>true</c>, test accounts should be created.</param>
public async Task CreateInitialDataAsync(byte numberOfGameServers, bool createTestAccounts)
{
BaseMapInitializer.ClearDefaultDropItemGroups();
using (var temporaryContext = this._persistenceContextProvider.CreateNewContext())
{
this.GameConfiguration = temporaryContext.CreateNew<GameConfiguration>();
this.GameConfiguration.SetGuid(1);
this.CreateSystemConfiguration(temporaryContext);
using var tempSuspension = temporaryContext.SuspendChangeNotifications();
await temporaryContext.SaveChangesAsync().ConfigureAwait(false);
}
using var contextWithConfiguration = this._persistenceContextProvider.CreateNewContext(this.GameConfiguration);
using var notificationSuspension = contextWithConfiguration.SuspendChangeNotifications();
this.Context = contextWithConfiguration;
this.CreateGameClientDefinition();
await this.CreateChatServerDefinitionAsync().ConfigureAwait(false);
this.GameConfigurationInitializer.Initialize();
var gameServerConfiguration = this.CreateGameServerConfiguration(this.GameConfiguration.Maps);
await this.CreateGameServerDefinitionsAsync(gameServerConfiguration, numberOfGameServers).ConfigureAwait(false);
await this.CreateConnectServerDefinitionAsync().ConfigureAwait(false);
await this.Context.SaveChangesAsync().ConfigureAwait(false);
this.GameMapsInitializer.SetSafezoneMaps();
if (createTestAccounts)
{
this.TestAccountsInitializer?.Initialize();
}
if (!AppDomain.CurrentDomain.GetAssemblies().Contains(typeof(GameServer.GameServer).Assembly))
{
// should never happen, but the access to the GameServer type is a trick to load the assembly into the current domain.
}
var dataSource = new GameConfigurationDataSource(this._loggerFactory.CreateLogger<GameConfigurationDataSource>(), this._persistenceContextProvider);
await dataSource.GetOwnerAsync(this.GameConfiguration.GetId());
var referenceHandler = new ByDataSourceReferenceHandler(dataSource);
var serviceContainer = new ServiceContainer();
serviceContainer.AddService(typeof(IPersistenceContextProvider), this._persistenceContextProvider);
var plugInManager = new PlugInManager(null, this._loggerFactory, serviceContainer, referenceHandler);
plugInManager.DiscoverAndRegisterPlugIns();
plugInManager.KnownPlugInTypes.ForEach(plugInType =>
{
var plugInConfiguration = this.Context.CreateNew<PlugInConfiguration>();
plugInConfiguration.SetGuid(plugInType.GUID);
plugInConfiguration.TypeId = plugInType.GUID;
plugInConfiguration.IsActive = !plugInType.IsAssignableTo(typeof(IDisabledByDefault));
this.GameConfiguration.PlugInConfigurations.Add(plugInConfiguration);
if (plugInType.GetInterfaces().Contains(typeof(ISupportDefaultCustomConfiguration)))
{
this.CreateDefaultPlugInConfiguration(plugInType, plugInConfiguration, referenceHandler);
}
if (plugInType == typeof(BlessJewelConsumeHandlerPlugIn))
{
var config = new BlessJewelConsumeHandlerPlugInConfiguration
{
MaximumLevel = 5,
MinimumLevel = 0,
SuccessRatePercentage = 100,
SuccessRateBonusWithLuckPercentage = 0,
ResetToLevel0WhenFailMinLevel = 0,
};
if (this.GameConfiguration.Items.FirstOrDefault(item => item is { Group: 13, Number: 37 }) is { } fenrir)
{
config.RepairTargetItems.Add(fenrir);
}
plugInConfiguration.SetConfiguration(config, referenceHandler);
}
// We don't move the player anymore by his request. This was usually requested after a player performed a skill.
// However, it adds way for cheaters to move through the map.
// The plugin is therefore deactivated by default.
if (plugInType.IsAssignableTo(typeof(CharacterMoveBaseHandlerPlugIn)))
{
plugInConfiguration.IsActive = false;
}
// Disable plugins marked as disabled by default.
if (plugInType.IsAssignableTo(typeof(IDisabledByDefault)))
{
plugInConfiguration.IsActive = false;
}
});
this.AddAllUpdateEntries(plugInManager);
await this.Context.SaveChangesAsync().ConfigureAwait(false);
}
/// <summary>
/// Creates the game client definition.
/// </summary>
protected abstract void CreateGameClientDefinition();
private void CreateDefaultPlugInConfiguration(Type plugInType, PlugInConfiguration plugInConfiguration, ReferenceHandler referenceHandler)
{
try
{
var plugin = (ISupportDefaultCustomConfiguration)Activator.CreateInstance(plugInType)!;
var defaultConfig = plugin.CreateDefaultConfig();
plugInConfiguration.SetConfiguration(defaultConfig, referenceHandler);
}
catch (Exception ex)
{
this._loggerFactory.CreateLogger(this.GetType()).LogWarning(ex, "Could not create custom default configuration for plugin type {plugInType}", plugInType);
}
}
private void AddAllUpdateEntries(PlugInManager plugInManager)
{
var updates = plugInManager.GetStrategyProvider<int, IConfigurationUpdatePlugIn>()
?.AvailableStrategies.Where(up => up.DataInitializationKey == this.Key)
.OrderBy(up => up.Version)
.ToList();
if (updates is not { Count: > 0 })
{
return;
}
foreach (var update in updates)
{
var entry = this.Context.CreateNew<ConfigurationUpdate>();
entry.Version = (int)update.Version;
entry.Name = update.Name;
entry.Description = update.Description;
entry.CreatedAt = update.CreatedAt;
entry.InstalledAt = DateTime.UtcNow;
}
var updateState = this.Context.CreateNew<ConfigurationUpdateState>();
updateState.InitializationKey = this.Key;
updateState.CurrentInstalledVersion = updates.Max(u => (int)u.Version);
}
private async ValueTask CreateConnectServerDefinitionAsync()
{
var port = 44405;
var clients = await this.Context.GetAsync<GameClientDefinition>().ConfigureAwait(false);
foreach (var client in clients.OrderBy(c => c.Season))
{
var connectServer = this.Context.CreateNew<ConnectServerDefinition>();
connectServer.InitializeDefaults();
connectServer.SetGuid(client.Season, client.Episode);
connectServer.Client = client;
connectServer.ClientListenerPort = port;
connectServer.Description = $"Connect Server ({client.Description})";
port++;
}
}
private async ValueTask CreateGameServerDefinitionsAsync(GameServerConfiguration gameServerConfiguration, int numberOfServers)
{
var port = 55901;
for (int i = 0; i < numberOfServers; i++)
{
var server = this.Context!.CreateNew<GameServerDefinition>();
server.SetGuid((short)i);
server.ServerID = (byte)i;
server.Description = $"Server {i}";
server.ExperienceRate = 1.0f;
server.GameConfiguration = this.GameConfiguration;
server.ServerConfiguration = gameServerConfiguration;
foreach (var client in await this.Context.GetAsync<GameClientDefinition>().ConfigureAwait(false))
{
var endPoint = this.Context.CreateNew<GameServerEndpoint>();
endPoint.SetGuid((short)i, (short)server.Endpoints.Count);
endPoint.Client = client;
endPoint.NetworkPort = port;
server.Endpoints.Add(endPoint);
port++;
}
}
}
private async ValueTask CreateChatServerDefinitionAsync()
{
var server = this.Context!.CreateNew<ChatServerDefinition>();
server.SetGuid(0);
server.ServerId = 0;
server.Description = "Chat Server";
var client = (await this.Context!.GetAsync<GameClientDefinition>().ConfigureAwait(false)).First();
var endPoint = this.Context.CreateNew<ChatServerEndpoint>();
server.SetGuid(0);
endPoint.Client = client;
endPoint.NetworkPort = 55980;
server.Endpoints.Add(endPoint);
}
private GameServerConfiguration CreateGameServerConfiguration(ICollection<GameMapDefinition> maps)
{
var gameServerConfiguration = this.Context.CreateNew<GameServerConfiguration>();
gameServerConfiguration.SetGuid(0);
gameServerConfiguration.MaximumPlayers = 1000;
// by default we add every map to a server configuration
foreach (var map in maps)
{
gameServerConfiguration.Maps.Add(map);
}
return gameServerConfiguration;
}
private void CreateSystemConfiguration(IContext context)
{
var systemConfiguration = context.CreateNew<SystemConfiguration>();
systemConfiguration.SetGuid(0);
systemConfiguration.AutoStart = true;
systemConfiguration.AutoUpdateSchema = true;
systemConfiguration.ReadConsoleInput = false;
var (type, param) = IpAddressResolverFactory.DetermineBestFittingResolver(Environment.GetCommandLineArgs());
systemConfiguration.IpResolver = type;
systemConfiguration.IpResolverParameter = param;
}
}