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,154 @@
// <copyright file="HealingHandler.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.GameLogic.Offline;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.DataModel.Configuration.Items;
using MUnique.OpenMU.DataModel.Entities;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.MuHelper;
using MUnique.OpenMU.GameLogic.PlayerActions.ItemConsumeActions;
using MUnique.OpenMU.GameLogic.Views.World;
using MUnique.OpenMU.Interfaces;
/// <summary>
/// Handles health recovery for the offline player and their party.
/// </summary>
public sealed class HealingHandler
{
private static readonly ItemConsumeAction ConsumeAction = new();
private static readonly ItemIdentifier[] HealthPotionPriority =
[
ItemConstants.LargeHealingPotion,
ItemConstants.MediumHealingPotion,
ItemConstants.SmallHealingPotion,
ItemConstants.Apple,
];
private readonly OfflinePlayer _player;
private readonly IMuHelperSettings? _config;
/// <summary>
/// Initializes a new instance of the <see cref="HealingHandler"/> class.
/// </summary>
/// <param name="player">The offline player.</param>
/// <param name="config">The MU helper settings.</param>
public HealingHandler(OfflinePlayer player, IMuHelperSettings? config)
{
this._player = player;
this._config = config;
}
/// <summary>
/// Performs health recovery actions for the player and their party.
/// </summary>
public async ValueTask PerformHealthRecoveryAsync()
{
if (this._config is null || this._player.Attributes is null)
{
return;
}
await this.PerformSelfHealingAsync().ConfigureAwait(false);
await this.PerformPartyHealingAsync().ConfigureAwait(false);
}
private async ValueTask PerformSelfHealingAsync()
{
if (this.IsHealthBelowThreshold(this._player, this._config!.HealThresholdPercent))
{
var healSkill = this.FindSkillByType(SkillType.Regeneration);
if (healSkill is not null && this._config.AutoHeal)
{
await this._player.ForEachWorldObserverAsync<IShowSkillAnimationPlugIn>(
p => p.ShowSkillAnimationAsync(this._player, this._player, healSkill.Skill!, true),
includeThis: true).ConfigureAwait(false);
await this._player.ApplyRegenerationAsync(this._player, healSkill).ConfigureAwait(false);
return;
}
}
if (this._config!.UseHealPotion && this.IsHealthBelowThreshold(this._player, this._config.PotionThresholdPercent))
{
await this.UseHealthPotionAsync().ConfigureAwait(false);
}
}
private async ValueTask PerformPartyHealingAsync()
{
if (!this._config!.AutoHealParty || this._player.Party is not { } party)
{
return;
}
var healSkill = this.FindSkillByType(SkillType.Regeneration);
if (healSkill is null)
{
return;
}
foreach (var member in party.PartyList.OfType<Player>())
{
if (member == this._player)
{
continue;
}
if (!member.IsActive() || !this._player.IsInRange(member, this._config!.HuntingRange))
{
continue;
}
if (this.IsHealthBelowThreshold(member, this._config.HealPartyThresholdPercent))
{
await this._player.ForEachWorldObserverAsync<IShowSkillAnimationPlugIn>(
p => p.ShowSkillAnimationAsync(this._player, member, healSkill.Skill!, true),
includeThis: true).ConfigureAwait(false);
await member.ApplyRegenerationAsync(this._player, healSkill).ConfigureAwait(false);
}
}
}
private bool IsHealthBelowThreshold(IAttackable target, int thresholdPercent)
{
if (target.Attributes is not { } attributes)
{
return false;
}
double hp = attributes[Stats.CurrentHealth];
double maxHp = attributes[Stats.MaximumHealth];
return maxHp > 0 && (hp * 100.0 / maxHp) <= thresholdPercent;
}
private async ValueTask UseHealthPotionAsync()
{
if (this._player.Inventory is null)
{
return;
}
foreach (var identifier in HealthPotionPriority)
{
var potion = this._player.Inventory.Items
.FirstOrDefault(i => i.Definition?.Group == identifier.Group
&& i.Definition.Number == identifier.Number);
if (potion is not null)
{
await ConsumeAction.HandleConsumeRequestAsync(
this._player, potion.ItemSlot, potion.ItemSlot, FruitUsage.Undefined).ConfigureAwait(false);
return;
}
}
}
private SkillEntry? FindSkillByType(SkillType type)
{
return this._player.SkillList?.Skills.FirstOrDefault(s => s.Skill?.SkillType == type);
}
}