baseline: OpenMU upstream b5a0961 (fresh source)

This commit is contained in:
Acentech Dev
2026-07-14 19:00:35 +03:00
parent 450402e47d
commit 36fc125d5c
11968 changed files with 748705 additions and 0 deletions

View File

@@ -0,0 +1,36 @@
// -----------------------------------------------------------------------
// <copyright file="AlcoholConsumeHandlerPlugIn.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 System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.Views;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The alcohol consume handler.
/// </summary>
[Guid("7FC2FE02-9215-4AD3-958F-D2279CD84266")]
[PlugIn]
[Display(Name = nameof(PlugInResources.AlcoholConsumeHandlerPlugIn_Name), Description = nameof(PlugInResources.AlcoholConsumeHandlerPlugIn_Description), ResourceType = typeof(PlugInResources))]
public class AlcoholConsumeHandlerPlugIn : ApplyMagicEffectConsumeHandlerPlugIn
{
/// <inheritdoc />
public override ItemIdentifier Key => ItemConstants.Alcohol;
/// <inheritdoc/>
public override async ValueTask<bool> ConsumeItemAsync(Player player, Item item, Item? targetItem, FruitUsage fruitUsage)
{
if (await base.ConsumeItemAsync(player, item, targetItem, fruitUsage).ConfigureAwait(false))
{
var effectDefinition = item.Definition?.ConsumeEffect;
await player.InvokeViewPlugInAsync<IConsumeSpecialItemPlugIn>(p => p.ConsumeSpecialItemAsync(item, (ushort)(effectDefinition?.Duration?.ConstantValue.Value ?? 0))).ConfigureAwait(false);
return true;
}
return false;
}
}

View File

@@ -0,0 +1,40 @@
// -----------------------------------------------------------------------
// <copyright file="AntidoteConsumeHandlerPlugIn.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 System.Runtime.InteropServices;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Consume handler for the antidote potion. It removes the poison effect from the player.
/// </summary>
[Guid("F838B348-DAA5-475B-BCED-41A076D08948")]
[PlugIn]
[Display(Name = nameof(PlugInResources.AntidoteConsumeHandlerPlugIn_Name), Description = nameof(PlugInResources.AntidoteConsumeHandlerPlugIn_Description), ResourceType = typeof(PlugInResources))]
public class AntidoteConsumeHandlerPlugIn : BaseConsumeHandlerPlugIn
{
private const short PoisonEffectNumber = 0x37;
/// <inheritdoc />
public override ItemIdentifier Key => ItemConstants.Antidote;
/// <inheritdoc />
public override async ValueTask<bool> ConsumeItemAsync(Player player, Item item, Item? targetItem, FruitUsage fruitUsage)
{
if (await base.ConsumeItemAsync(player, item, targetItem, fruitUsage).ConfigureAwait(false))
{
if (player.MagicEffectList.ActiveEffects.TryGetValue(PoisonEffectNumber, out var effect))
{
effect.Dispose();
}
return true;
}
return false;
}
}

View File

@@ -0,0 +1,25 @@
// -----------------------------------------------------------------------
// <copyright file="AppleConsumeHandlerPlugIn.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 System.Runtime.InteropServices;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Consume handler for apples.
/// </summary>
[Guid("58518298-42BC-48D1-AB07-17A9D83A2103")]
[PlugIn]
[Display(Name = nameof(PlugInResources.AppleConsumeHandlerPlugIn_Name), Description = nameof(PlugInResources.AppleConsumeHandlerPlugIn_Description), ResourceType = typeof(PlugInResources))]
public class AppleConsumeHandlerPlugIn : HealthPotionConsumeHandlerPlugIn
{
/// <inheritdoc />
public override ItemIdentifier Key => ItemConstants.Apple;
/// <inheritdoc/>
protected override int Multiplier => 0;
}

View File

@@ -0,0 +1,73 @@
// <copyright file="ApplyMagicEffectConsumeHandlerPlugIn.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;
using MUnique.OpenMU.GameLogic.Attributes;
/// <summary>
/// <see cref="IItemConsumeHandlerPlugIn"/> for <see cref="Item"/>s which have a defined <see cref="ItemDefinition.ConsumeEffect"/>.
/// </summary>
public class ApplyMagicEffectConsumeHandlerPlugIn : BaseConsumeHandlerPlugIn
{
/// <inheritdoc />
public override ItemIdentifier Key => new(0xFF, 0xFF);
/// <inheritdoc />
public override async ValueTask<bool> ConsumeItemAsync(Player player, Item item, Item? targetItem, FruitUsage fruitUsage)
{
if (item.Definition?.ConsumeEffect is not { } effectDefinition)
{
return false;
}
return await this.ConsumeItemAsyncCore(player, item, targetItem, fruitUsage, effectDefinition).ConfigureAwait(false);
}
/// <summary>
/// Consumes the item at the specified slot with the specified effect and reduces its durability by one.
/// If the durability has reached 0, the item is getting destroyed.
/// If a target slot is specified, the consumption targets the item on this slot (e.g. upgrade of an item by a jewel).
/// </summary>
/// <param name="player">The player which is consuming.</param>
/// <param name="item">The item which gets consumed.</param>
/// <param name="targetItem">The item which is the target of the consumption (e.g. upgrade target of a jewel).</param>
/// <param name="fruitUsage">In case the item is a fruit, this parameter defines how the fruit should be used.</param>
/// <param name="effectDefinition">The effect definition.</param>
/// <returns>
/// The success of the consumption.
/// </returns>
protected async ValueTask<bool> ConsumeItemAsyncCore(Player player, Item item, Item? targetItem, FruitUsage fruitUsage, MagicEffectDefinition effectDefinition)
{
if (!effectDefinition.PowerUpDefinitions.Any()
|| effectDefinition.Duration?.ConstantValue.Value is not { } durationInSeconds)
{
return false;
}
if (await player.MagicEffectList.TryGetActiveEffectOfSubTypeAsync(effectDefinition.SubType).ConfigureAwait(false) is { } existingEffect)
{
await existingEffect.DisposeAsync().ConfigureAwait(false);
}
var boosts = effectDefinition.PowerUpDefinitions
.Where(def => def.Boost is not null && def.TargetAttribute is not null)
.Select(def => new MagicEffect.ElementWithTarget(player.Attributes!.CreateElement(def), def.TargetAttribute!))
.ToArray();
if (boosts.Length == 0)
{
return false;
}
if (!await base.ConsumeItemAsync(player, item, targetItem, fruitUsage).ConfigureAwait(false))
{
return false;
}
var effect = new MagicEffect(TimeSpan.FromSeconds(durationInSeconds), effectDefinition, boosts!);
await player.MagicEffectList.AddEffectAsync(effect).ConfigureAwait(false);
return true;
}
}

View File

@@ -0,0 +1,59 @@
// -----------------------------------------------------------------------
// <copyright file="BaseConsumeHandlerPlugIn.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;
/// <summary>
/// Base class of an item consumption handler.
/// </summary>
public abstract class BaseConsumeHandlerPlugIn : IItemConsumeHandlerPlugIn
{
/// <inheritdoc />
public abstract ItemIdentifier Key { get; }
/// <inheritdoc/>
public virtual async ValueTask<bool> ConsumeItemAsync(Player player, Item item, Item? targetItem, FruitUsage fruitUsage)
{
if (!this.CheckPreconditions(player, item))
{
return false;
}
await this.ConsumeSourceItemAsync(player, item).ConfigureAwait(false);
return true;
}
/// <summary>
/// Consumes the source item.
/// </summary>
/// <param name="player">The player.</param>
/// <param name="item">The item.</param>
protected async ValueTask ConsumeSourceItemAsync(Player player, Item item)
{
if (item.Durability > 0)
{
item.Durability -= 1;
}
}
/// <summary>
/// Checks the preconditions.
/// </summary>
/// <param name="player">The player.</param>
/// <param name="item">The item.</param>
/// <returns><c>True</c>, if preconditions are met.</returns>
protected virtual bool CheckPreconditions(Player player, Item item)
{
if (player.PlayerState.CurrentState != PlayerState.EnteredWorld
|| item.Durability == 0)
{
return false;
}
return true;
}
}

View File

@@ -0,0 +1,49 @@
// -----------------------------------------------------------------------
// <copyright file="BlessJewelConsumeHandlerPlugIn.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 System.Runtime.InteropServices;
using MUnique.OpenMU.Persistence;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Consume handler for upgrading items up to level 6 using the Jewel of Bless.
/// </summary>
[Guid("E95A0292-B3B4-4E8C-AC5A-7F3DB4F01A37")]
[PlugIn]
[Display(Name = nameof(PlugInResources.BlessJewelConsumeHandlerPlugIn_Name), Description = nameof(PlugInResources.BlessJewelConsumeHandlerPlugIn_Description), ResourceType = typeof(PlugInResources))]
public class BlessJewelConsumeHandlerPlugIn : UpgradeItemLevelJewelConsumeHandlerPlugIn<BlessJewelConsumeHandlerPlugInConfiguration>
{
/// <inheritdoc />
public override ItemIdentifier Key => ItemConstants.JewelOfBless;
/// <inheritdoc />
public override object CreateDefaultConfig()
{
return new BlessJewelConsumeHandlerPlugInConfiguration
{
MaximumLevel = 5,
MinimumLevel = 0,
SuccessRatePercentage = 100,
SuccessRateBonusWithLuckPercentage = 0,
ResetToLevel0WhenFailMinLevel = 0,
};
}
/// <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;
}
return base.ModifyItem(item, persistenceContext);
}
}

View File

@@ -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 : UpgradeItemLevelConfiguration
{
/// <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>();
}

View File

