baseline: OpenMU upstream b5a0961 (fresh source)
This commit is contained in:
60
src/GameLogic/Pet/AttackerSurrogate.cs
Normal file
60
src/GameLogic/Pet/AttackerSurrogate.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
// <copyright file="AttackerSurrogate.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameLogic.Pet;
|
||||
|
||||
using MUnique.OpenMU.AttributeSystem;
|
||||
using MUnique.OpenMU.GameLogic.Views;
|
||||
using MUnique.OpenMU.Pathfinding;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// A surrogate for an <see cref="Player"/> which exposes the attack attributes
|
||||
/// of another <see cref="IAttributeSystem"/>.
|
||||
/// </summary>
|
||||
public class AttackerSurrogate : IAttacker, IWorldObserver, IPlayerSurrogate
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AttackerSurrogate" /> class.
|
||||
/// </summary>
|
||||
/// <param name="owner">The owner.</param>
|
||||
/// <param name="attributeSystem">The attribute system.</param>
|
||||
public AttackerSurrogate(Player owner, IAttributeSystem attributeSystem)
|
||||
{
|
||||
this.Owner = owner;
|
||||
this.Attributes = attributeSystem;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Player Owner { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>
|
||||
/// Pets don't use "skills", so it doesn't make sense to pass the
|
||||
/// combo state of the player here.
|
||||
/// </remarks>
|
||||
public ComboStateMachine? ComboState => null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ushort Id => this.Owner.Id;
|
||||
|
||||
/// <inheritdoc />
|
||||
public GameMap? CurrentMap => this.Owner.CurrentMap;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Point Position
|
||||
{
|
||||
get => this.Owner.Position;
|
||||
set => this.Owner.Position = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IAttributeSystem Attributes { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public ILogger Logger => this.Owner.Logger;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICustomPlugInContainer<IViewPlugIn> ViewPlugIns => this.Owner.ViewPlugIns;
|
||||
}
|
||||
20
src/GameLogic/Pet/IPetCommandManager.cs
Normal file
20
src/GameLogic/Pet/IPetCommandManager.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
// <copyright file="IPetCommandManager.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameLogic.Pet;
|
||||
|
||||
using MUnique.OpenMU.GameLogic.PlayerActions.Items;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for a pet command manager.
|
||||
/// </summary>
|
||||
public interface IPetCommandManager : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the behaviour of the pet.
|
||||
/// </summary>
|
||||
/// <param name="newBehaviour">The new behaviour.</param>
|
||||
/// <param name="target">The target.</param>
|
||||
ValueTask SetBehaviourAsync(PetBehaviour newBehaviour, IAttackable? target);
|
||||
}
|
||||
57
src/GameLogic/Pet/PetLevelHelper.cs
Normal file
57
src/GameLogic/Pet/PetLevelHelper.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
// <copyright file="PetLevelHelper.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameLogic.Pet;
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using MUnique.OpenMU.DataModel.Configuration.Items;
|
||||
using org.mariuszgromada.math.mxparser;
|
||||
|
||||
/// <summary>
|
||||
/// Helper class for pet related stuff.
|
||||
/// </summary>
|
||||
public static class PetLevelHelper
|
||||
{
|
||||
private static readonly ConcurrentDictionary<(string, byte), uint[]> ValueResultCache = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the experience of level.
|
||||
/// </summary>
|
||||
/// <param name="petDefinition">The pet definition.</param>
|
||||
/// <param name="petLevel">The pet level.</param>
|
||||
/// <param name="maximumLevel">The maximum level.</param>
|
||||
/// <returns>The calculated experience of the specified pet level.</returns>
|
||||
public static uint GetExperienceOfPetLevel(this ItemDefinition petDefinition, byte petLevel, byte maximumLevel)
|
||||
{
|
||||
if (petLevel <= 0 || petLevel > maximumLevel)
|
||||
{
|
||||
return uint.MaxValue;
|
||||
}
|
||||
|
||||
var formula = petDefinition.PetExperienceFormula;
|
||||
if (formula is null)
|
||||
{
|
||||
return uint.MaxValue;
|
||||
}
|
||||
|
||||
if (ValueResultCache.TryGetValue((formula, maximumLevel), out var results))
|
||||
{
|
||||
return results[petLevel - 1];
|
||||
}
|
||||
|
||||
results = new uint[maximumLevel];
|
||||
var argument = new Argument("level", 0);
|
||||
var expression = new Expression(formula);
|
||||
expression.addArguments(argument);
|
||||
for (int i = 0; i < maximumLevel; i++)
|
||||
{
|
||||
argument.setArgumentValue(i + 1);
|
||||
results[i] = (uint)expression.calculate();
|
||||
}
|
||||
|
||||
ValueResultCache.TryAdd((formula, maximumLevel), results);
|
||||
|
||||
return results[petLevel - 1];
|
||||
}
|
||||
}
|
||||
98
src/GameLogic/Pet/RavenAttributeSystem.cs
Normal file
98
src/GameLogic/Pet/RavenAttributeSystem.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
// <copyright file="RavenAttributeSystem.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameLogic.Pet;
|
||||
|
||||
using MUnique.OpenMU.AttributeSystem;
|
||||
using MUnique.OpenMU.GameLogic.Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// An <see cref="IAttributeSystem"/> which provides the attack attributes of the raven.
|
||||
/// </summary>
|
||||
public class RavenAttributeSystem : IAttributeSystem
|
||||
{
|
||||
private static readonly Dictionary<AttributeDefinition, Func<Player, float>> StatMapping =
|
||||
new()
|
||||
{
|
||||
{
|
||||
Stats.MinimumPhysBaseDmg, player =>
|
||||
{
|
||||
var minDamage = player.Attributes?[Stats.RavenMinimumDamage] ?? 0;
|
||||
minDamage += minDamage * (player.Attributes?[Stats.RavenAttackDamageIncrease] ?? 1);
|
||||
return minDamage;
|
||||
}
|
||||
},
|
||||
{
|
||||
Stats.MaximumPhysBaseDmg, player =>
|
||||
{
|
||||
var maxDamage = player.Attributes?[Stats.RavenMaximumDamage] ?? 0;
|
||||
maxDamage += maxDamage * (player.Attributes?[Stats.RavenAttackDamageIncrease] ?? 1);
|
||||
return maxDamage;
|
||||
}
|
||||
},
|
||||
{ Stats.RavenBonusDamage, player => player.Attributes?[Stats.RavenBonusDamage] ?? 0 },
|
||||
{ Stats.AttackSpeed, player => player.Attributes?[Stats.RavenAttackSpeed] ?? 0 },
|
||||
{ Stats.AttackRatePvm, player => player.Attributes?[Stats.RavenAttackRate] ?? 0 },
|
||||
{ Stats.AttackRatePvp, player => player.Attributes?[Stats.AttackRatePvp] ?? 0 },
|
||||
{ Stats.CriticalDamageChance, player => player.Attributes?[Stats.RavenCriticalDamageChance] ?? 0 },
|
||||
{ Stats.ExcellentDamageChance, player => player.Attributes?[Stats.RavenExcDamageChance] ?? 0 },
|
||||
{ Stats.DefenseIgnoreChance, player => player.Attributes?[Stats.DefenseIgnoreChance] ?? 0 },
|
||||
{ Stats.ShieldBypassChance, player => player.Attributes?[Stats.ShieldBypassChance] ?? 0 },
|
||||
{ Stats.ShieldDecreaseRateIncrease, player => player.Attributes?[Stats.ShieldDecreaseRateIncrease] ?? 0 },
|
||||
{ Stats.AttackDamageIncrease, player => 1.0f },
|
||||
};
|
||||
|
||||
private readonly Player _owner;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RavenAttributeSystem"/> class.
|
||||
/// </summary>
|
||||
/// <param name="owner">The owner of the pet.</param>
|
||||
public RavenAttributeSystem(Player owner)
|
||||
{
|
||||
this._owner = owner;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public float this[AttributeDefinition key]
|
||||
{
|
||||
get => this.GetValueOfAttribute(key);
|
||||
set => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public float GetValueOfAttribute(AttributeDefinition attributeDefinition)
|
||||
{
|
||||
if (StatMapping.TryGetValue(attributeDefinition, out var mappingFunction))
|
||||
{
|
||||
return mappingFunction(this._owner);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AddElement(IElement element, AttributeDefinition targetAttribute)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RemoveElement(IElement element, AttributeDefinition targetAttribute)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AddAttributeRelationship(AttributeRelationship relationship, IAttributeSystem sourceAttributeHolder, AggregateType aggregateType)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IElement GetOrCreateAttribute(AttributeDefinition attributeDefinition)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
300
src/GameLogic/Pet/RavenCommandManager.cs
Normal file
300
src/GameLogic/Pet/RavenCommandManager.cs
Normal file
@@ -0,0 +1,300 @@
|
||||
// <copyright file="RavenCommandManager.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameLogic.Pet;
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using MUnique.OpenMU.GameLogic.Attributes;
|
||||
using MUnique.OpenMU.GameLogic.NPC;
|
||||
using MUnique.OpenMU.GameLogic.PlayerActions.Items;
|
||||
using MUnique.OpenMU.GameLogic.Views.Pet;
|
||||
using Nito.AsyncEx;
|
||||
|
||||
/// <summary>
|
||||
/// Implementation of the <see cref="IPetCommandManager"/> for the dark raven pet.
|
||||
/// </summary>
|
||||
public class RavenCommandManager : Disposable, IPetCommandManager
|
||||
{
|
||||
private const int AttackRange = 7;
|
||||
private const int MaxRangeHits = 3;
|
||||
|
||||
private readonly Player _owner;
|
||||
private readonly Item _pet;
|
||||
private readonly IAttacker _petAttackerSurrogate;
|
||||
private readonly List<IAttackable> _targetBuffer = new(MaxRangeHits);
|
||||
private readonly AsyncLock _rangeAttackLock = new();
|
||||
|
||||
private CancellationTokenSource? _attackCts;
|
||||
private PetBehaviour _currentBehaviour;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RavenCommandManager" /> class.
|
||||
/// </summary>
|
||||
/// <param name="owner">The owner of the pet.</param>
|
||||
/// <param name="pet">The pet.</param>
|
||||
public RavenCommandManager(Player owner, Item pet)
|
||||
{
|
||||
this._owner = owner;
|
||||
this._pet = pet;
|
||||
this._petAttackerSurrogate = new AttackerSurrogate(owner, new RavenAttributeSystem(owner));
|
||||
}
|
||||
|
||||
private TimeSpan AttackDelay => TimeSpan.FromMilliseconds(Math.Max(100, 1500 - (this._petAttackerSurrogate.Attributes[Stats.AttackSpeed] * 10)));
|
||||
|
||||
/// <summary>
|
||||
/// Sets the behaviour.
|
||||
/// </summary>
|
||||
/// <param name="newBehaviour">The new behaviour.</param>
|
||||
/// <param name="target">The target.</param>
|
||||
public async ValueTask SetBehaviourAsync(PetBehaviour newBehaviour, IAttackable? target)
|
||||
{
|
||||
await (this._attackCts?.CancelAsync() ?? Task.CompletedTask).ConfigureAwait(false);
|
||||
this._attackCts?.Dispose();
|
||||
this._attackCts = null;
|
||||
|
||||
if (this._pet.Durability == 0.0)
|
||||
{
|
||||
this._currentBehaviour = PetBehaviour.Idle;
|
||||
}
|
||||
|
||||
this._currentBehaviour = newBehaviour;
|
||||
|
||||
await this._owner.InvokeViewPlugInAsync<IPetBehaviourChangedViewPlugIn>(p => p.PetBehaviourChangedAsync(this._pet, this._currentBehaviour, target)).ConfigureAwait(false);
|
||||
if (newBehaviour == PetBehaviour.Idle)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this._attackCts = new();
|
||||
switch (newBehaviour)
|
||||
{
|
||||
case PetBehaviour.AttackRandom:
|
||||
_ = this.AttackRandomAsync(this._attackCts.Token);
|
||||
break;
|
||||
case PetBehaviour.AttackTarget when target is { }:
|
||||
_ = this.AttackTargetUntilDeathAsync(target, this._attackCts.Token);
|
||||
break;
|
||||
case PetBehaviour.AttackWithOwner:
|
||||
_ = this.AttackSameAsOwnerAsync(this._attackCts.Token);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(newBehaviour), $"Unknown behavior: {newBehaviour}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
this._attackCts?.Cancel();
|
||||
this._attackCts?.Dispose();
|
||||
this._attackCts = null;
|
||||
}
|
||||
|
||||
private async ValueTask AttackAsync(IAttackable target, CancellationToken cancellationToken)
|
||||
{
|
||||
if (this._owner.Logger.IsEnabled(LogLevel.Debug))
|
||||
{
|
||||
this._owner.Logger.LogDebug($"Pet attacks target: {target}");
|
||||
}
|
||||
|
||||
if (this._owner.IsAtSafezone())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var attackType = Rand.NextRandomBool(0.3) ? PetAttackType.RangeAttack : PetAttackType.SingleTarget;
|
||||
|
||||
await this._owner.ForEachWorldObserverAsync<IPetAttackViewPlugIn>(
|
||||
p => p.ShowPetAttackAnimationAsync(this._owner, this._pet, target, attackType),
|
||||
true)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (attackType == PetAttackType.SingleTarget)
|
||||
{
|
||||
await target.AttackByAsync(this._petAttackerSurrogate, null, false).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = this.RangeAttackAsync(target, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does a range attack on the main target and other targets in the same area, with up to 3 hits.
|
||||
/// </summary>
|
||||
/// <param name="mainTarget">The main target which was selected to be attacked.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
private async ValueTask RangeAttackAsync(IAttackable mainTarget, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var l = await this._rangeAttackLock.LockAsync(cancellationToken);
|
||||
var delay = this.AttackDelay / 4;
|
||||
this._targetBuffer.Clear();
|
||||
this._targetBuffer.Add(mainTarget);
|
||||
var targets = mainTarget
|
||||
.CurrentMap!
|
||||
.GetAttackablesInRange(mainTarget.Position, 3)
|
||||
.OfType<AttackableNpcBase>()
|
||||
.Where(t => t != mainTarget)
|
||||
.Where(this.IsValidTarget)
|
||||
.Take(2);
|
||||
this._targetBuffer.AddRange(targets);
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
var target = this._targetBuffer[i % this._targetBuffer.Count];
|
||||
await this._owner.ForEachWorldObserverAsync<IPetAttackViewPlugIn>(
|
||||
p => p.ShowPetAttackAnimationAsync(this._owner, this._pet, target, PetAttackType.RangeAttack),
|
||||
true)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
await target.AttackByAsync(this._petAttackerSurrogate, null, false).ConfigureAwait(false);
|
||||
await Task.Delay(delay, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// We can ignore that.
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this._owner.Logger.LogError(ex, "Unexpected error in range attack of the pet.");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task AttackTargetUntilDeathAsync(IAttackable target, CancellationToken cancellationToken)
|
||||
{
|
||||
this._owner.Logger.LogDebug($"Starting to attack {target} with the pet, until it's dead.");
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (!target.IsAlive)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (!this._owner.IsAtSafezone() && this.IsValidTarget(target))
|
||||
{
|
||||
await this.AttackAsync(target, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await Task.Delay(this.AttackDelay, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
this._currentBehaviour = PetBehaviour.Idle;
|
||||
await this._owner.InvokeViewPlugInAsync<IPetBehaviourChangedViewPlugIn>(p => p.PetBehaviourChangedAsync(this._pet, this._currentBehaviour, null)).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
this._owner.Logger.LogDebug("Cancelled attack with the pet.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this._owner.Logger.LogError(ex, "Unexpected error in attack loop of the pet.");
|
||||
}
|
||||
|
||||
this._owner.Logger.LogDebug("Ending attack with the pet.");
|
||||
}
|
||||
|
||||
private async Task AttackRandomAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
this._owner.Logger.LogDebug("Starting random attack with the pet.");
|
||||
try
|
||||
{
|
||||
IAttackable? currentTarget = null;
|
||||
while (true)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (!this._owner.IsAtSafezone())
|
||||
{
|
||||
if (!this.IsValidTarget(currentTarget))
|
||||
{
|
||||
var attackablesInRange = this._owner.CurrentMap?
|
||||
.GetAttackablesInRange(this._owner.Position, AttackRange)
|
||||
.OfType<AttackableNpcBase>()
|
||||
.Where(this.IsValidTarget);
|
||||
currentTarget = attackablesInRange?.SelectRandom();
|
||||
if (this._owner.Logger.IsEnabled(LogLevel.Debug))
|
||||
{
|
||||
this._owner.Logger.LogDebug($"Pet selected target: {currentTarget}");
|
||||
}
|
||||
}
|
||||
|
||||
if (currentTarget is { IsAlive: true })
|
||||
{
|
||||
await this.AttackAsync(currentTarget, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Delay(this.AttackDelay, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
this._owner.Logger.LogDebug("Cancelled random attack with the pet.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this._owner.Logger.LogError(ex, "Unexpected error in random attack loop of the pet.");
|
||||
}
|
||||
|
||||
this._owner.Logger.LogDebug("Ending random attack with the pet.");
|
||||
}
|
||||
|
||||
private async Task AttackSameAsOwnerAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
this._owner.Logger.LogDebug("Starting attack with the pet.");
|
||||
try
|
||||
{
|
||||
IAttackable? currentTarget = null;
|
||||
while (true)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (this._owner.LastAttackedTarget.TryGetTarget(out var lastTarget)
|
||||
&& lastTarget != currentTarget)
|
||||
{
|
||||
currentTarget = lastTarget;
|
||||
if (this._owner.Logger.IsEnabled(LogLevel.Debug))
|
||||
{
|
||||
this._owner.Logger.LogDebug($"Pet selected last target of player: {currentTarget}");
|
||||
}
|
||||
}
|
||||
|
||||
if (this.IsValidTarget(currentTarget))
|
||||
{
|
||||
await this.AttackAsync(currentTarget, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await Task.Delay(this.AttackDelay, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
this._owner.Logger.LogDebug("Cancelled attack with the pet.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this._owner.Logger.LogError(ex, "Unexpected error in attack loop of the pet.");
|
||||
}
|
||||
|
||||
this._owner.Logger.LogDebug("Ending attack with the pet.");
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool IsValidTarget([NotNullWhen(true)] IAttackable? target)
|
||||
{
|
||||
return target is not null
|
||||
&& target is not Monster { Definition.ObjectKind: NpcObjectKind.Guard }
|
||||
&& target.IsActive()
|
||||
&& target.IsInRange(this._owner.Position, AttackRange);
|
||||
}
|
||||
}
|
||||
44
src/GameLogic/Pet/UpdatePetCommandManagerOnItemMovePlugIn.cs
Normal file
44
src/GameLogic/Pet/UpdatePetCommandManagerOnItemMovePlugIn.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
// <copyright file="UpdatePetCommandManagerOnItemMovePlugIn.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameLogic.Pet;
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.GameLogic.PlayerActions.Items;
|
||||
using MUnique.OpenMU.GameLogic.PlugIns;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// Updates the pet command manager on the player object after a pet has been equipped or unequipped.
|
||||
/// </summary>
|
||||
[Guid("E25CBAE0-CFD2-4BAF-9178-4FBD90F26E4B")]
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(PlugInResources.UpdatePetCommandManagerOnItemMovePlugIn_Name), Description = nameof(PlugInResources.UpdatePetCommandManagerOnItemMovePlugIn_Description), ResourceType = typeof(PlugInResources))]
|
||||
public class UpdatePetCommandManagerOnItemMovePlugIn : IItemMovedPlugIn
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async ValueTask ItemMovedAsync(Player player, Item item)
|
||||
{
|
||||
if (player.Inventory is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var isAttackPet = item.IsTrainablePet() && !item.IsDefensiveItem();
|
||||
if (!isAttackPet)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var itemAdded = player.Inventory.EquippedItems.Contains(item);
|
||||
if (itemAdded && player.PetCommandManager is { } petCommandManager)
|
||||
{
|
||||
await petCommandManager.SetBehaviourAsync(PetBehaviour.Idle, null).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
player.RemovePetCommandManager();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user