Skip to content

Commit

Permalink
Merge pull request #540 from nitoygo/dev/summon-party-skill-handler
Browse files Browse the repository at this point in the history
  • Loading branch information
sven-n authored Dec 1, 2024
2 parents 3d5649e + 01fccf8 commit 60003ed
Show file tree
Hide file tree
Showing 10 changed files with 620 additions and 351 deletions.
49 changes: 46 additions & 3 deletions src/GameLogic/Player.cs
Original file line number Diff line number Diff line change
Expand Up @@ -463,9 +463,9 @@ public IPetCommandManager? PetCommandManager
public WeakReference<Player>? LastRequestedPlayerStore { get; set; }

/// <summary>
/// Gets or sets the cancellation token source for the nova skill.
/// Gets or sets the cancellation token source for the targeted skills with channeling.
/// </summary>
public NovaCancellationTokenSource? NovaCancellationTokenSource { get; set; }
public SkillCancellationTokenSource? SkillCancelTokenSource { get; set; }

/// <summary>
/// Gets the mu helper.
Expand Down Expand Up @@ -688,7 +688,7 @@ public async Task TeleportAsync(Point target, Skill teleportSkill)
this.IsTeleporting = true;
try
{
await (this.NovaCancellationTokenSource?.CancelAsync() ?? Task.CompletedTask).ConfigureAwait(false);
await (this.SkillCancelTokenSource?.CancelAsync() ?? Task.CompletedTask).ConfigureAwait(false);

await this._walker.StopAsync().ConfigureAwait(false);

Expand Down Expand Up @@ -723,6 +723,49 @@ public async Task TeleportAsync(Point target, Skill teleportSkill)
this.IsTeleporting = false;
}

/// <summary>
/// Teleports this player to the specified target map and point.
/// </summary>
/// <param name="targetMap">The target map for teleportation.</param>
/// <param name="targetPoint">The target coordinate in the target map.</param>
public async Task TeleportToMapAsync(GameMap targetMap, Point targetPoint)
{
if (!this.IsAlive)
{
return;
}

this.IsTeleporting = true;
try
{
await (this.SkillCancelTokenSource?.CancelAsync() ?? Task.CompletedTask).ConfigureAwait(false);

await this._walker.StopAsync().ConfigureAwait(false);

await this.ForEachWorldObserverAsync<IObjectsOutOfScopePlugIn>(p => p.ObjectsOutOfScopeAsync(this.GetAsEnumerable()), false).ConfigureAwait(false);

if (this.IsAlive)
{
ExitGate tempGate = new()
{
Map = targetMap.Definition,
X1 = targetPoint.X,
X2 = targetPoint.X,
Y1 = targetPoint.Y,
Y2 = targetPoint.Y,
};

await this.WarpToAsync(tempGate).ConfigureAwait(false);
}
}
catch (Exception e)
{
this.Logger.LogWarning(e, "Error during teleport");
}

this.IsTeleporting = false;
}

/// <summary>
/// Is called after the player killed a <see cref="Monster"/>.
/// Adds recovered mana and health to the players attributes.
Expand Down
29 changes: 29 additions & 0 deletions src/GameLogic/PlayerActions/Skills/ForceSkillAction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// <copyright file="ForceSkillAction.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.Skills;

using System.Runtime.InteropServices;
using MUnique.OpenMU.PlugIns;

/// <summary>
/// The Force skill action.
/// </summary>
[PlugIn(nameof(ForceSkillAction), "Handles the force skill of the dark lord.")]
[Guid("552e4e3d-8215-44f4-bee3-b006da049eb2")]
public class ForceSkillAction : TargetedSkillDefaultPlugin
{
private const ushort ForceWaveSkillId = 66;

/// <inheritdoc/>
public override short Key => 60;

/// <inheritdoc/>
public override async ValueTask PerformSkillAsync(Player player, IAttackable target, ushort skillId)
{
// Special handling of force wave skill. The client might send skill id 60,
// even though it's performing force wave.
await base.PerformSkillAsync(player, target, ForceWaveSkillId).ConfigureAwait(false);
}
}
Original file line number Diff line number Diff line change
@@ -1,72 +1,64 @@
// <copyright file="NovaSkillAction.cs" company="MUnique">
// <copyright file="NovaSkillStartPlugin.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.Skills;