@@ -0,0 +1,45 @@
// <copyright file="ComplexPotionConsumeHandlerPlugIn.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;
/// <summary>
/// Abstract consume handler for complex potions which combines a <see cref="HealthPotionConsumeHandlerPlugIn"/> and a <see cref="ShieldPotionConsumeHandlerPlugIn"/>.
/// </summary>
public abstract class ComplexPotionConsumeHandlerPlugIn : BaseConsumeHandlerPlugIn
{
private readonly HealthPotionConsumeHandlerPlugIn _healthPotionConsumeHandlerPlugIn;
private readonly ShieldPotionConsumeHandlerPlugIn _shieldPotionConsumeHandlerPlugIn;
/// <summary>
/// Initializes a new instance of the <see cref="ComplexPotionConsumeHandlerPlugIn"/> class.
/// </summary>
/// <param name="healthPotionConsumeHandlerPlugIn">The health potion consume handler.</param>
/// <param name="shieldPotionConsumeHandlerPlugIn">The shield potion consume handler.</param>
protected ComplexPotionConsumeHandlerPlugIn(HealthPotionConsumeHandlerPlugIn healthPotionConsumeHandlerPlugIn, ShieldPotionConsumeHandlerPlugIn shieldPotionConsumeHandlerPlugIn)
{
this._healthPotionConsumeHandlerPlugIn = healthPotionConsumeHandlerPlugIn ?? throw new ArgumentNullException(nameof(healthPotionConsumeHandlerPlugIn));
this._shieldPotionConsumeHandlerPlugIn = shieldPotionConsumeHandlerPlugIn ?? throw new ArgumentNullException(nameof(shieldPotionConsumeHandlerPlugIn));
}
/// <inheritdoc />
public override async ValueTask<bool> ConsumeItemAsync(Player player, Item item, Item? targetItem, FruitUsage fruitUsage)
{
if (await base.ConsumeItemAsync(player, item, targetItem, fruitUsage).ConfigureAwait(false))
{
await this._healthPotionConsumeHandlerPlugIn.RecoverAsync(player, item).ConfigureAwait(false);
await this._shieldPotionConsumeHandlerPlugIn.RecoverAsync(player, item).ConfigureAwait(false);
return true;
}
return false;
}
/// <inheritdoc />
protected override bool CheckPreconditions(Player player, Item item)
{
return base.CheckPreconditions(player, item)
&& player.PotionCooldownUntil <= DateTime.UtcNow;
}
}

View File

@@ -0,0 +1,187 @@
// <copyright file="FruitConsumeHandlerPlugIn.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 System.Runtime.InteropServices;
using MUnique.OpenMU.AttributeSystem;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.Views.Character;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Implementation of a consume handler for fruits.
/// </summary>
[Guid("151E4292-96FE-4FF5-A51B-060B510D3DF8")]
[PlugIn]
[Display(Name = nameof(PlugInResources.FruitConsumeHandlerPlugIn_Name), Description = nameof(PlugInResources.FruitConsumeHandlerPlugIn_Description), ResourceType = typeof(PlugInResources))]
public class FruitConsumeHandlerPlugIn : BaseConsumeHandlerPlugIn
{
/// <inheritdoc />
public override ItemIdentifier Key => ItemConstants.Fruits;
/// <inheritdoc />
public override async ValueTask<bool> ConsumeItemAsync(Player player, Item item, Item? targetItem, FruitUsage fruitUsage)
{
if (!this.CheckPreconditions(player, item))
{
return false;
}
var isAdding = fruitUsage != FruitUsage.RemovePoints;
var statAttribute = this.GetStatAttribute(item);
if (player.Level < 10 || item.Level > 4)
{
await player.InvokeViewPlugInAsync<IFruitConsumptionResponsePlugIn>(
p => p.ShowResponseAsync(isAdding ? FruitConsumptionResult.PlusPrevented : FruitConsumptionResult.MinusPrevented, 0, statAttribute)).ConfigureAwait(false);
return false;
}
var statAttributeDefinition = player.SelectedCharacter!.CharacterClass?.StatAttributes.FirstOrDefault(a =>
a.IncreasableByPlayer && a.Attribute == statAttribute);
if (statAttributeDefinition is null)
{
await player.InvokeViewPlugInAsync<IFruitConsumptionResponsePlugIn>(
p => p.ShowResponseAsync(isAdding ? FruitConsumptionResult.PlusPrevented : FruitConsumptionResult.MinusPrevented, 0, statAttribute)).ConfigureAwait(false);
return false;
}
if (player.Inventory!.EquippedItems.Any())
{
await player.InvokeViewPlugInAsync<IFruitConsumptionResponsePlugIn>(p => p.ShowResponseAsync(FruitConsumptionResult.PreventedByEquippedItems, 0, statAttribute)).ConfigureAwait(false);
return false;
}
var maximumRemainingPoints = player.SelectedCharacter.GetMaximumFruitPoints()
- (isAdding
? player.SelectedCharacter.UsedFruitPoints
: player.SelectedCharacter.UsedNegFruitPoints);
if (maximumRemainingPoints <= 0)
{
await player.InvokeViewPlugInAsync<IFruitConsumptionResponsePlugIn>(p => p.ShowResponseAsync(isAdding ? FruitConsumptionResult.PlusPreventedByMaximum : FruitConsumptionResult.MinusPreventedByMaximum, 0, statAttribute)).ConfigureAwait(false);
return false;
}
if (!isAdding && player.Attributes![statAttribute] <= statAttributeDefinition.BaseValue)
{
await player.InvokeViewPlugInAsync<IFruitConsumptionResponsePlugIn>(p => p.ShowResponseAsync(FruitConsumptionResult.MinusPreventedByDefault, 0, statAttribute)).ConfigureAwait(false);
return false;
}
var successPercentage = this.GetSuccessPercentage(player, isAdding);
if (Rand.NextRandomBool(successPercentage))
{
var randomPoints = (byte)Math.Min(maximumRemainingPoints, this.GetRandomPoints(isAdding));
if (isAdding)
{
player.Attributes![statAttribute] += randomPoints;
player.SelectedCharacter.UsedFruitPoints += randomPoints;
await player.InvokeViewPlugInAsync<IFruitConsumptionResponsePlugIn>(p => p.ShowResponseAsync(FruitConsumptionResult.PlusSuccess, randomPoints, statAttribute)).ConfigureAwait(false);
}
else
{
player.Attributes![statAttribute] -= randomPoints;
player.SelectedCharacter.UsedNegFruitPoints += randomPoints;
await player.InvokeViewPlugInAsync<IFruitConsumptionResponsePlugIn>(p => p.ShowResponseAsync(FruitConsumptionResult.MinusSuccess, randomPoints, statAttribute)).ConfigureAwait(false);
}
}
else
{
await player.InvokeViewPlugInAsync<IFruitConsumptionResponsePlugIn>(p => p.ShowResponseAsync(isAdding ? FruitConsumptionResult.PlusFailed : FruitConsumptionResult.MinusFailed, 0, statAttribute)).ConfigureAwait(false);
}
await this.ConsumeSourceItemAsync(player, item).ConfigureAwait(false);
return true;
}
private int GetRandomPoints(bool isAdding)
{
var random = Rand.NextInt(0, 101);
if (isAdding)
{
if (random < 70)
{
return 1;
}
if (random < 95)
{
return 2;
}
return 3;
}
if (random < 50)
{
return 1;
}
if (random < 75)
{
return 3;
}
if (random < 91)
{
return 5;
}
if (random < 98)
{
return 7;
}
return 9;
}
private int GetSuccessPercentage(Player player, bool isAdding)
{
var currentUseCount = isAdding
? player.SelectedCharacter!.UsedFruitPoints
: player.SelectedCharacter!.UsedNegFruitPoints;
var maximumUseCount = player.SelectedCharacter.GetMaximumFruitPoints();
if (currentUseCount <= 10)
{
return 100;
}
if ((currentUseCount - 10) < (maximumUseCount * 0.1))
{
return 90;
}
if ((currentUseCount - 10) < (maximumUseCount * 0.3))
{
return 80;
}
if ((currentUseCount - 10) < (maximumUseCount * 0.5))
{
return 70;
}
if ((currentUseCount - 10) < (maximumUseCount * 0.8))
{
return 60;
}
return 50;
}
private AttributeDefinition GetStatAttribute(Item item)
{
return item.Level switch
{
0 => Stats.BaseEnergy,
1 => Stats.BaseVitality,
2 => Stats.BaseAgility,
3 => Stats.BaseStrength,
4 => Stats.BaseLeadership,
_ => throw new ArgumentException($"Invalid item level {item.Level}"),
};
}
}

View File

@@ -0,0 +1,26 @@
// <copyright file="FruitUsage.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;
/// <summary>
/// Defines how the fruit is used. Only applies, if the the item is a fruit.
/// </summary>
public enum FruitUsage
{
/// <summary>
/// The undefined usage. Used when it doesn't apply.
/// </summary>
Undefined,
/// <summary>
/// Adds 1~3 stat points to the character.
/// </summary>
AddPoints,
/// <summary>
/// Removes 1~9 stat points from the character.
/// </summary>
RemovePoints,
}

View File

@@ -0,0 +1,41 @@
// <copyright file="HarmonyJewelConsumeHandlerPlugIn.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 System.Runtime.InteropServices;
using MUnique.OpenMU.DataModel.Configuration.Items;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The consume handler for the harmony jewel.
/// </summary>
[Guid("DAC3E5C2-FF0F-4773-AFBF-EBDC0C35336D")]
[PlugIn]
[Display(Name = nameof(PlugInResources.HarmonyJewelConsumeHandlerPlugIn_Name), Description = nameof(PlugInResources.HarmonyJewelConsumeHandlerPlugIn_Description), ResourceType = typeof(PlugInResources))]
public class HarmonyJewelConsumeHandlerPlugIn : ItemUpgradeConsumeHandlerPlugIn
{
/// <summary>
/// Initializes a new instance of the <see cref="HarmonyJewelConsumeHandlerPlugIn" /> class.
/// </summary>
public HarmonyJewelConsumeHandlerPlugIn()
: base(new ItemUpgradeConfiguration(ItemOptionTypes.HarmonyOption, true, false, 0.6, ItemFailResult.None))
{
}
/// <inheritdoc />
public override ItemIdentifier Key => ItemConstants.JewelOfHarmony;
/// <inheritdoc />
protected override bool ItemCanHaveOption(Item item)
{
if (item.IsAncient())
{
// Until S16E2 ancient and socket items couldn't have harmony options: https://muonline.webzen.com/en/gameinfo/guide/detail/117
return false;
}
return base.ItemCanHaveOption(item);
}
}

