baseline: OpenMU upstream b5a0961 (fresh source)
This commit is contained in:
37
src/GameLogic/Resets/GatekeeperNpcPlugin.cs
Normal file
37
src/GameLogic/Resets/GatekeeperNpcPlugin.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
// <copyright file="GatekeeperNpcPlugin.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameLogic.Resets;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.GameLogic.NPC;
|
||||
using MUnique.OpenMU.GameLogic.PlugIns;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// Handles the gatekeeper npc in the Barracks of Balgass.
|
||||
/// </summary>
|
||||
[Guid("1B7BCA14-3124-4550-94B4-3FFCEE1FD55A")]
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.GatekeeperNpcPlugin_Name), Description = nameof(PlugInResources.GatekeeperNpcPlugin_Description), ResourceType = typeof(PlugInResources))]
|
||||
public class GatekeeperNpcPlugin : IPlayerTalkToNpcPlugIn
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the NPC number of 'Gatekeeper' in Barracks of Balgass.
|
||||
/// </summary>
|
||||
public static short GatekeeperNpcNumber => 408;
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask PlayerTalksToNpcAsync(Player player, NonPlayerCharacter npc, NpcTalkEventArgs eventArgs)
|
||||
{
|
||||
if (npc.Definition.Number != GatekeeperNpcNumber)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// The client opens the dialog itself, so we don't need to do anything here.
|
||||
eventArgs.HasBeenHandled = true;
|
||||
eventArgs.LeavesDialogOpen = true;
|
||||
}
|
||||
}
|
||||
221
src/GameLogic/Resets/ResetCharacterAction.cs
Normal file
221
src/GameLogic/Resets/ResetCharacterAction.cs
Normal file
@@ -0,0 +1,221 @@
|
||||
// <copyright file="ResetCharacterAction.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameLogic.Resets;
|
||||
|
||||
using MUnique.OpenMU.GameLogic.Attributes;
|
||||
using MUnique.OpenMU.GameLogic.NPC;
|
||||
using MUnique.OpenMU.GameLogic.PlayerActions;
|
||||
using MUnique.OpenMU.GameLogic.Views.Character;
|
||||
using MUnique.OpenMU.GameLogic.Views.Login;
|
||||
using MUnique.OpenMU.GameLogic.Views.NPC;
|
||||
|
||||
/// <summary>
|
||||
/// Action to reset a character.
|
||||
/// </summary>
|
||||
public class ResetCharacterAction
|
||||
{
|
||||
private readonly Player _player;
|
||||
private readonly NonPlayerCharacter? _npc;
|
||||
private readonly LogoutAction _logoutAction = new();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ResetCharacterAction"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">Player to reset.</param>
|
||||
/// <param name="npc">NPC which the player talks to to initiate the reset action.</param>
|
||||
public ResetCharacterAction(Player player, NonPlayerCharacter? npc = null)
|
||||
{
|
||||
this._player = player;
|
||||
this._npc = npc;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset specific character.
|
||||
/// </summary>
|
||||
public async ValueTask ResetCharacterAsync()
|
||||
{
|
||||
var resetFeature = this._player.GameContext.FeaturePlugIns.GetPlugIn<ResetFeaturePlugIn>();
|
||||
if (resetFeature is null)
|
||||
{
|
||||
await this.ShowMessageAsync(nameof(PlayerMessage.ResetNotEnabled)).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._player.PlayerState.CurrentState != PlayerState.EnteredWorld && this._npc is null)
|
||||
{
|
||||
await this.ShowMessageAsync(nameof(PlayerMessage.CantResetWithOpenedWindows)).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._player.Attributes is null || this._player.SelectedCharacter is null)
|
||||
{
|
||||
await this.ShowMessageAsync(nameof(PlayerMessage.NotEnteredTheGame)).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var configuration = resetFeature.Configuration;
|
||||
if (configuration is null)
|
||||
{
|
||||
await this.ShowMessageAsync(nameof(PlayerMessage.ResetNotConfigured)).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var resetProgression = ResetProgressionCalculator.Calculate(this.GetResetCount(), (int)this._player.Attributes[Stats.PointsPerReset], configuration);
|
||||
|
||||
if (this._player.Level < configuration.RequiredLevel)
|
||||
{
|
||||
await this.ShowMessageAsync(nameof(PlayerMessage.RequiredLevelForReset), configuration.RequiredLevel).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (configuration.ResetLimit > 0 && resetProgression.NextResetCount > configuration.ResetLimit)
|
||||
{
|
||||
await this.ShowMessageAsync(nameof(PlayerMessage.MaximumResetsReached), configuration.ResetLimit).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!await this.TryConsumeResetCostsAsync(configuration, resetProgression).ConfigureAwait(false))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this._player.Attributes[Stats.Resets] = resetProgression.NextResetCount;
|
||||
this._player.Attributes[Stats.Level] = configuration.LevelAfterReset;
|
||||
this._player.SelectedCharacter.Experience = 0;
|
||||
this.UpdateStats(configuration, resetProgression);
|
||||
if (configuration.MoveHome)
|
||||
{
|
||||
await this.MoveHomeAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (configuration.LogOut)
|
||||
{
|
||||
await this._logoutAction.LogoutAsync(this._player, LogoutType.BackToCharacterSelection).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await this.UpdateClientStatsAsync(configuration).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask ShowMessageAsync(string messageKey, params object?[] args)
|
||||
{
|
||||
var message = this._player.GetLocalizedMessage(messageKey, args);
|
||||
|
||||
if (this._npc is null)
|
||||
{
|
||||
await this._player.ShowBlueMessageAsync(message).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await this._player.InvokeViewPlugInAsync<IShowMessageOfObjectPlugIn>(p => p.ShowMessageOfObjectAsync(message, this._npc)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private int GetResetCount()
|
||||
{
|
||||
return (int)this._player.Attributes![Stats.Resets];
|
||||
}
|
||||
|
||||
private async ValueTask<bool> TryConsumeResetCostsAsync(ResetConfiguration configuration, ResetProgression resetProgression)
|
||||
{
|
||||
var requiredItems = await this.GetRequiredItemsToConsumeAsync(configuration, resetProgression.RequiredItemAmount).ConfigureAwait(false);
|
||||
if (requiredItems is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this._player.Money < resetProgression.RequiredZen)
|
||||
{
|
||||
await this.ShowMessageAsync(nameof(PlayerMessage.NotEnoughMoneyForReset), resetProgression.RequiredZen).ConfigureAwait(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (resetProgression.RequiredZen > 0 && !this._player.TryRemoveMoney(resetProgression.RequiredZen))
|
||||
{
|
||||
await this.ShowMessageAsync(nameof(PlayerMessage.NotEnoughMoneyForReset), resetProgression.RequiredZen).ConfigureAwait(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var item in requiredItems)
|
||||
{
|
||||
await this._player.DestroyInventoryItemAsync(item).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async ValueTask<IList<Item>?> GetRequiredItemsToConsumeAsync(ResetConfiguration configuration, int requiredItemAmount)
|
||||
{
|
||||
if (requiredItemAmount <= 0 || configuration.RequiredResetItem is null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
if (this._player.Inventory is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var requiredDefinition = configuration.RequiredResetItem;
|
||||
var requiredItems = this._player.Inventory.Items
|
||||
.Where(item => item.Definition is { } definition
|
||||
&& definition.Group == requiredDefinition.Group
|
||||
&& definition.Number == requiredDefinition.Number)
|
||||
.Take(requiredItemAmount)
|
||||
.ToList();
|
||||
if (requiredItems.Count < requiredItemAmount)
|
||||
{
|
||||
await this.ShowMessageAsync(
|
||||
nameof(PlayerMessage.NotEnoughItemsForReset),
|
||||
requiredItemAmount,
|
||||
configuration.RequiredResetItem.Name).ConfigureAwait(false);
|
||||
return null;
|
||||
}
|
||||
|
||||
return requiredItems;
|
||||
}
|
||||
|
||||
private void UpdateStats(ResetConfiguration configuration, ResetProgression resetProgression)
|
||||
{
|
||||
if (configuration.ResetStats)
|
||||
{
|
||||
this._player.SelectedCharacter!.CharacterClass!.StatAttributes
|
||||
.Where(s => s.IncreasableByPlayer)
|
||||
.ForEach(s => this._player.Attributes![s.Attribute] = s.BaseValue);
|
||||
}
|
||||
|
||||
if (configuration.ReplacePointsPerReset)
|
||||
{
|
||||
this._player.SelectedCharacter!.LevelUpPoints = resetProgression.TotalPointsAfterReset;
|
||||
}
|
||||
else
|
||||
{
|
||||
this._player.SelectedCharacter!.LevelUpPoints += resetProgression.PointsForReset;
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask MoveHomeAsync()
|
||||
{
|
||||
var homeMapDef = this._player.SelectedCharacter!.CharacterClass!.HomeMap;
|
||||
if (homeMapDef is { }
|
||||
&& await this._player.GameContext.GetMapAsync((ushort)homeMapDef.Number).ConfigureAwait(false) is { SafeZoneSpawnGate: { } spawnGate })
|
||||
{
|
||||
this._player.SelectedCharacter.PositionX = (byte)Rand.NextInt(spawnGate.X1, spawnGate.X2);
|
||||
this._player.SelectedCharacter.PositionY = (byte)Rand.NextInt(spawnGate.Y1, spawnGate.Y2);
|
||||
this._player.SelectedCharacter.CurrentMap = spawnGate.Map;
|
||||
this._player.Rotation = spawnGate.Direction;
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask UpdateClientStatsAsync(ResetConfiguration configuration)
|
||||
{
|
||||
if (configuration.ResetStats)
|
||||
{
|
||||
await this._player.InvokeViewPlugInAsync<IUpdateCharacterBaseStatsPlugIn>(p => p.UpdateCharacterBaseStatsAsync()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await this._player.InvokeViewPlugInAsync<IUpdateLevelPlugIn>(p => p.UpdateLevelAsync()).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
37
src/GameLogic/Resets/ResetCharacterNpcPlugin.cs
Normal file
37
src/GameLogic/Resets/ResetCharacterNpcPlugin.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
// <copyright file="ResetCharacterNpcPlugin.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameLogic.Resets;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.GameLogic.NPC;
|
||||
using MUnique.OpenMU.GameLogic.PlugIns;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// Action to reset a character.
|
||||
/// </summary>
|
||||
[Guid("08953BE6-DABF-49CC-A500-FDB9DC2C4D80")]
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.ResetCharacterNpcPlugin_Name), Description = nameof(PlugInResources.ResetCharacterNpcPlugin_Description), ResourceType = typeof(PlugInResources))]
|
||||
public class ResetCharacterNpcPlugin : IPlayerTalkToNpcPlugIn
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the reset NPC number of 'Leo the Helper'.
|
||||
/// </summary>
|
||||
public static short ResetNpcNumber => 371;
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask PlayerTalksToNpcAsync(Player player, NonPlayerCharacter npc, NpcTalkEventArgs eventArgs)
|
||||
{
|
||||
if (npc.Definition.Number != ResetNpcNumber)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
eventArgs.HasBeenHandled = true;
|
||||
var resetAction = new ResetCharacterAction(player, npc);
|
||||
await resetAction.ResetCharacterAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
35
src/GameLogic/Resets/ResetChatCommandPlugIn.cs
Normal file
35
src/GameLogic/Resets/ResetChatCommandPlugIn.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
// <copyright file="ResetChatCommandPlugIn.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameLogic.Resets;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// A chat command plugin which handles reset command.
|
||||
/// </summary>
|
||||
/// <seealso cref="MUnique.OpenMU.GameLogic.PlugIns.ChatCommands.IChatCommandPlugIn" />
|
||||
[Guid("90B35404-AADE-4F22-B5D2-4CD59B8BB4C8")]
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.ResetChatCommandPlugIn_Name), Description = nameof(PlugInResources.ResetChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
|
||||
[ChatCommandHelp(Command, "Does a character reset, if available.", null)]
|
||||
public class ResetChatCommandPlugIn : IChatCommandPlugIn
|
||||
{
|
||||
private const string Command = "/reset";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => Command;
|
||||
|
||||
/// <inheritdoc />
|
||||
public CharacterStatus MinCharacterStatusRequirement => CharacterStatus.Normal;
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask HandleCommandAsync(Player player, string command)
|
||||
{
|
||||
var resetAction = new ResetCharacterAction(player);
|
||||
await resetAction.ResetCharacterAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
143
src/GameLogic/Resets/ResetConfiguration.cs
Normal file
143
src/GameLogic/Resets/ResetConfiguration.cs
Normal file
@@ -0,0 +1,143 @@
|
||||
// <copyright file="ResetConfiguration.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameLogic.Resets;
|
||||
|
||||
using MUnique.OpenMU.DataModel.Composition;
|
||||
using MUnique.OpenMU.DataModel.Configuration.Items;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration of the Reset System.
|
||||
/// </summary>
|
||||
public class ResetConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the reset limit, which is the maximum amount of possible resets.
|
||||
/// </summary>
|
||||
[Display(ResourceType = typeof(PlugInResources), Name = nameof(PlugInResources.ResetConfiguration_ResetLimit_Name))]
|
||||
public int? ResetLimit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the required level for a reset.
|
||||
/// </summary>
|
||||
[Display(ResourceType = typeof(PlugInResources), Name = nameof(PlugInResources.ResetConfiguration_RequiredLevel_Name))]
|
||||
public int RequiredLevel { get; set; } = 400;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the character level after a reset.
|
||||
/// </summary>
|
||||
[Display(ResourceType = typeof(PlugInResources), Name = nameof(PlugInResources.ResetConfiguration_LevelAfterReset_Name))]
|
||||
public int LevelAfterReset { get; set; } = 10;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the required money for a reset.
|
||||
/// </summary>
|
||||
[Display(ResourceType = typeof(PlugInResources), Name = nameof(PlugInResources.ResetConfiguration_RequiredMoney_Name))]
|
||||
public int RequiredMoney { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the required money should
|
||||
/// be multiplied with the current reset count.
|
||||
/// </summary>
|
||||
[Display(ResourceType = typeof(PlugInResources), Name = nameof(PlugInResources.ResetConfiguration_MultiplyRequiredMoneyByResetCount_Name))]
|
||||
public bool MultiplyRequiredMoneyByResetCount { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the required item for a reset.
|
||||
/// </summary>
|
||||
[Display(Name = "Required reset item")]
|
||||
public ItemDefinition? RequiredResetItem { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the item costs per reset range.
|
||||
/// </summary>
|
||||
[Display(Name = "Item cost tiers")]
|
||||
[MemberOfAggregate]
|
||||
[ScaffoldColumn(true)]
|
||||
public ICollection<ResetItemCostTier> ItemCostTiers { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether a reset sets the stat points back to the initial values.
|
||||
/// </summary>
|
||||
[Display(ResourceType = typeof(PlugInResources), Name = nameof(PlugInResources.ResetConfiguration_ResetStats_Name))]
|
||||
public bool ResetStats { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the legacy amount of points which will be set at the <see cref="Character.LevelUpPoints"/> when doing a reset.
|
||||
/// Use <see cref="PointsTiers"/> instead.
|
||||
/// </summary>
|
||||
[Display(ResourceType = typeof(PlugInResources), Name = nameof(PlugInResources.ResetConfiguration_PointsPerReset_Name))]
|
||||
[ScaffoldColumn(false)]
|
||||
public int PointsPerReset { get; set; } = 1500;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the legacy <see cref="PointsPerReset"/> should be multiplied with the current reset count.
|
||||
/// Use <see cref="PointsTiers"/> instead.
|
||||
/// </summary>
|
||||
[Display(ResourceType = typeof(PlugInResources), Name = nameof(PlugInResources.ResetConfiguration_MultiplyPointsByResetCount_Name))]
|
||||
[ScaffoldColumn(false)]
|
||||
public bool MultiplyPointsByResetCount { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether a reset will replace (true) or add (false) the <see cref="Character.LevelUpPoints"/>.
|
||||
/// </summary>
|
||||
[Display(ResourceType = typeof(PlugInResources), Name = nameof(PlugInResources.ResetConfiguration_ReplacePointsPerReset_Name))]
|
||||
public bool ReplacePointsPerReset { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the points granted per reset range.
|
||||
/// </summary>
|
||||
[Display(Name = "Point tiers")]
|
||||
[MemberOfAggregate]
|
||||
[ScaffoldColumn(true)]
|
||||
public ICollection<ResetPointTier> PointsTiers { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether a reset moves the player home.
|
||||
/// </summary>
|
||||
[Display(ResourceType = typeof(PlugInResources), Name = nameof(PlugInResources.ResetConfiguration_MoveHome_Name))]
|
||||
public bool MoveHome { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether a reset logs the player out back to character selection.
|
||||
/// </summary>
|
||||
[Display(ResourceType = typeof(PlugInResources), Name = nameof(PlugInResources.ResetConfiguration_LogOut_Name))]
|
||||
public bool LogOut { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Tier definition for the points which are granted when resetting.
|
||||
/// </summary>
|
||||
public class ResetPointTier
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the minimum reset count at which this tier applies.
|
||||
/// </summary>
|
||||
[Display(Name = "Minimum reset count")]
|
||||
public int MinimumResetCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the points granted for this tier.
|
||||
/// </summary>
|
||||
[Display(Name = "Points granted")]
|
||||
public int PointsGranted { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tier definition for required reset item amounts.
|
||||
/// </summary>
|
||||
public class ResetItemCostTier
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the minimum reset count at which this tier applies.
|
||||
/// </summary>
|
||||
[Display(Name = "Minimum reset count")]
|
||||
public int MinimumResetCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the required item amount for this tier.
|
||||
/// </summary>
|
||||
[Display(Name = "Required item amount")]
|
||||
public int RequiredItemAmount { get; set; }
|
||||
}
|
||||
}
|
||||
23
src/GameLogic/Resets/ResetFeaturePlugIn.cs
Normal file
23
src/GameLogic/Resets/ResetFeaturePlugIn.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
// <copyright file="ResetFeaturePlugIn.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameLogic.Resets;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// Feature plugin which provides the configuration for the reset feature.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.ResetFeaturePlugIn_Name), Description = nameof(PlugInResources.ResetFeaturePlugIn_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("6A9D585D-79D7-4674-B6EA-7E87392FA501")]
|
||||
public class ResetFeaturePlugIn : IFeaturePlugIn, ISupportCustomConfiguration<ResetConfiguration>, ISupportDefaultCustomConfiguration, IDisabledByDefault
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public ResetConfiguration? Configuration { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public object CreateDefaultConfig() => new ResetConfiguration();
|
||||
}
|
||||
73
src/GameLogic/Resets/ResetInfoChatCommandPlugIn.cs
Normal file
73
src/GameLogic/Resets/ResetInfoChatCommandPlugIn.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
// <copyright file="ResetInfoChatCommandPlugIn.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameLogic.Resets;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.GameLogic.Attributes;
|
||||
using MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// A chat command plugin which shows reset costs and rewards.
|
||||
/// </summary>
|
||||
[Guid("79F2C2C2-2E4C-4F4B-8A74-4227D1209D27")]
|
||||
[PlugIn]
|
||||
[Display(Name = "Reset Info Command", Description = "Shows required costs and granted points for the next reset.")]
|
||||
[ChatCommandHelp(Command, "Shows required costs and gained points for the next reset.", null)]
|
||||
public class ResetInfoChatCommandPlugIn : IChatCommandPlugIn
|
||||
{
|
||||
private const string Command = "/resetinfo";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => Command;
|
||||
|
||||
/// <inheritdoc />
|
||||
public CharacterStatus MinCharacterStatusRequirement => CharacterStatus.Normal;
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask HandleCommandAsync(Player player, string command)
|
||||
{
|
||||
var configuration = player.GameContext.FeaturePlugIns.GetPlugIn<ResetFeaturePlugIn>()?.Configuration;
|
||||
if (configuration is null)
|
||||
{
|
||||
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.ResetSystemInactive)).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (player.Attributes is null || player.SelectedCharacter is null)
|
||||
{
|
||||
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.NotEnteredTheGame)).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var progression = ResetProgressionCalculator.Calculate(
|
||||
(int)player.Attributes[Stats.Resets],
|
||||
(int)player.Attributes[Stats.PointsPerReset],
|
||||
configuration);
|
||||
if (progression.RequiredItemAmount > 0 && configuration.RequiredResetItem is { Name: { } itemName })
|
||||
{
|
||||
await player.ShowLocalizedBlueMessageAsync(
|
||||
nameof(PlayerMessage.NextResetInfoCompactWithItem),
|
||||
progression.NextResetCount,
|
||||
configuration.RequiredLevel,
|
||||
progression.RequiredZen,
|
||||
itemName,
|
||||
progression.RequiredItemAmount,
|
||||
progression.PointsForReset,
|
||||
progression.TotalPointsAfterReset)
|
||||
.ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await player.ShowLocalizedBlueMessageAsync(
|
||||
nameof(PlayerMessage.NextResetInfoCompactNoItem),
|
||||
progression.NextResetCount,
|
||||
configuration.RequiredLevel,
|
||||
progression.RequiredZen,
|
||||
progression.PointsForReset,
|
||||
progression.TotalPointsAfterReset)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
20
src/GameLogic/Resets/ResetProgression.cs
Normal file
20
src/GameLogic/Resets/ResetProgression.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
// <copyright file="ResetProgression.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameLogic.Resets;
|
||||
|
||||
/// <summary>
|
||||
/// A value object that contains costs and rewards for the next reset.
|
||||
/// </summary>
|
||||
/// <param name="NextResetCount">The resulting reset count after a successful reset.</param>
|
||||
/// <param name="RequiredZen">The required zen for the reset.</param>
|
||||
/// <param name="RequiredItemAmount">The required number of configured reset items.</param>
|
||||
/// <param name="PointsForReset">The number of points granted for the reset.</param>
|
||||
/// <param name="TotalPointsAfterReset">The total number of points after the reset when replacement mode is active.</param>
|
||||
public readonly record struct ResetProgression(
|
||||
int NextResetCount,
|
||||
int RequiredZen,
|
||||
int RequiredItemAmount,
|
||||
int PointsForReset,
|
||||
int TotalPointsAfterReset);
|
||||
93
src/GameLogic/Resets/ResetProgressionCalculator.cs
Normal file
93
src/GameLogic/Resets/ResetProgressionCalculator.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
// <copyright file="ResetProgressionCalculator.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameLogic.Resets;
|
||||
|
||||
/// <summary>
|
||||
/// Calculates costs and rewards for the next character reset.
|
||||
/// </summary>
|
||||
public static class ResetProgressionCalculator
|
||||
{
|
||||
/// <summary>
|
||||
/// Calculates the reset progression for the next reset.
|
||||
/// </summary>
|
||||
/// <param name="currentResetCount">The current reset count.</param>
|
||||
/// <param name="pointsPerResetOverride">The player-specific points per reset override (0 means not configured).</param>
|
||||
/// <param name="configuration">The reset configuration.</param>
|
||||
/// <returns>The calculated progression.</returns>
|
||||
public static ResetProgression Calculate(int currentResetCount, int pointsPerResetOverride, ResetConfiguration configuration)
|
||||
{
|
||||
var nextResetCount = currentResetCount + 1;
|
||||
var requiredZen = Math.Max(0, configuration.RequiredMoney);
|
||||
if (configuration.MultiplyRequiredMoneyByResetCount)
|
||||
{
|
||||
requiredZen *= nextResetCount;
|
||||
}
|
||||
|
||||
var pointsForReset = GetPointsForReset(configuration, pointsPerResetOverride, nextResetCount);
|
||||
var totalPointsAfterReset = GetTotalPointsAfterReset(configuration, pointsPerResetOverride, nextResetCount, pointsForReset);
|
||||
var requiredItemAmount = GetRequiredItemAmount(configuration, nextResetCount);
|
||||
|
||||
return new ResetProgression(nextResetCount, requiredZen, requiredItemAmount, pointsForReset, totalPointsAfterReset);
|
||||
}
|
||||
|
||||
private static int GetPointsForReset(ResetConfiguration configuration, int pointsPerResetOverride, int nextResetCount)
|
||||
{
|
||||
if (GetMatchingTier(configuration.PointsTiers, nextResetCount, tier => tier.MinimumResetCount) is { } tier)
|
||||
{
|
||||
return Math.Max(0, tier.PointsGranted);
|
||||
}
|
||||
|
||||
var pointsPerReset = pointsPerResetOverride == 0 ? configuration.PointsPerReset : pointsPerResetOverride;
|
||||
if (configuration.MultiplyPointsByResetCount)
|
||||
{
|
||||
pointsPerReset *= nextResetCount;
|
||||
}
|
||||
|
||||
return Math.Max(0, pointsPerReset);
|
||||
}
|
||||
|
||||
private static int GetRequiredItemAmount(ResetConfiguration configuration, int nextResetCount)
|
||||
{
|
||||
if (configuration.RequiredResetItem is null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (GetMatchingTier(configuration.ItemCostTiers, nextResetCount, tier => tier.MinimumResetCount) is not { } tier)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Math.Max(0, tier.RequiredItemAmount);
|
||||
}
|
||||
|
||||
private static int GetTotalPointsAfterReset(ResetConfiguration configuration, int pointsPerResetOverride, int nextResetCount, int pointsForReset)
|
||||
{
|
||||
if (configuration.PointsTiers.Count == 0)
|
||||
{
|
||||
return pointsForReset;
|
||||
}
|
||||
|
||||
long total = 0;
|
||||
for (var resetCount = 1; resetCount <= nextResetCount; resetCount++)
|
||||
{
|
||||
total += GetPointsForReset(configuration, pointsPerResetOverride, resetCount);
|
||||
if (total >= int.MaxValue)
|
||||
{
|
||||
return int.MaxValue;
|
||||
}
|
||||
}
|
||||
|
||||
return (int)total;
|
||||
}
|
||||
|
||||
private static TTier? GetMatchingTier<TTier>(IEnumerable<TTier> tiers, int resetCount, Func<TTier, int> getMinimumResetCount)
|
||||
where TTier : class
|
||||
{
|
||||
return tiers
|
||||
.OrderByDescending(getMinimumResetCount)
|
||||
.FirstOrDefault(tier => getMinimumResetCount(tier) <= resetCount);
|
||||
}
|
||||
}
|
||||
178
src/GameLogic/Resets/ResetStatsAction.cs
Normal file
178
src/GameLogic/Resets/ResetStatsAction.cs
Normal file
@@ -0,0 +1,178 @@
|
||||
// <copyright file="ResetStatsAction.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameLogic.Resets;
|
||||
|
||||
using MUnique.OpenMU.GameLogic.NPC;
|
||||
using MUnique.OpenMU.GameLogic.PlayerActions;
|
||||
using MUnique.OpenMU.GameLogic.Views.Character;
|
||||
using MUnique.OpenMU.GameLogic.Views.Login;
|
||||
|
||||
/// <summary>
|
||||
/// Action to reset a character's stats back to base values and refund the invested points.
|
||||
/// </summary>
|
||||
public class ResetStatsAction
|
||||
{
|
||||
private readonly Player _player;
|
||||
private readonly LogoutAction _logoutAction = new();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ResetStatsAction"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">Player to reset stats for.</param>
|
||||
public ResetStatsAction(Player player)
|
||||
{
|
||||
this._player = player;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the character stats to base values and refunds invested points.
|
||||
/// </summary>
|
||||
public async ValueTask ResetStatsAsync()
|
||||
{
|
||||
var statResetFeature = this._player.GameContext.FeaturePlugIns.GetPlugIn<StatResetFeaturePlugIn>();
|
||||
if (statResetFeature is null)
|
||||
{
|
||||
await this.ShowMessageAsync(nameof(PlayerMessage.StatResetNotEnabled)).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._player.PlayerState.CurrentState != PlayerState.EnteredWorld)
|
||||
{
|
||||
await this.ShowMessageAsync(nameof(PlayerMessage.CantResetStatsWithOpenedWindows)).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._player.Attributes is null || this._player.SelectedCharacter is null)
|
||||
{
|
||||
await this.ShowMessageAsync(nameof(PlayerMessage.NotEnteredTheGame)).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var configuration = statResetFeature.Configuration;
|
||||
if (configuration is null)
|
||||
{
|
||||
await this.ShowMessageAsync(nameof(PlayerMessage.StatResetNotConfigured)).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._player.IsAtSafezone())
|
||||
{
|
||||
await this.ShowMessageAsync(nameof(PlayerMessage.CantResetStatsNotInSafezone)).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._player.Level < configuration.RequiredLevel)
|
||||
{
|
||||
await this.ShowMessageAsync(nameof(PlayerMessage.RequiredLevelForStatReset), configuration.RequiredLevel).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!await this.TryConsumeCostsAsync(configuration).ConfigureAwait(false))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.ResetAttributes();
|
||||
|
||||
if (configuration.MoveHome)
|
||||
{
|
||||
await this.MoveHomeAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (configuration.LogOut)
|
||||
{
|
||||
await this._logoutAction.LogoutAsync(this._player, LogoutType.BackToCharacterSelection).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await this.UpdateClientStatsAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetAttributes()
|
||||
{
|
||||
var selectedCharacter = this._player.SelectedCharacter!;
|
||||
var investedPoints = 0;
|
||||
|
||||
foreach (var statDef in selectedCharacter.CharacterClass!.StatAttributes.Where(s => s.IncreasableByPlayer))
|
||||
{
|
||||
if (statDef.Attribute is not { } attribute)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var currentValue = (int)this._player.Attributes![attribute];
|
||||
var baseValue = (int)statDef.BaseValue;
|
||||
investedPoints += Math.Max(0, currentValue - baseValue);
|
||||
this._player.Attributes[attribute] = baseValue;
|
||||
}
|
||||
|
||||
selectedCharacter.LevelUpPoints += investedPoints;
|
||||
}
|
||||
|
||||
private async ValueTask<bool> TryConsumeCostsAsync(StatResetConfiguration configuration)
|
||||
{
|
||||
if (this._player.Money < configuration.RequiredMoney)
|
||||
{
|
||||
await this.ShowMessageAsync(nameof(PlayerMessage.NotEnoughMoneyForStatReset), configuration.RequiredMoney).ConfigureAwait(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
Item? requiredItem = null;
|
||||
if (configuration.RequiredResetItem is { } requiredDefinition)
|
||||
{
|
||||
if (this._player.Inventory is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
requiredItem = this._player.Inventory.Items
|
||||
.FirstOrDefault(item => item.Definition is { } definition
|
||||
&& definition.Group == requiredDefinition.Group
|
||||
&& definition.Number == requiredDefinition.Number);
|
||||
|
||||
if (requiredItem is null)
|
||||
{
|
||||
await this.ShowMessageAsync(nameof(PlayerMessage.NotEnoughItemsForStatReset), 1, requiredDefinition.Name).ConfigureAwait(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (configuration.RequiredMoney > 0 && !this._player.TryRemoveMoney(configuration.RequiredMoney))
|
||||
{
|
||||
await this.ShowMessageAsync(nameof(PlayerMessage.NotEnoughMoneyForStatReset), configuration.RequiredMoney).ConfigureAwait(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (requiredItem is not null)
|
||||
{
|
||||
await this._player.DestroyInventoryItemAsync(requiredItem).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async ValueTask MoveHomeAsync()
|
||||
{
|
||||
var homeMapDef = this._player.SelectedCharacter!.CharacterClass!.HomeMap;
|
||||
if (homeMapDef is { }
|
||||
&& await this._player.GameContext.GetMapAsync((ushort)homeMapDef.Number).ConfigureAwait(false) is { SafeZoneSpawnGate: { } spawnGate })
|
||||
{
|
||||
await this._player.WarpToAsync(spawnGate).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask UpdateClientStatsAsync()
|
||||
{
|
||||
await this._player.InvokeViewPlugInAsync<IUpdateCharacterBaseStatsPlugIn>(p => p.UpdateCharacterBaseStatsAsync()).ConfigureAwait(false);
|
||||
await this._player.InvokeViewPlugInAsync<IUpdateCharacterStatsPlugIn>(p => p.UpdateCharacterStatsAsync()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async ValueTask ShowMessageAsync(string messageKey, params object?[] args)
|
||||
{
|
||||
var message = this._player.GetLocalizedMessage(messageKey, args);
|
||||
await this._player.ShowBlueMessageAsync(message).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
41
src/GameLogic/Resets/ResetStatsChatCommandPlugIn.cs
Normal file
41
src/GameLogic/Resets/ResetStatsChatCommandPlugIn.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
// <copyright file="ResetStatsChatCommandPlugIn.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameLogic.Resets;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// A chat command plugin which handles the stat reset command.
|
||||
/// </summary>
|
||||
[Guid("A1B2C3D4-E5F6-7890-ABCD-EF1234567891")]
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.ResetStatsChatCommandPlugIn_Name), Description = nameof(PlugInResources.ResetStatsChatCommandPlugIn_Description), ResourceType = typeof(PlugInResources))]
|
||||
[ChatCommandHelp(Command, "Resets your character stats to base values and refunds all invested points.", null)]
|
||||
public class ResetStatsChatCommandPlugIn : IChatCommandPlugIn
|
||||
{
|
||||
private const string Command = "/resetstats";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => Command;
|
||||
|
||||
/// <inheritdoc />
|
||||
public CharacterStatus MinCharacterStatusRequirement => CharacterStatus.Normal;
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask HandleCommandAsync(Player player, string command)
|
||||
{
|
||||
var statResetFeature = player.GameContext.FeaturePlugIns.GetPlugIn<StatResetFeaturePlugIn>();
|
||||
if (statResetFeature?.Configuration is { } configuration && !configuration.ChatCommandEnabled)
|
||||
{
|
||||
await player.ShowLocalizedBlueMessageAsync(PlayerMessage.StatResetChatCommandDisabled).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var resetAction = new ResetStatsAction(player);
|
||||
await resetAction.ResetStatsAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
50
src/GameLogic/Resets/StatResetConfiguration.cs
Normal file
50
src/GameLogic/Resets/StatResetConfiguration.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
// <copyright file="StatResetConfiguration.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameLogic.Resets;
|
||||
|
||||
using MUnique.OpenMU.DataModel.Composition;
|
||||
using MUnique.OpenMU.DataModel.Configuration.Items;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration of the Stat Reset System.
|
||||
/// </summary>
|
||||
public class StatResetConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the required level for a stat reset.
|
||||
/// </summary>
|
||||
[Display(ResourceType = typeof(PlugInResources), Name = nameof(PlugInResources.StatResetConfiguration_RequiredLevel_Name))]
|
||||
public int RequiredLevel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the required money for a stat reset.
|
||||
/// </summary>
|
||||
[Display(ResourceType = typeof(PlugInResources), Name = nameof(PlugInResources.StatResetConfiguration_RequiredMoney_Name))]
|
||||
public int RequiredMoney { get; set; } = 1000000;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the required item for a stat reset.
|
||||
/// </summary>
|
||||
[Display(ResourceType = typeof(PlugInResources), Name = nameof(PlugInResources.StatResetConfiguration_RequiredResetItem_Name))]
|
||||
public ItemDefinition? RequiredResetItem { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the chat command is enabled.
|
||||
/// </summary>
|
||||
[Display(ResourceType = typeof(PlugInResources), Name = nameof(PlugInResources.StatResetConfiguration_ChatCommandEnabled_Name))]
|
||||
public bool ChatCommandEnabled { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether a stat reset moves the player home (safe zone).
|
||||
/// </summary>
|
||||
[Display(ResourceType = typeof(PlugInResources), Name = nameof(PlugInResources.StatResetConfiguration_MoveHome_Name))]
|
||||
public bool MoveHome { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether a stat reset logs the player out back to character selection.
|
||||
/// </summary>
|
||||
[Display(ResourceType = typeof(PlugInResources), Name = nameof(PlugInResources.StatResetConfiguration_LogOut_Name))]
|
||||
public bool LogOut { get; set; } = true;
|
||||
}
|
||||
23
src/GameLogic/Resets/StatResetFeaturePlugIn.cs
Normal file
23
src/GameLogic/Resets/StatResetFeaturePlugIn.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
// <copyright file="StatResetFeaturePlugIn.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameLogic.Resets;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// Feature plugin which provides the configuration for the stat reset feature.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.StatResetFeaturePlugIn_Name), Description = nameof(PlugInResources.StatResetFeaturePlugIn_Description), ResourceType = typeof(PlugInResources))]
|
||||
[Guid("0F1E2D3C-4B5A-6978-8C7D-6E5F4A3B2C1D")]
|
||||
public class StatResetFeaturePlugIn : IFeaturePlugIn, ISupportCustomConfiguration<StatResetConfiguration>, ISupportDefaultCustomConfiguration, IDisabledByDefault
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public StatResetConfiguration? Configuration { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public object CreateDefaultConfig() => new StatResetConfiguration();
|
||||
}
|
||||
Reference in New Issue
Block a user