using System.Runtime.InteropServices;
using System.Threading;
using MUnique.OpenMU.AttributeSystem;
using MUnique.OpenMU.GameLogic.Attributes;
using MUnique.OpenMU.GameLogic.NPC;
using MUnique.OpenMU.GameLogic.Views.World;
using MUnique.OpenMU.PlugIns;

/// <summary>
/// The nova skill action.
/// The start nova skill action.
/// </summary>
public class NovaSkillAction
[PlugIn(nameof(NovaSkillStartPlugin), "Handles the start of nova skill of the wizard class.")]
[Guid("e966e7eb-58b8-4356-8725-5da9f43c1fa4")]
public class NovaSkillStartPlugin : TargetedSkillPluginBase
{
private const byte NovaSkillId = 40;
private const byte NovaStartId = 58;
private static readonly TimeSpan NovaStepDelay = TimeSpan.FromMilliseconds(500);

/// <summary>
/// The nova damage per stage, which is still hardcoded. May be configurable later.
/// </summary>
private static readonly int[] NovaDamageTable = { 0, 20, 50, 99, 160, 225, 325, 425, 550, 700, 880, 1090, 1320 };

/// <summary>
/// Starts the nova skill.
/// </summary>
/// <param name="player">The player which performs the skill.</param>
public async ValueTask StartNovaSkillAsync(Player player)
/// <inheritdoc/>
public override short Key => 58;

/// <inheritdoc />
public override async ValueTask PerformSkillAsync(Player player, IAttackable target, ushort skillId)
{
if (player.NovaCancellationTokenSource is not null)
if (player.SkillCancelTokenSource is not null)
{
// A nova is already ongoing.
return;
}

var skillEntry = player.SkillList?.GetSkill(NovaSkillId);
var skillEntry = player.SkillList?.GetSkill(skillId);
if (skillEntry?.Skill is null)
{
return;
}

// Consume full ability points first...
var novaStart = player.GameContext.Configuration.Skills.First(s => s.Number == NovaStartId);
var novaStart = player.GameContext.Configuration.Skills.First(s => s.Number == skillId);
if (!await player.TryConsumeForSkillAsync(novaStart).ConfigureAwait(false))
{
return;
}

await player.ForEachWorldObserverAsync<IShowSkillAnimationPlugIn>(p => p.ShowNovaStartAsync(player), true).ConfigureAwait(false);
var cancellationTokenSource = new NovaCancellationTokenSource();
player.NovaCancellationTokenSource = cancellationTokenSource;
var cancellationTokenSource = new SkillCancellationTokenSource();
player.SkillCancelTokenSource = cancellationTokenSource;

_ = this.RunNovaAsync(player, skillEntry, cancellationTokenSource);
}

/// <summary>
/// Stops the nova skill.
/// </summary>
/// <param name="player">The player which performed the skill.</param>
/// <param name="extraTargetId">The extra target identifier.</param>
public async ValueTask StopNovaSkillAsync(Player player, ushort extraTargetId)
{
player.NovaCancellationTokenSource?.CancelWithExtraTarget(extraTargetId);
}

private async ValueTask RunNovaAsync(Player player, SkillEntry skillEntry, NovaCancellationTokenSource cancellationTokenSource)
private async ValueTask RunNovaAsync(Player player, SkillEntry skillEntry, SkillCancellationTokenSource cancellationTokenSource)
{
var cancellationToken = cancellationTokenSource.Token;
if (player.Attributes is not { } playerAttributes
Expand Down Expand Up @@ -95,7 +87,7 @@ private async ValueTask RunNovaAsync(Player player, SkillEntry skillEntry, NovaC
completedSteps++;
stepDamageElement.Value = NovaDamageTable[completedSteps];
var steps = completedSteps;
await player.ForEachWorldObserverAsync<IShowSkillStageUpdatePlugIn>(p => p.UpdateSkillStageAsync(player, NovaSkillId, steps), true).ConfigureAwait(false);
await player.ForEachWorldObserverAsync<IShowSkillStageUpdatePlugIn>(p => p.UpdateSkillStageAsync(player, this.Key, steps), true).ConfigureAwait(false);
await Task.Delay(NovaStepDelay, cancellationToken).ConfigureAwait(false); // Hint: Player could cancel the nova 500 ms before end without damage loss - if he is good
}
}
Expand All @@ -109,16 +101,16 @@ private async ValueTask RunNovaAsync(Player player, SkillEntry skillEntry, NovaC
catch (Exception ex)
{
player.Logger.LogError(ex, "Unexpected error during performing nova skill");
player.NovaCancellationTokenSource?.Dispose();
player.NovaCancellationTokenSource = null;
player.SkillCancelTokenSource?.Dispose();
player.SkillCancelTokenSource = null;
}
finally
{
player.Attributes?.RemoveElement(stepDamageElement, Stats.NovaStageDamage);
}
}

private async ValueTask AttackTargetsAsync(Player player, SkillEntry skillEntry, NovaCancellationTokenSource cancellationTokenSource)
private async ValueTask AttackTargetsAsync(Player player, SkillEntry skillEntry, SkillCancellationTokenSource cancellationTokenSource)
{
if (!player.IsAlive || player.IsAtSafezone() || skillEntry.Skill is not { } skill)
{
Expand All @@ -130,8 +122,8 @@ private async ValueTask AttackTargetsAsync(Player player, SkillEntry skillEntry,
var targets = this.DetermineTargets(player, skill, explicitTargetId);

// Set cancellation token source to null, so that the next nova can be started.
player.NovaCancellationTokenSource?.Dispose();
player.NovaCancellationTokenSource = null;
player.SkillCancelTokenSource?.Dispose();
player.SkillCancelTokenSource = null;

await Task.Delay(500, CancellationToken.None).ConfigureAwait(false);

Expand Down
25 changes: 25 additions & 0 deletions src/GameLogic/PlayerActions/Skills/NovaSkillStopPlugin.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// <copyright file="NovaSkillStopPlugin.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.Skills;

using System.Runtime.InteropServices;
using MUnique.OpenMU.PlugIns;

/// <summary>
/// The stop nova skill action.
/// </summary>
[PlugIn(nameof(NovaSkillStopPlugin), "Handles the stopping of nova skill of the wizard class.")]
[Guid("3cb98892-b3ce-42de-8956-5ed5625c6285")]
public class NovaSkillStopPlugin : TargetedSkillPluginBase
{
/// <inheritdoc/>
public override short Key => 40;

/// <inheritdoc />
public override async ValueTask PerformSkillAsync(Player player, IAttackable target, ushort skillId)
{
player.SkillCancelTokenSource?.CancelWithExtraTarget(target.Id);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// <copyright file="NovaCancellationTokenSource.cs" company="MUnique">
// <copyright file="SkillCancelTokenSource.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>

Expand All @@ -10,7 +10,7 @@ namespace MUnique.OpenMU.GameLogic.PlayerActions.Skills;
/// A <see cref="CancellationTokenSource"/> which allows to specify an explicit target when cancelling the nova skill.
/// </summary>
/// <seealso cref="System.Threading.CancellationTokenSource" />
public class NovaCancellationTokenSource : CancellationTokenSource
public class SkillCancellationTokenSource : CancellationTokenSource
{
/// <summary>
/// Gets the explicit target identifier.
Expand Down
Loading

0 comments on commit 60003ed

Please sign in to comment.