View File

@@ -0,0 +1,22 @@
// -----------------------------------------------------------------------
// <copyright file="HealthPotionConsumeHandlerPlugIn.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.AttributeSystem;
using MUnique.OpenMU.GameLogic.Attributes;
/// <summary>
/// The consume handler for a potion that recovers health.
/// </summary>
public abstract class HealthPotionConsumeHandlerPlugIn : RecoverConsumeHandlerPlugIn.ManaHealthConsumeHandlerPlugIn, IItemConsumeHandlerPlugIn
{
/// <inheritdoc/>
protected override AttributeDefinition MaximumAttribute => Stats.MaximumHealth;
/// <inheritdoc/>
protected override AttributeDefinition CurrentAttribute => Stats.CurrentHealth;
}

View File

@@ -0,0 +1,29 @@
// <copyright file="HigherRefineStoneConsumeHandlerPlugIn.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 System.Runtime.InteropServices;
using MUnique.OpenMU.DataModel.Configuration.Items;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Consume handler for the Higher Refine Stone which increases the item option of <see cref="ItemOptionTypes.HarmonyOption"/>.
/// </summary>
[Guid("A9F58DF6-06DB-4187-B386-9F00382333EE")]
[PlugIn]
[Display(Name = nameof(PlugInResources.HigherRefineStoneConsumeHandlerPlugIn_Name), Description = nameof(PlugInResources.HigherRefineStoneConsumeHandlerPlugIn_Description), ResourceType = typeof(PlugInResources))]
public class HigherRefineStoneConsumeHandlerPlugIn : RefineStoneUpgradeConsumeHandlerPlugIn
{
/// <summary>
/// Initializes a new instance of the <see cref="HigherRefineStoneConsumeHandlerPlugIn" /> class.
/// </summary>
public HigherRefineStoneConsumeHandlerPlugIn()
: base(new ItemUpgradeConfiguration(ItemOptionTypes.HarmonyOption, false, true, 0.8, ItemFailResult.SetOptionToBaseLevel))
{
}
/// <inheritdoc />
public override ItemIdentifier Key => ItemConstants.HigherRefineStone;
}

View File

@@ -0,0 +1,30 @@
// -----------------------------------------------------------------------
// <copyright file="IItemConsumeHandlerPlugIn.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 System.Runtime.InteropServices;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Consume handler to modify items which are specified by the target slot.
/// </summary>
[Guid("420FB26E-CE78-4942-8589-3A416EF4E31F")]
[PlugInPoint("Item consume handlers", "Plugins which will be executed to consume an item.")]
internal interface IItemConsumeHandlerPlugIn : IStrategyPlugIn<ItemIdentifier>
{
/// <summary>
/// Consumes the item at the specified slot, and reduces its durability by one.
/// If the durability has reached 0, the item is getting destroyed.
/// If a target slot is specified, the consumption targets the item on this slot (e.g. upgrade of an item by a jewel).
/// </summary>
/// <param name="player">The player which is consuming.</param>
/// <param name="item">The item which gets consumed.</param>
/// <param name="targetItem">The item which is the target of the consumption (e.g. upgrade target of a jewel).</param>
/// <param name="fruitUsage">In case the item is a fruit, this parameter defines how the fruit should be used.</param>
/// <returns>The success of the consumption.</returns>
ValueTask<bool> ConsumeItemAsync(Player player, Item item, Item? targetItem, FruitUsage fruitUsage);
}

View File

@@ -0,0 +1,83 @@
// <copyright file="ItemConsumeAction.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 System.ComponentModel;
using MUnique.OpenMU.GameLogic.PlugIns;
using MUnique.OpenMU.GameLogic.Views.Inventory;
/// <summary>
/// Action to consume an item.
/// </summary>
public class ItemConsumeAction
{
private readonly IItemConsumeHandlerPlugIn _magicEffectHandler = new ApplyMagicEffectConsumeHandlerPlugIn();
/// <summary>
/// Handles the consume request.
/// </summary>
/// <param name="player">The player.</param>
/// <param name="inventorySlot">The inventory slot.</param>
/// <param name="inventoryTargetSlot">The inventory target slot.</param>
/// <param name="fruitUsage">The fruit usage.</param>
public async ValueTask HandleConsumeRequestAsync(Player player, byte inventorySlot, byte inventoryTargetSlot, FruitUsage fruitUsage)
{
var item = player.Inventory?.GetItem(inventorySlot);
if (item?.Definition is null)
{
await player.InvokeViewPlugInAsync<IRequestedItemConsumptionFailedPlugIn>(p => p.RequestedItemConsumptionFailedAsync()).ConfigureAwait(false);
return;
}
var consumeHandler = player.GameContext.PlugInManager.GetStrategy<ItemIdentifier, IItemConsumeHandlerPlugIn>(new ItemIdentifier(item.Definition.Number, item.Definition.Group))
?? player.GameContext.PlugInManager.GetStrategy<ItemIdentifier, IItemConsumeHandlerPlugIn>(new ItemIdentifier(null, item.Definition.Group));
if (consumeHandler is null && item.Definition.Skill is { } && !item.IsWearable())
{
consumeHandler = player.GameContext.PlugInManager.GetStrategy<ItemIdentifier, IItemConsumeHandlerPlugIn>(ItemConstants.AllScrolls);
}
if (consumeHandler is null && item.Definition.ConsumeEffect is { })
{
consumeHandler = this._magicEffectHandler;
}
if (consumeHandler is null)
{
await player.InvokeViewPlugInAsync<IRequestedItemConsumptionFailedPlugIn>(p => p.RequestedItemConsumptionFailedAsync()).ConfigureAwait(false);
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.UsingThisItemNotImplemented)).ConfigureAwait(false);
return;
}
var targetItem = player.Inventory!.GetItem(inventoryTargetSlot);
if (player.GameContext.PlugInManager.GetPlugInPoint<IItemConsumingPlugIn>() is { } plugInPoint)
{
var eventArgs = new CancelEventArgs();
plugInPoint.ItemConsuming(player, item, targetItem, eventArgs);
if (eventArgs.Cancel)
{
return;
}
}
if (!await consumeHandler.ConsumeItemAsync(player, item, targetItem, fruitUsage).ConfigureAwait(false))
{
await player.InvokeViewPlugInAsync<IRequestedItemConsumptionFailedPlugIn>(p => p.RequestedItemConsumptionFailedAsync()).ConfigureAwait(false);
return;
}
if (item.Durability == 0)
{
await player.DestroyInventoryItemAsync(item).ConfigureAwait(false);
}
else
{
await player.InvokeViewPlugInAsync<IItemDurabilityChangedPlugIn>(p => p.ItemDurabilityChangedAsync(item, true)).ConfigureAwait(false);
}
player.GameContext.PlugInManager.GetPlugInPoint<IItemConsumedPlugIn>()?.ItemConsumed(player, item, targetItem);
}
}

View File

@@ -0,0 +1,62 @@
// -----------------------------------------------------------------------
// <copyright file="ItemModifyConsumeHandlerPlugIn.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.GameLogic.Views.Inventory;
using MUnique.OpenMU.Persistence;
/// <summary>
/// Consume handler to modify items which are specified by the target slot.
/// </summary>
public abstract class ItemModifyConsumeHandlerPlugIn : BaseConsumeHandlerPlugIn
{
/// <inheritdoc/>
public override async ValueTask<bool> ConsumeItemAsync(Player player, Item item, Item? targetItem, FruitUsage fruitUsage)
{
if (player.PlayerState.CurrentState != PlayerState.EnteredWorld)
{
return false;
}
if (targetItem is null)
{
return false;
}
if (targetItem.ItemSlot <= InventoryConstants.LastEquippableItemSlotIndex)
{
// It shouldn't be possible to upgrade an equipped item.
// The original server allowed this, however people managed to downgrade their maxed out weapons to +6 when some
// visual bugs on the client occured :D Example: On the server side there is a jewel of bless on a certain slot,
// but client shows a health potion. When the client then consumes the potion it would apply the bless to item slot 0.
return false;
}
if (!this.CheckPreconditions(player, item))
{
return false;
}
if (!this.ModifyItem(targetItem, player.PersistenceContext))
{
return false;
}
await this.ConsumeSourceItemAsync(player, item).ConfigureAwait(false);
await player.InvokeViewPlugInAsync<IItemUpgradedPlugIn>(p => p.ItemUpgradedAsync(targetItem)).ConfigureAwait(false);
return true;
}
/// <summary>
/// Modifies the item.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="persistenceContext">The persistence context.</param>
/// <returns>Flag indicating whether the modification of the item occured.</returns>
protected abstract bool ModifyItem(Item item, IContext persistenceContext);
}

View File

@@ -0,0 +1,244 @@
// -----------------------------------------------------------------------
// <copyright file="ItemUpgradeConsumeHandlerPlugIn.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;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.Persistence;
/// <summary>
/// An item consume handler which upgrades the target item.
/// </summary>
public abstract class ItemUpgradeConsumeHandlerPlugIn : ItemModifyConsumeHandlerPlugIn
{
/// <summary>
/// Initializes a new instance of the <see cref="ItemUpgradeConsumeHandlerPlugIn"/> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
internal ItemUpgradeConsumeHandlerPlugIn(ItemUpgradeConfiguration configuration)
{
this.Configuration = configuration;
}
/// <summary>
/// Specifies what should happen with the item, if the upgrading failed randomly.
/// </summary>
public enum ItemFailResult
{
/// <summary>
/// Nothing happens.
/// </summary>
None,
/// <summary>
/// Sets the option to the base level.
/// </summary>
SetOptionToBaseLevel,
/// <summary>
/// Removes the option.
/// </summary>
RemoveOption,
}
/// <summary>
/// Gets the upgrade configuration.
/// </summary>
internal ItemUpgradeConfiguration Configuration { get; }
/// <inheritdoc/>
protected override bool ModifyItem(Item item, IContext persistenceContext)
{
if (!this.ItemCanHaveOption(item))
{
return false;
}
if (this.ItemHasOptionAlready(item))
{
return this.TryUpgradeItemOption(item);
}
return this.TryAddItemOption(item, persistenceContext);
}
/// <summary>
/// Checks if an item can have the configured option.
/// </summary>
/// <param name="item">The item.</param>
/// <returns>Flag indicating whether the item can have the option.</returns>
protected virtual bool ItemCanHaveOption(Item item)
{
return item.Definition?.PossibleItemOptions.Any(o => o.PossibleOptions.Any(p => p.OptionType == this.Configuration.OptionType)) ?? false;
}
/// <summary>
/// Tries to upgrade the item option.
/// </summary>
/// <param name="item">The item to upgrade.</param>
/// <returns>Flag indicating whether the item option was upgraded.</returns>
protected virtual bool TryUpgradeItemOption(Item item)
{
if (!this.Configuration.IncreasesOption)
{
return false;
}
var itemOption = item.ItemOptions.First(o => o.ItemOption?.OptionType == this.Configuration.OptionType);
var increasableOption = itemOption.ItemOption;
var higherOptionPossible = increasableOption?.LevelDependentOptions.Any(o => o.Level > itemOption.Level && o.RequiredItemLevel <= item.Level) ?? false;
if (!higherOptionPossible)
{
return false;
}
if (Rand.NextRandomBool(this.Configuration.SuccessChance))
{
itemOption.Level++;
}
else
{
this.HandleFailedUpgrade(item, itemOption);
}
return true;
}
private void HandleFailedUpgrade(Item item, ItemOptionLink itemOption)
{
switch (this.Configuration.FailResult)
{
case ItemFailResult.RemoveOption:
item.ItemOptions.Remove(itemOption);
break;
case ItemFailResult.SetOptionToBaseLevel:
itemOption.Level = itemOption.ItemOption?.LevelDependentOptions.Min(ldo => ldo.Level) ?? itemOption.Level;
break;
default:
// do nothing
break;
}
}
private bool TryAddItemOption(Item item, IContext persistenceContext)
{
if (!this.Configuration.AddsOption || item.Definition is null)
{
return false;
}
if (Rand.NextRandomBool(this.Configuration.SuccessChance))
{
var possibleOptions = item.Definition.PossibleItemOptions.
SelectMany(o => o.PossibleOptions).
Where(o => o.OptionType == this.Configuration.OptionType
&& (!o.LevelDependentOptions.Any() || o.LevelDependentOptions.Any(ldo => ldo.RequiredItemLevel <= item.Level))).ToList();
if (!possibleOptions.Any())
{
return false;
}
var optionLink = persistenceContext.CreateNew<ItemOptionLink>();
if (this.Configuration.OptionType == ItemOptionTypes.HarmonyOption)
{
// Str and agi reduction options are not always applicable, and so should be removed from the pool
if (!item.Definition.Requirements.Any(r => r.Attribute == Stats.TotalStrengthRequirementValue)
&& possibleOptions.FirstOrDefault(po => po.LevelDependentOptions
.Any(ldo => ldo.PowerUpDefinition?.TargetAttribute == Stats.RequiredStrengthReduction)) is { } strReductOpt)
{
possibleOptions.Remove(strReductOpt);
}
if (!item.Definition.Requirements.Any(r => r.Attribute == Stats.TotalAgilityRequirementValue)
&& possibleOptions.FirstOrDefault(po => po.LevelDependentOptions
.Any(ldo => ldo.PowerUpDefinition?.TargetAttribute == Stats.RequiredAgilityReduction)) is { } agiReductOpt)
{
possibleOptions.Remove(agiReductOpt);
}
optionLink.ItemOption = possibleOptions.SelectWeightedRandom(possibleOptions.Select(po => (int)po.Weight));
optionLink.Level = optionLink.ItemOption?.LevelDependentOptions.Select(ldo => ldo.Level).Min() ?? 0;
}
else
{
// ItemOptionTypes.Option
optionLink.ItemOption = possibleOptions.SelectRandom();
optionLink.Level = 1;
}
item.ItemOptions.Add(optionLink);
}
return true;
}
private bool ItemHasOptionAlready(Item item)
{
return item.ItemOptions.Any(o => o.ItemOption?.OptionType == this.Configuration.OptionType);
}
/// <summary>
/// The upgrade configuration.
/// </summary>
internal class ItemUpgradeConfiguration
{
/// <summary>
/// Initializes a new instance of the <see cref="ItemUpgradeConfiguration"/> class.
/// </summary>
/// <param name="optionType">Type of the option.</param>
/// <param name="addsOption">if set to <c>true</c> [adds option].</param>
/// <param name="increasesOption">if set to <c>true</c> [increases option].</param>
/// <param name="successChance">The success chance.</param>
/// <param name="failResult">The fail result.</param>
public ItemUpgradeConfiguration(ItemOptionType optionType, bool addsOption, bool increasesOption, double successChance, ItemFailResult failResult)
{
this.OptionType = optionType;
this.AddsOption = addsOption;
this.IncreasesOption = increasesOption;
this.SuccessChance = successChance;
this.FailResult = failResult;
}
/// <summary>
/// Gets the type of the option.
/// </summary>
public ItemOptionType OptionType { get; }
/// <summary>
/// Gets or sets a value indicating whether the handler adds option, if the item does not already have it.
/// </summary>
public bool AddsOption { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the handler increases the option.
/// </summary>
public bool IncreasesOption { get; set; }
/// <summary>
/// Gets or sets the success chance between 0 and 1.
/// </summary>
public double SuccessChance { get; set; }
/// <summary>
/// Gets or sets what should happen with the item if the upgrade fails because of no success.
/// </summary>
public ItemFailResult FailResult { get; set; }
/// <summary>
/// Gets or sets the option type which can boost the success rate.
/// </summary>
/// <remarks>
/// e.g. luck option which adds 25 per cent.
/// </remarks>
public ItemOptionType? BoostOptionType { get; set; }
/// <summary>
/// Gets or sets the success chance boost if the target item has the option of type specified in <see cref="BoostOptionType"/>.
/// </summary>
public double SuccessChanceBoost { get; set; }
}
}

View File

@@ -0,0 +1,28 @@
// <copyright file="LargeComplexPotionConsumeHandlerPlugIn.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 System.Runtime.InteropServices;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Consume handler for large complex potions.
/// </summary>
[Guid("F5A8C0C4-7960-4815-83C2-F57339CD6FE2")]
[PlugIn]
[Display(Name = nameof(PlugInResources.LargeComplexPotionConsumeHandlerPlugIn_Name), Description = nameof(PlugInResources.LargeComplexPotionConsumeHandlerPlugIn_Description), ResourceType = typeof(PlugInResources))]
public class LargeComplexPotionConsumeHandlerPlugIn : ComplexPotionConsumeHandlerPlugIn
{
/// <summary>
/// Initializes a new instance of the <see cref="LargeComplexPotionConsumeHandlerPlugIn"/> class.
/// </summary>
public LargeComplexPotionConsumeHandlerPlugIn()
: base(new LargeHealthPotionConsumeHandlerPlugIn(), new LargeShieldPotionConsumeHandlerPlugIn())
{
}
/// <inheritdoc />
public override ItemIdentifier Key => ItemConstants.LargeComplexPotion;
}

View File

@@ -0,0 +1,25 @@
// -----------------------------------------------------------------------
// <copyright file="LargeHealthPotionConsumeHandlerPlugIn.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 System.Runtime.InteropServices;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Consume handler for big health potions.
/// </summary>
[Guid("5035BBF5-A45D-454F-9D15-4DB6F725DCFB")]
[PlugIn]
[Display(Name = nameof(PlugInResources.LargeHealthPotionConsumeHandlerPlugIn_Name), Description = nameof(PlugInResources.LargeHealthPotionConsumeHandlerPlugIn_Description), ResourceType = typeof(PlugInResources))]
public class LargeHealthPotionConsumeHandlerPlugIn : HealthPotionConsumeHandlerPlugIn
{
/// <inheritdoc />
public override ItemIdentifier Key => ItemConstants.LargeHealingPotion;
/// <inheritdoc/>
protected override int Multiplier => 3;
}

View File

@@ -0,0 +1,25 @@
// -----------------------------------------------------------------------
// <copyright file="LargeManaPotionConsumeHandler.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 System.Runtime.InteropServices;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Consume handler for gib mana potions.
/// </summary>
[Guid("21CB28A4-BE9A-421C-9C7C-6F2E0FC9D614")]
[PlugIn]
[Display(Name = nameof(PlugInResources.LargeManaPotionConsumeHandler_Name), Description = nameof(PlugInResources.LargeManaPotionConsumeHandler_Description), ResourceType = typeof(PlugInResources))]
public class LargeManaPotionConsumeHandler : ManaPotionConsumeHandler
{
/// <inheritdoc />
public override ItemIdentifier Key => ItemConstants.LargeManaPotion;
/// <inheritdoc/>
protected override int Multiplier => 3;
}

View File

@@ -0,0 +1,25 @@
// -----------------------------------------------------------------------
// <copyright file="LargeShieldPotionConsumeHandlerPlugIn.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 System.Runtime.InteropServices;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Consume handler for large shield potions.
/// </summary>
[Guid("683C4BF1-8794-41B0-9742-B17B73A12BFE")]
[PlugIn]
[Display(Name = nameof(PlugInResources.LargeShieldPotionConsumeHandlerPlugIn_Name), Description = nameof(PlugInResources.LargeShieldPotionConsumeHandlerPlugIn_Description), ResourceType = typeof(PlugInResources))]
public class LargeShieldPotionConsumeHandlerPlugIn : ShieldPotionConsumeHandlerPlugIn
{
/// <inheritdoc />
public override ItemIdentifier Key => ItemConstants.LargeShieldPotion;
/// <inheritdoc />
protected override double RecoverPercentage => 100;
}

View File

@@ -0,0 +1,57 @@
// <copyright file="LearnablesConsumeHandlerPlugIn.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 System.Runtime.InteropServices;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Consume handler for items (e.g. scrolls, orbs) which add a skill when being consumed.
/// </summary>
[Guid("FD86947E-0B94-4490-8158-63B11A61565F")]
[PlugIn]
[Display(Name = nameof(PlugInResources.LearnablesConsumeHandlerPlugIn_Name), Description = nameof(PlugInResources.LearnablesConsumeHandlerPlugIn_Description), ResourceType = typeof(PlugInResources))]
public class LearnablesConsumeHandlerPlugIn : BaseConsumeHandlerPlugIn
{
/// <inheritdoc />
public override ItemIdentifier Key => ItemConstants.AllScrolls;
/// <inheritdoc/>
public override async ValueTask<bool> ConsumeItemAsync(Player player, Item item, Item? targetItem, FruitUsage fruitUsage)
{
var skill = this.GetLearnableSkill(item, player.GameContext.Configuration);
if (skill is null || player.SkillList!.ContainsSkill(skill.Number.ToUnsigned()))
{
return false;
}
if (!await base.ConsumeItemAsync(player, item, targetItem, fruitUsage).ConfigureAwait(false))
{
return false;
}
await player.SkillList.AddLearnedSkillAsync(skill).ConfigureAwait(false);
return true;
}
/// <inheritdoc />
protected override bool CheckPreconditions(Player player, Item item)
{
return base.CheckPreconditions(player, item)
&& player.CompliesRequirements(item);
}
/// <summary>
/// Gets the learnable skill.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="gameConfiguration">The game configuration.</param>
/// <returns>The skill to learn.</returns>
protected virtual Skill? GetLearnableSkill(Item item, GameConfiguration gameConfiguration)
{
return item.Definition?.Skill;
}
}

View File

@@ -0,0 +1,31 @@
// -----------------------------------------------------------------------
// <copyright file="LifeJewelConsumeHandlerPlugIn.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 System.Runtime.InteropServices;
using MUnique.OpenMU.DataModel.Configuration.Items;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Consume handler for the Jewel of Life which adds and increases the item option of <see cref="ItemOptionTypes.Option"/>.
/// </summary>
[Guid("8AC6592D-D51C-47C9-B491-4778C615691D")]
[PlugIn]
[Display(Name = nameof(PlugInResources.LifeJewelConsumeHandlerPlugIn_Name), Description = nameof(PlugInResources.LifeJewelConsumeHandlerPlugIn_Description), ResourceType = typeof(PlugInResources))]
public class LifeJewelConsumeHandlerPlugIn : ItemUpgradeConsumeHandlerPlugIn
{
/// <summary>
/// Initializes a new instance of the <see cref="LifeJewelConsumeHandlerPlugIn" /> class.
/// </summary>
public LifeJewelConsumeHandlerPlugIn()
: base(new ItemUpgradeConfiguration(ItemOptionTypes.Option, true, true, 0.5, ItemFailResult.RemoveOption))
{
}
/// <inheritdoc />
public override ItemIdentifier Key => ItemConstants.JewelOfLife;
}

View File

@@ -0,0 +1,29 @@
// <copyright file="LowerRefineStoneConsumeHandlerPlugIn.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 System.Runtime.InteropServices;
using MUnique.OpenMU.DataModel.Configuration.Items;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Consume handler for the Lower Refine Stone which increases the item option of <see cref="ItemOptionTypes.HarmonyOption"/>.
/// </summary>
[Guid("71380E37-7AA9-447A-8A83-D08B676E55E1")]
[PlugIn]
[Display(Name = nameof(PlugInResources.LowerRefineStoneConsumeHandlerPlugIn_Name), Description = nameof(PlugInResources.LowerRefineStoneConsumeHandlerPlugIn_Description), ResourceType = typeof(PlugInResources))]
public class LowerRefineStoneConsumeHandlerPlugIn : RefineStoneUpgradeConsumeHandlerPlugIn
{
/// <summary>
/// Initializes a new instance of the <see cref="LowerRefineStoneConsumeHandlerPlugIn" /> class.
/// </summary>
public LowerRefineStoneConsumeHandlerPlugIn()
: base(new ItemUpgradeConfiguration(ItemOptionTypes.HarmonyOption, false, true, 0.2, ItemFailResult.SetOptionToBaseLevel))
{
}
/// <inheritdoc />
public override ItemIdentifier Key => ItemConstants.LowerRefineStone;
}

View File

@@ -0,0 +1,23 @@
// -----------------------------------------------------------------------
// <copyright file="ManaPotionConsumeHandler.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.AttributeSystem;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.Views.Character;
/// <summary>
/// Consume handler for potions which refills the players attribute <see cref="Stats.CurrentMana"/>.
/// </summary>
public abstract class ManaPotionConsumeHandler : RecoverConsumeHandlerPlugIn.ManaHealthConsumeHandlerPlugIn, IItemConsumeHandlerPlugIn
{
/// <inheritdoc/>
protected override AttributeDefinition MaximumAttribute => Stats.MaximumMana;
/// <inheritdoc/>
protected override AttributeDefinition CurrentAttribute => Stats.CurrentMana;
}

View File

@@ -0,0 +1,28 @@
// <copyright file="MediumComplexPotionConsumeHandlerPlugIn.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 System.Runtime.InteropServices;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Consume handler for medium complex potions.
/// </summary>
[Guid("D4ED0E2E-3CAA-4B35-BA17-230E29EC324B")]
[PlugIn]
[Display(Name = nameof(PlugInResources.MediumComplexPotionConsumeHandlerPlugIn_Name), Description = nameof(PlugInResources.MediumComplexPotionConsumeHandlerPlugIn_Description), ResourceType = typeof(PlugInResources))]
public class MediumComplexPotionConsumeHandlerPlugIn : ComplexPotionConsumeHandlerPlugIn
{
/// <summary>
/// Initializes a new instance of the <see cref="MediumComplexPotionConsumeHandlerPlugIn"/> class.
/// </summary>
public MediumComplexPotionConsumeHandlerPlugIn()
: base(new MediumHealthPotionConsumeHandlerPlugIn(), new MediumShieldPotionConsumeHandlerPlugIn())
{
}
/// <inheritdoc />
public override ItemIdentifier Key => ItemConstants.MediumComplexPotion;
}

View File

@@ -0,0 +1,25 @@
// -----------------------------------------------------------------------
// <copyright file="MediumHealthPotionConsumeHandlerPlugIn.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 System.Runtime.InteropServices;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Consume handler for middle health potions.
/// </summary>
[Guid("2ED0A431-B562-4097-AAE4-C972074BDCBA")]
[PlugIn]
[Display(Name = nameof(PlugInResources.MediumHealthPotionConsumeHandlerPlugIn_Name), Description = nameof(PlugInResources.MediumHealthPotionConsumeHandlerPlugIn_Description), ResourceType = typeof(PlugInResources))]
public class MediumHealthPotionConsumeHandlerPlugIn : HealthPotionConsumeHandlerPlugIn
{
/// <inheritdoc />
public override ItemIdentifier Key => ItemConstants.MediumHealingPotion;
/// <inheritdoc/>
protected override int Multiplier => 2;
}

View File

@@ -0,0 +1,25 @@
// -----------------------------------------------------------------------
// <copyright file="MediumManaPotionConsumeHandler.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 System.Runtime.InteropServices;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Consume handler for middle health potions.
/// </summary>
[Guid("EC1F3DDF-5AF1-455C-AE0C-11A8018AE7D4")]
[PlugIn]
[Display(Name = nameof(PlugInResources.MediumManaPotionConsumeHandler_Name), Description = nameof(PlugInResources.MediumManaPotionConsumeHandler_Description), ResourceType = typeof(PlugInResources))]
public class MediumManaPotionConsumeHandler : ManaPotionConsumeHandler
{
/// <inheritdoc />
public override ItemIdentifier Key => ItemConstants.MediumManaPotion;
/// <inheritdoc/>
protected override int Multiplier => 2;
}

View File

@@ -0,0 +1,25 @@
// -----------------------------------------------------------------------
// <copyright file="MediumShieldPotionConsumeHandlerPlugIn.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 System.Runtime.InteropServices;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Consume handler for middle shield potions.
/// </summary>
[Guid("5205B818-68DE-4639-BA50-85CD285CDC95")]
[PlugIn]
[Display(Name = nameof(PlugInResources.MediumShieldPotionConsumeHandlerPlugIn_Name), Description = nameof(PlugInResources.MediumShieldPotionConsumeHandlerPlugIn_Description), ResourceType = typeof(PlugInResources))]
public class MediumShieldPotionConsumeHandlerPlugIn : ShieldPotionConsumeHandlerPlugIn
{
/// <inheritdoc />
public override ItemIdentifier Key => ItemConstants.MediumShieldPotion;
/// <inheritdoc />
protected override double RecoverPercentage => 40;
}

View File

@@ -0,0 +1,56 @@
// <copyright file="RecoverConsumeHandlerConfiguration.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 System.ComponentModel.DataAnnotations;
using MUnique.OpenMU.DataModel.Composition;
/// <summary>
/// The configuration of a <see cref="RecoverConsumeHandlerPlugIn"/>.
/// </summary>
public class RecoverConsumeHandlerConfiguration
{
/// <summary>
/// Gets or sets the total recover percentage.
/// </summary>
[Display(ResourceType = typeof(PlugInResources), Name = nameof(PlugInResources.RecoverConsumeHandlerConfiguration_TotalRecoverPercentage_Name))]
public double TotalRecoverPercentage { get; set; }
/// <summary>
/// Gets or sets the recover percentage increase by potion level.
/// </summary>
[Display(ResourceType = typeof(PlugInResources), Name = nameof(PlugInResources.RecoverConsumeHandlerConfiguration_RecoverPercentageIncreaseByPotionLevel_Name))]
public double RecoverPercentageIncreaseByPotionLevel { get; set; }
/// <summary>
/// Gets or sets the recover delay reduction by potion level.
/// A value between 0 and 1 (exclusive).
/// 1 would mean that the recover works instantly since level 1.
/// </summary>
[Display(ResourceType = typeof(PlugInResources), Name = nameof(PlugInResources.RecoverConsumeHandlerConfiguration_RecoverDelayReductionByPotionLevel_Name), Description = nameof(PlugInResources.RecoverConsumeHandlerConfiguration_RecoverDelayReductionByPotionLevel_Description))]
public double RecoverDelayReductionByPotionLevel { get; set; }
/// <summary>
/// Gets or sets the value which is additionally recovered.
/// From this value, the character level is subtracted.
/// </summary>
[Display(ResourceType = typeof(PlugInResources), Name = nameof(PlugInResources.RecoverConsumeHandlerConfiguration_AdditionalRecoverMinusCharacterLevel_Name), Description = nameof(PlugInResources.RecoverConsumeHandlerConfiguration_AdditionalRecoverMinusCharacterLevel_Description))]
public int AdditionalRecoverMinusCharacterLevel { get; set; }
/// <summary>
/// Gets or sets the cooldown time for the next consumption.
/// </summary>
[Display(ResourceType = typeof(PlugInResources), Name = nameof(PlugInResources.RecoverConsumeHandlerConfiguration_CooldownTime_Name), Description = nameof(PlugInResources.RecoverConsumeHandlerConfiguration_CooldownTime_Description))]
public TimeSpan CooldownTime { get; set; }
/// <summary>
/// Gets or sets the recover steps. If none are defined, the recover happens
/// instantly.
/// </summary>
[Display(ResourceType = typeof(PlugInResources), Name = nameof(PlugInResources.RecoverConsumeHandlerConfiguration_RecoverSteps_Name), Description = nameof(PlugInResources.RecoverConsumeHandlerConfiguration_RecoverSteps_Description))]
[ScaffoldColumn(true)]
[MemberOfAggregate]
public ICollection<RecoverStep> RecoverSteps { get; set; } = new List<RecoverStep>();
}

View File

@@ -0,0 +1,156 @@
// <copyright file="RecoverConsumeHandlerPlugIn.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.AttributeSystem;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Consume handler which can recover attributes.
/// </summary>
public abstract class RecoverConsumeHandlerPlugIn : BaseConsumeHandlerPlugIn, ISupportCustomConfiguration<RecoverConsumeHandlerConfiguration>, ISupportDefaultCustomConfiguration
{
/// <summary>
/// Gets or sets the configuration.
/// </summary>
public RecoverConsumeHandlerConfiguration? Configuration { get; set; }
/// <summary>
/// Gets the attribute which contains the value which should get recovered.
/// </summary>
protected abstract AttributeDefinition CurrentAttribute { get; }
/// <summary>
/// Gets the attribute which contains the maximum value of the <see cref="CurrentAttribute"/>.
/// </summary>
protected abstract AttributeDefinition MaximumAttribute { get; }
/// <inheritdoc/>
public override async ValueTask<bool> ConsumeItemAsync(Player player, Item item, Item? targetItem, FruitUsage fruitUsage)
{
if (await base.ConsumeItemAsync(player, item, targetItem, fruitUsage).ConfigureAwait(false))
{
await this.RecoverAsync(player, item).ConfigureAwait(false);
return true;
}
return false;
}
/// <inheritdoc />
public abstract object CreateDefaultConfig();
/// <summary>
/// Recovers the attributes of the specified player.
/// </summary>
/// <param name="player">The player.</param>
/// <param name="item">The item.</param>
internal async ValueTask RecoverAsync(Player player, Item item)
{
if (player.Attributes is null)
{
return;
}
var configuration = this.Configuration ??= (RecoverConsumeHandlerConfiguration)this.CreateDefaultConfig();
var recoverPercentage = configuration.TotalRecoverPercentage + (item.Level * configuration.RecoverPercentageIncreaseByPotionLevel);
var additionalRecover = Math.Max(0, configuration.AdditionalRecoverMinusCharacterLevel - player.Attributes[Stats.Level]);
var totalRecoverAmount = (player.Attributes[this.MaximumAttribute] * recoverPercentage / 100.0) + additionalRecover;
var delayReduction = configuration.RecoverDelayReductionByPotionLevel * item.Level;
if (configuration.RecoverSteps.Count == 0 || delayReduction >= 1)
{
player.Attributes[this.CurrentAttribute] = (uint)Math.Min(player.Attributes[this.MaximumAttribute], player.Attributes[this.CurrentAttribute] + totalRecoverAmount);
await this.OnAfterRecoverAsync(player).ConfigureAwait(false);
}
else
{
_ = this.RecoverByStepsAsync(player, delayReduction, configuration, totalRecoverAmount);
}
player.PotionCooldownUntil = DateTime.UtcNow.Add(this.Configuration.CooldownTime);
}
/// <inheritdoc />
protected override bool CheckPreconditions(Player player, Item item)
{
return base.CheckPreconditions(player, item)
&& player.PotionCooldownUntil <= DateTime.UtcNow;
}
/// <summary>
/// Called after the attribute was recovered. Can be handled to update
/// the view.
/// </summary>
/// <param name="player">The player.</param>
protected virtual ValueTask OnAfterRecoverAsync(Player player)
{
return default;
}
private async Task RecoverByStepsAsync(Player player, double delayReduction, RecoverConsumeHandlerConfiguration configuration, double totalRecoverAmount)
{
foreach (var step in configuration.RecoverSteps)
{
if (!player.IsAlive || player.Attributes is not { } playerAttributes)
{
break;
}
var delay = TimeSpan.FromMilliseconds(step.Delay.TotalMilliseconds * (1.0 - delayReduction));
if (delay.TotalMilliseconds > 0)
{
await Task.Delay(delay).ConfigureAwait(false);
}
var recoverAmount = totalRecoverAmount * (step.RecoverPercentage / 100.0);
playerAttributes[this.CurrentAttribute] = (uint)Math.Min(playerAttributes[this.MaximumAttribute], playerAttributes[this.CurrentAttribute] + recoverAmount);
await this.OnAfterRecoverAsync(player).ConfigureAwait(false);
}
}
/// <summary>
/// The base class for health and mana /.
/// </summary>
public abstract class ManaHealthConsumeHandlerPlugIn : RecoverConsumeHandlerPlugIn
{
/// <summary>
/// Gets the multiplier of 50 for the additional recover.
/// </summary>
protected abstract int Multiplier { get; }
/// <inheritdoc />
public override object CreateDefaultConfig()
{
return new RecoverConsumeHandlerConfiguration
{
TotalRecoverPercentage = this.Multiplier * 10,
AdditionalRecoverMinusCharacterLevel = (this.Multiplier + 1) * 50,
RecoverDelayReductionByPotionLevel = 1.0 / 16.0,
RecoverPercentageIncreaseByPotionLevel = 1,
CooldownTime = TimeSpan.FromSeconds(0.5),
RecoverSteps =
{
new RecoverStep
{
Delay = TimeSpan.FromMilliseconds(200),
RecoverPercentage = 20,
},
new RecoverStep
{
Delay = TimeSpan.FromMilliseconds(600),
RecoverPercentage = 60,
},
new RecoverStep
{
Delay = TimeSpan.FromMilliseconds(200),
RecoverPercentage = 20,
},
},
};
}
}
}

View File

@@ -0,0 +1,24 @@
// <copyright file="RecoverStep.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;
/// <summary>
/// Defines one step of a recovery.
/// </summary>
public class RecoverStep
{
/// <summary>
/// Gets or sets the delay after which the recovery of the defined <see cref="RecoverPercentage"/>
/// occurs.
/// </summary>
public TimeSpan Delay { get; set; }
/// <summary>
/// Gets or sets the recover percentage of the <see cref="RecoverConsumeHandlerConfiguration.TotalRecoverPercentage"/>
/// after waiting for the <see cref="Delay"/>.
/// The total of all steps of a <see cref="RecoverConsumeHandlerConfiguration"/> should be 100.
/// </summary>
public int RecoverPercentage { get; set; }
}

View File

@@ -0,0 +1,45 @@
// -----------------------------------------------------------------------
// <copyright file="RefineStoneUpgradeConsumeHandlerPlugIn.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;
using MUnique.OpenMU.GameLogic.Attributes;
/// <summary>
/// Consume handler for a Refine Stone which increases the item option of <see cref="ItemOptionTypes.HarmonyOption"/>.
/// </summary>
public abstract class RefineStoneUpgradeConsumeHandlerPlugIn : ItemUpgradeConsumeHandlerPlugIn
{
/// <summary>
/// Initializes a new instance of the <see cref="RefineStoneUpgradeConsumeHandlerPlugIn"/> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
private protected RefineStoneUpgradeConsumeHandlerPlugIn(ItemUpgradeConfiguration configuration)
: base(configuration)
{
}
/// <inheritdoc/>
protected override bool TryUpgradeItemOption(Item item)
{
var harmonyOption = item.ItemOptions.First(o => o.ItemOption?.OptionType == this.Configuration.OptionType);
var levelOptions = harmonyOption.ItemOption?.LevelDependentOptions;
if (levelOptions?.FirstOrDefault()?.PowerUpDefinition?.TargetAttribute == Stats.MinimumPhysBaseDmg)
{ // The difference betwen the max and min dmg of a weapon must be at least 1
var weaponMinDmg = item.Definition!.BasePowerUpAttributes.First(bpu => bpu.TargetAttribute == Stats.MinimumPhysBaseDmgByWeapon).BaseValue;
var weaponMaxDmg = item.Definition!.BasePowerUpAttributes.First(bpu => bpu.TargetAttribute == Stats.MaximumPhysBaseDmgByWeapon).BaseValue;
var nextMinDmgBoost = levelOptions.FirstOrDefault(o => o.Level == harmonyOption.Level + 1)?.PowerUpDefinition?.Boost?.ConstantValue.Value;
if (nextMinDmgBoost is float boost && weaponMaxDmg - (weaponMinDmg + boost) < 1)
{
return false;
}
}
return base.TryUpgradeItemOption(item);
}
}

View File

@@ -0,0 +1,57 @@
// -----------------------------------------------------------------------
// <copyright file="ShieldPotionConsumeHandlerPlugIn.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.AttributeSystem;
using MUnique.OpenMU.GameLogic.Attributes;
/// <summary>
/// Consume handler for shield potions, which recover the <see cref="Stats.CurrentShield"/>.
/// </summary>
public abstract class ShieldPotionConsumeHandlerPlugIn : RecoverConsumeHandlerPlugIn, IItemConsumeHandlerPlugIn
{
/// <inheritdoc/>
protected override AttributeDefinition MaximumAttribute => Stats.MaximumShield;
/// <inheritdoc/>
protected override AttributeDefinition CurrentAttribute => Stats.CurrentShield;
/// <summary>
/// Gets the recover percentage.
/// </summary>
protected abstract double RecoverPercentage { get; }
/// <inheritdoc />
public sealed override object CreateDefaultConfig()
{
return new RecoverConsumeHandlerConfiguration
{
TotalRecoverPercentage = this.RecoverPercentage,
RecoverDelayReductionByPotionLevel = 1.0 / 16.0,
RecoverPercentageIncreaseByPotionLevel = 1,
CooldownTime = TimeSpan.FromSeconds(0.5),
RecoverSteps =
{
new RecoverStep
{
Delay = TimeSpan.FromMilliseconds(200),
RecoverPercentage = 20,
},
new RecoverStep
{
Delay = TimeSpan.FromMilliseconds(600),
RecoverPercentage = 60,
},
new RecoverStep
{
Delay = TimeSpan.FromMilliseconds(200),
RecoverPercentage = 20,
},
},
};
}
}

View File

@@ -0,0 +1,50 @@
// -----------------------------------------------------------------------
// <copyright file="SiegePotionConsumeHandlerPlugIn.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 System.Runtime.InteropServices;
using MUnique.OpenMU.GameLogic.PlugIns.ChatCommands;
using MUnique.OpenMU.GameLogic.Views;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The alcohol consume handler.
/// </summary>
[Guid("9D50CE95-5354-43A7-8DD5-9D6953700DFA")]
[PlugIn]
[Display(Name = nameof(PlugInResources.SiegePotionConsumeHandlerPlugIn_Name), Description = nameof(PlugInResources.SiegePotionConsumeHandlerPlugIn_Description), ResourceType = typeof(PlugInResources))]
public class SiegePotionConsumeHandlerPlugIn : ApplyMagicEffectConsumeHandlerPlugIn
{
/// <inheritdoc />
public override ItemIdentifier Key => ItemConstants.SiegePotion;
/// <inheritdoc/>
public override async ValueTask<bool> ConsumeItemAsync(Player player, Item item, Item? targetItem, FruitUsage fruitUsage)
{
if (item.Level == 0
&& player.GameContext.Configuration.MagicEffects.FirstOrDefault(e => e.Number == 10) is { } blessEffectDefinition)
{
return await base.ConsumeItemAsyncCore(player, item, targetItem, fruitUsage, blessEffectDefinition).ConfigureAwait(false);
}
if (item.Level == 1
&& player.GameContext.Configuration.MagicEffects.FirstOrDefault(e => e.Number == 11) is { } effectDefinition)
{
if (await base.ConsumeItemAsyncCore(player, item, targetItem, fruitUsage, effectDefinition).ConfigureAwait(false))
{
await player.InvokeViewPlugInAsync<IConsumeSpecialItemPlugIn>(p => p.ConsumeSpecialItemAsync(item, (ushort)(effectDefinition.Duration?.ConstantValue.Value ?? 0))).ConfigureAwait(false);
return true;
}
}
else
{
await player.ShowLocalizedBlueMessageAsync(nameof(PlayerMessage.ItemEffectNotFound)).ConfigureAwait(false);
}
return false;
}
}

View File

@@ -0,0 +1,28 @@
// <copyright file="SmallComplexPotionConsumeHandlerPlugIn.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 System.Runtime.InteropServices;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Consume handler for small complex potions.
/// </summary>
[Guid("9424D511-0AF7-4FC2-BD11-24799368D651")]
[PlugIn]
[Display(Name = nameof(PlugInResources.SmallComplexPotionConsumeHandlerPlugIn_Name), Description = nameof(PlugInResources.SmallComplexPotionConsumeHandlerPlugIn_Description), ResourceType = typeof(PlugInResources))]
public class SmallComplexPotionConsumeHandlerPlugIn : ComplexPotionConsumeHandlerPlugIn
{
/// <summary>
/// Initializes a new instance of the <see cref="SmallComplexPotionConsumeHandlerPlugIn"/> class.
/// </summary>
public SmallComplexPotionConsumeHandlerPlugIn()
: base(new SmallHealthPotionConsumeHandlerPlugIn(), new SmallShieldPotionConsumeHandlerPlugIn())
{
}
/// <inheritdoc />
public override ItemIdentifier Key => ItemConstants.SmallComplexPotion;
}

View File

@@ -0,0 +1,25 @@
// -----------------------------------------------------------------------
// <copyright file="SmallHealthPotionConsumeHandlerPlugIn.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 System.Runtime.InteropServices;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Consume handler for small health potions.
/// </summary>
[Guid("BF28D5A4-D97E-44AA-88CB-D448B1BF7A75")]
[PlugIn]
[Display(Name = nameof(PlugInResources.SmallHealthPotionConsumeHandlerPlugIn_Name), Description = nameof(PlugInResources.SmallHealthPotionConsumeHandlerPlugIn_Description), ResourceType = typeof(PlugInResources))]
public class SmallHealthPotionConsumeHandlerPlugIn : HealthPotionConsumeHandlerPlugIn
{
/// <inheritdoc />
public override ItemIdentifier Key => ItemConstants.SmallHealingPotion;
/// <inheritdoc/>
protected override int Multiplier => 1;
}

View File

@@ -0,0 +1,25 @@
// -----------------------------------------------------------------------
// <copyright file="SmallManaPotionConsumeHandler.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 System.Runtime.InteropServices;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Consume handler for small health potions.
/// </summary>
[Guid("A55849BC-7BD7-4444-A35A-F1AC1D48F179")]
[PlugIn]
[Display(Name = nameof(PlugInResources.SmallManaPotionConsumeHandler_Name), Description = nameof(PlugInResources.SmallManaPotionConsumeHandler_Description), ResourceType = typeof(PlugInResources))]
public class SmallManaPotionConsumeHandler : ManaPotionConsumeHandler
{
/// <inheritdoc />
public override ItemIdentifier Key => ItemConstants.SmallManaPotion;
/// <inheritdoc/>
protected override int Multiplier => 1;
}

View File

@@ -0,0 +1,25 @@
// -----------------------------------------------------------------------
// <copyright file="SmallShieldPotionConsumeHandlerPlugIn.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 System.Runtime.InteropServices;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Consume handler for small shield potions.
/// </summary>
[Guid("C403C8D7-9143-42BC-9894-CA285303E17A")]
[PlugIn]
[Display(Name = nameof(PlugInResources.SmallShieldPotionConsumeHandlerPlugIn_Name), Description = nameof(PlugInResources.SmallShieldPotionConsumeHandlerPlugIn_Description), ResourceType = typeof(PlugInResources))]
public class SmallShieldPotionConsumeHandlerPlugIn : ShieldPotionConsumeHandlerPlugIn
{
/// <inheritdoc />
public override ItemIdentifier Key => ItemConstants.SmallShieldPotion;
/// <inheritdoc />
protected override double RecoverPercentage => 20;
}

View File

@@ -0,0 +1,48 @@
// -----------------------------------------------------------------------
// <copyright file="SoulJewelConsumeHandlerPlugIn.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 System.Runtime.InteropServices;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Consume handler for the Jewel of Soul which increases the item level by one until the level of 9 with a chance of 50%.
/// </summary>
[Guid("A76CDA49-1C56-401A-96D1-294D9A68A7B9")]
[PlugIn]
[Display(Name = nameof(PlugInResources.SoulJewelConsumeHandlerPlugIn_Name), Description = nameof(PlugInResources.SoulJewelConsumeHandlerPlugIn_Description), ResourceType = typeof(PlugInResources))]
public class SoulJewelConsumeHandlerPlugIn : UpgradeItemLevelJewelConsumeHandlerPlugIn<UpgradeItemLevelConfiguration>
{
/// <summary>
/// Initializes a new instance of the <see cref="SoulJewelConsumeHandlerPlugIn"/> class.
/// </summary>
public SoulJewelConsumeHandlerPlugIn()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="SoulJewelConsumeHandlerPlugIn"/> class.
/// </summary>
/// <param name="randomizer">The randomizer.</param>
internal SoulJewelConsumeHandlerPlugIn(IRandomizer randomizer)
: base(randomizer)
{
}
/// <inheritdoc />
public override ItemIdentifier Key => ItemConstants.JewelOfSoul;
/// <inheritdoc />
public override object CreateDefaultConfig() => new UpgradeItemLevelConfiguration
{
MaximumLevel = 8,
MinimumLevel = 0,
SuccessRatePercentage = 50,
SuccessRateBonusWithLuckPercentage = 25,
ResetToLevel0WhenFailMinLevel = 7,
};
}

View File

@@ -0,0 +1,41 @@
// <copyright file="SummoningOrbConsumeHandlerPlugIn.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 System.Runtime.InteropServices;
using MUnique.OpenMU.DataModel.Configuration.Items;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// The summoning orb consume handler.
/// There is only one "Orb" item definition which allows to learn different skills, depending on the item level.
/// This consume handler determines the skill by adding the item level to the skill number
/// of the <see cref="ItemDefinition.Skill"/>.
/// </summary>
[Guid("71C8E542-4868-487E-BC92-0B7CC7CAEC8B")]
[PlugIn]
[Display(Name = nameof(PlugInResources.SummoningOrbConsumeHandlerPlugIn_Name), Description = nameof(PlugInResources.SummoningOrbConsumeHandlerPlugIn_Description), ResourceType = typeof(PlugInResources))]
public class SummoningOrbConsumeHandlerPlugIn : LearnablesConsumeHandlerPlugIn
{
/// <inheritdoc />
public override ItemIdentifier Key => ItemConstants.SummonOrb;
/// <summary>
/// Gets the learnable skill.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="gameConfiguration">The game configuration.</param>
/// <returns>
/// The skill to learn.
/// </returns>
protected override Skill GetLearnableSkill(Item item, GameConfiguration gameConfiguration)
{
item.ThrowNotInitializedProperty(item.Definition?.Skill is null, "Definition.Skill");
var baseSkillNumber = item.Definition.Skill.Number;
var targetSkillNumber = baseSkillNumber + item.Level;
return gameConfiguration.Skills.First(s => s.Number == targetSkillNumber);
}
}

View File

@@ -0,0 +1,44 @@
// -----------------------------------------------------------------------
// <copyright file="TownPortalScrollConsumeHandlerPlugIn.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 System.Runtime.InteropServices;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Consume handler for the town portal scroll.
/// It warps the player to the nearest town.
/// </summary>
/// <remarks>
/// We might need a field for the "nearest" town in the <see cref="GameMapDefinition"/>.
/// <see cref="GameMapDefinition.SafezoneMap"/> might not be suitable.
/// </remarks>
[Guid("825C3110-75F1-4157-A189-15B365B4791E")]
[PlugIn]
[Display(Name = nameof(PlugInResources.TownPortalScrollConsumeHandlerPlugIn_Name), Description = nameof(PlugInResources.TownPortalScrollConsumeHandlerPlugIn_Description), ResourceType = typeof(PlugInResources))]
public class TownPortalScrollConsumeHandlerPlugIn : BaseConsumeHandlerPlugIn
{
/// <inheritdoc />
public override ItemIdentifier Key => ItemConstants.TownPortalScroll;
/// <inheritdoc />
public override async ValueTask<bool> ConsumeItemAsync(Player player, Item item, Item? targetItem, FruitUsage fruitUsage)
{
if (await base.ConsumeItemAsync(player, item, targetItem, fruitUsage).ConfigureAwait(false))
{
var targetMapDef = player.CurrentMap!.Definition.SafezoneMap ?? player.SelectedCharacter!.CharacterClass!.HomeMap;
if (targetMapDef is { }
&& await player.GameContext.GetMapAsync((ushort)targetMapDef.Number).ConfigureAwait(false) is { SafeZoneSpawnGate: { } spawnGate })
{
await player.WarpToAsync(spawnGate).ConfigureAwait(false);
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,64 @@
// -----------------------------------------------------------------------
// <copyright file="UpgradeItemLevelConfiguration.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 System.ComponentModel.DataAnnotations;
using MUnique.OpenMU.DataModel.Configuration.Items;
/// <summary>
/// Configuration for a <see cref="UpgradeItemLevelJewelConsumeHandlerPlugIn{TConfig}"/>.
/// </summary>
public class UpgradeItemLevelConfiguration
{
/// <summary>
/// Gets or sets the success rate percentage.
/// </summary>
[Display(ResourceType = typeof(PlugInResources), Name = nameof(PlugInResources.UpgradeItemLevelConfiguration_SuccessRatePercentage_Name), Description = nameof(PlugInResources.UpgradeItemLevelConfiguration_SuccessRatePercentage_Description))]
public byte SuccessRatePercentage { get; set; }
/// <summary>
/// Gets or sets the success rate bonus with luck percentage.
/// </summary>
[Display(ResourceType = typeof(PlugInResources), Name = nameof(PlugInResources.UpgradeItemLevelConfiguration_SuccessRateBonusWithLuckPercentage_Name), Description = nameof(PlugInResources.UpgradeItemLevelConfiguration_SuccessRateBonusWithLuckPercentage_Description))]
public byte SuccessRateBonusWithLuckPercentage { get; set; }
/// <summary>
/// Gets or sets the minimum item level which the item has to have before applying the jewel.
/// </summary>
[Display(ResourceType = typeof(PlugInResources), Name = nameof(PlugInResources.UpgradeItemLevelConfiguration_MinimumLevel_Name), Description = nameof(PlugInResources.UpgradeItemLevelConfiguration_MinimumLevel_Description))]
public byte MinimumLevel { get; set; }
/// <summary>
/// Gets or sets the maximum item level which the item has to have before applying the jewel.
/// </summary>
[Display(ResourceType = typeof(PlugInResources), Name = nameof(PlugInResources.UpgradeItemLevelConfiguration_MaximumLevel_Name), Description = nameof(PlugInResources.UpgradeItemLevelConfiguration_MaximumLevel_Description))]
public byte MaximumLevel { get; set; }
/// <summary>
/// Gets or sets the amount of levels which the item will be upgraded by.
/// </summary>
[Display(ResourceType = typeof(PlugInResources), Name = nameof(PlugInResources.UpgradeItemLevelConfiguration_LevelAmount_Name), Description = nameof(PlugInResources.UpgradeItemLevelConfiguration_LevelAmount_Description))]
public byte LevelAmount { get; set; } = 1;
/// <summary>
/// Gets or sets the items which are allowed to be upgraded. If empty, all items are allowed except those in <see cref="DisallowedItems"/>.
/// </summary>
[Display(ResourceType = typeof(PlugInResources), Name = nameof(PlugInResources.UpgradeItemLevelConfiguration_AllowedItems_Name), Description = nameof(PlugInResources.UpgradeItemLevelConfiguration_AllowedItems_Description))]
public ICollection<ItemDefinition> AllowedItems { get; set; } = new List<ItemDefinition>();
/// <summary>
/// Gets or sets the items which are not allowed to be upgraded.
/// </summary>
[Display(ResourceType = typeof(PlugInResources), Name = nameof(PlugInResources.UpgradeItemLevelConfiguration_DisallowedItems_Name), Description = nameof(PlugInResources.UpgradeItemLevelConfiguration_DisallowedItems_Description))]
public ICollection<ItemDefinition> DisallowedItems { get; set; } = new List<ItemDefinition>();
/// <summary>
/// Gets or sets the item level after which the item will drop to level 0 when it fails.
/// </summary>
[Display(ResourceType = typeof(PlugInResources), Name = nameof(PlugInResources.UpgradeItemLevelConfiguration_ResetToLevel0WhenFailMinLevel_Name), Description = nameof(PlugInResources.UpgradeItemLevelConfiguration_ResetToLevel0WhenFailMinLevel_Description))]
public byte ResetToLevel0WhenFailMinLevel { get; set; }
}

View File

@@ -0,0 +1,106 @@
// -----------------------------------------------------------------------
// <copyright file="UpgradeItemLevelJewelConsumeHandlerPlugIn.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;
using MUnique.OpenMU.Persistence;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// Base class for consume handlers which upgrade the item level by consuming a jewel.
/// </summary>
/// <typeparam name="TConfig">The type of the configuration.</typeparam>
public abstract class UpgradeItemLevelJewelConsumeHandlerPlugIn<TConfig>
: ItemModifyConsumeHandlerPlugIn, ISupportCustomConfiguration<TConfig>, ISupportDefaultCustomConfiguration
where TConfig : UpgradeItemLevelConfiguration
{
private readonly IRandomizer _randomizer;
/// <summary>
/// Initializes a new instance of the <see cref="UpgradeItemLevelJewelConsumeHandlerPlugIn{TConfig}"/> class.
/// </summary>
protected UpgradeItemLevelJewelConsumeHandlerPlugIn()
: this(Rand.GetRandomizer())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="UpgradeItemLevelJewelConsumeHandlerPlugIn{TConfig}"/> class.
/// </summary>
/// <param name="randomizer">The randomizer.</param>
protected UpgradeItemLevelJewelConsumeHandlerPlugIn(IRandomizer randomizer)
{
this._randomizer = randomizer;
}
/// <inheritdoc/>
public TConfig? Configuration { get; set; }
/// <inheritdoc />
public abstract object CreateDefaultConfig();
/// <inheritdoc/>
protected override bool ModifyItem(Item item, IContext persistenceContext)
{
if (!item.CanLevelBeUpgraded())
{
return false;
}
this.Configuration ??= (TConfig)this.CreateDefaultConfig();
if (item.Level < this.Configuration.MinimumLevel)
{
return false;
}
if (this.Configuration.DisallowedItems.Contains(item.Definition!))
{
return false;
}
if (this.Configuration.AllowedItems.Any() && !this.Configuration.AllowedItems.Contains(item.Definition!))
{
return false;
}
var maximumAllowedLevel = Math.Min(this.Configuration.MaximumLevel + 1, item.Definition!.MaximumItemLevel);
var levelAmount = Math.Min(this.Configuration.LevelAmount, maximumAllowedLevel - item.Level);
if (levelAmount <= 0)
{
return false;
}
int percent = this.Configuration.SuccessRatePercentage;
if (ItemHasLuck(item))
{
percent += this.Configuration.SuccessRateBonusWithLuckPercentage;
}
if (this._randomizer.NextRandomBool(percent))
{
item.Level += (byte)levelAmount;
item.Durability = item.GetMaximumDurabilityOfOnePiece();
return true; // true doesn't mean that it was successful, just that the consumption happened.
}
if (item.Level >= this.Configuration.ResetToLevel0WhenFailMinLevel)
{
item.Level = 0;
}
else
{
item.Level = (byte)Math.Max(item.Level - 1, 0);
}
return true;
}
private static bool ItemHasLuck(Item item)
{
return item.ItemOptions.Any(o => o.ItemOption?.OptionType == ItemOptionTypes.Luck);
}
}