feat(CS-P3): warp registered members to Valley of Loren on siege start + Crown Switch throne capture (NPC 217/218)
Some checks failed
.NET Core / build (push) Has been cancelled
Some checks failed
.NET Core / build (push) Has been cancelled
This commit is contained in:
79
src/GameLogic/CastleSiege/CastleSiegeThroneTalkPlugIn.cs
Normal file
79
src/GameLogic/CastleSiege/CastleSiegeThroneTalkPlugIn.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
// <copyright file="CastleSiegeThroneTalkPlugIn.cs" company="MUnique">
|
||||
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
// </copyright>
|
||||
|
||||
namespace MUnique.OpenMU.GameLogic.CastleSiege;
|
||||
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.GameLogic.NPC;
|
||||
using MUnique.OpenMU.GameLogic.PlugIns;
|
||||
using MUnique.OpenMU.GameLogic.PlugIns.PeriodicTasks;
|
||||
using MUnique.OpenMU.GameLogic.Views;
|
||||
using MUnique.OpenMU.Interfaces;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// Handles talking to the Castle Siege 'Crown Switch' NPCs (217, 218) on Valley of Loren: while the
|
||||
/// siege phase is running, a registered guild member using a switch captures the throne for their guild.
|
||||
/// The guild holding the throne when the siege ends becomes the castle owner (see <see cref="CastleSiegeContext"/>).
|
||||
/// </summary>
|
||||
[Guid("CA5710A0-7A1B-4C2D-8E3F-000000000217")]
|
||||
[PlugIn]
|
||||
[Display(Name = "Castle Siege Crown Switch", Description = "Captures the throne for a registered guild during the siege (Crown Switch NPCs 217/218).")]
|
||||
public class CastleSiegeThroneTalkPlugIn : IPlayerTalkToNpcPlugIn
|
||||
{
|
||||
private static readonly short[] CrownSwitchNumbers = { 217, 218 };
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask PlayerTalksToNpcAsync(Player player, NonPlayerCharacter npc, NpcTalkEventArgs eventArgs)
|
||||
{
|
||||
if (!CrownSwitchNumbers.Contains(npc.Definition.Number))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
eventArgs.HasBeenHandled = true;
|
||||
|
||||
var context = CastleSiegeEventPlugIn.TryGetContext(player.GameContext);
|
||||
if (context is null)
|
||||
{
|
||||
await ShowAsync(player, "Castle Siege is not active on this server.").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.Phase != CastleSiegePhase.Siege)
|
||||
{
|
||||
await ShowAsync(player, "The siege is not running right now.").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (player.GuildStatus is not { } guildStatus)
|
||||
{
|
||||
await ShowAsync(player, "Only members of a registered guild can capture the throne.").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var guildName = guildStatus.GuildId.ToString();
|
||||
if (player.GameContext is IGameServerContext serverContext)
|
||||
{
|
||||
var guild = await serverContext.GuildServer.GetGuildAsync(guildStatus.GuildId).ConfigureAwait(false);
|
||||
if (guild?.Name is { Length: > 0 } name)
|
||||
{
|
||||
guildName = name;
|
||||
}
|
||||
}
|
||||
|
||||
if (!context.RegisteredGuilds.Contains(guildName))
|
||||
{
|
||||
await ShowAsync(player, "Your guild is not registered for this Castle Siege.").ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
context.CaptureThrone(guildName);
|
||||
await ShowAsync(player, $"Your guild '{guildName}' has captured the throne! Hold it until the siege ends to win the castle.").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static ValueTask ShowAsync(Player player, string text)
|
||||
=> player.InvokeViewPlugInAsync<IShowMessagePlugIn>(p => p.ShowMessageAsync(text, MessageType.BlueNormal));
|
||||
}
|
||||
@@ -5,19 +5,24 @@
|
||||
namespace MUnique.OpenMU.GameLogic.PlugIns.PeriodicTasks;
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.GameLogic.CastleSiege;
|
||||
using MUnique.OpenMU.Interfaces;
|
||||
using MUnique.OpenMU.PlugIns;
|
||||
|
||||
/// <summary>
|
||||
/// Drives the Castle Siege phase state machine: ticks it every second and carries its configuration.
|
||||
/// State is per-<see cref="IGameContext"/> and kept in memory (P1: no persistence).
|
||||
/// When the siege phase starts (P3), warps registered guild members to the Valley of Loren battle map.
|
||||
/// </summary>
|
||||
[PlugIn]
|
||||
[Display(Name = nameof(CastleSiegeEventPlugIn), Description = "Castle Siege event (P1 skeleton: phase state machine + scheduling).")]
|
||||
[Display(Name = nameof(CastleSiegeEventPlugIn), Description = "Castle Siege event (phase state machine, scheduling, siege warp).")]
|
||||
[Guid("6E2C8B41-9A4D-4C2E-9E7B-1F2A3B4C5D60")]
|
||||
public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustomConfiguration<CastleSiegeConfiguration>, ISupportDefaultCustomConfiguration
|
||||
{
|
||||
private const ushort ValleyOfLorenMapNumber = 30;
|
||||
|
||||
private static readonly ConcurrentDictionary<IGameContext, CastleSiegeContext> Contexts = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -28,9 +33,6 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom
|
||||
/// </summary>
|
||||
/// <param name="gameContext">The game context.</param>
|
||||
/// <returns>The context, or <c>null</c> if not yet initialized.</returns>
|
||||
/// <remarks>Static so GM chat commands can reach the state machine without a plugin instance
|
||||
/// (<c>GetKnownPlugInsOf</c> returns Types, not instances). The tick runs every second, so the
|
||||
/// context exists within ~1s of server start.</remarks>
|
||||
public static CastleSiegeContext? TryGetContext(IGameContext gameContext)
|
||||
=> Contexts.TryGetValue(gameContext, out var context) ? context : null;
|
||||
|
||||
@@ -40,7 +42,22 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom
|
||||
/// <inheritdoc />
|
||||
public async ValueTask ExecuteTaskAsync(GameContext gameContext)
|
||||
{
|
||||
var context = Contexts.GetOrAdd(gameContext, _ => new CastleSiegeContext(this.Configuration ?? new CastleSiegeConfiguration()));
|
||||
var context = Contexts.GetOrAdd(gameContext, gc =>
|
||||
{
|
||||
var created = new CastleSiegeContext(this.Configuration ?? new CastleSiegeConfiguration());
|
||||
|
||||
// When the siege phase begins, pull all registered guild members onto the battle map.
|
||||
created.PhaseChanged += phase =>
|
||||
{
|
||||
if (phase == CastleSiegePhase.Siege)
|
||||
{
|
||||
_ = WarpRegisteredMembersToSiegeAsync(gc, created);
|
||||
}
|
||||
};
|
||||
|
||||
return created;
|
||||
});
|
||||
|
||||
await context.TickAsync(DateTime.UtcNow).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -53,4 +70,49 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom
|
||||
_ = context.ForceStartRegistrationAsync(DateTime.UtcNow);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task WarpRegisteredMembersToSiegeAsync(IGameContext gameContext, CastleSiegeContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
var map = await gameContext.GetMapAsync(ValleyOfLorenMapNumber).ConfigureAwait(false);
|
||||
if (map?.SafeZoneSpawnGate is not { } gate)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await gameContext.ForEachPlayerAsync(async player =>
|
||||
{
|
||||
var guildName = await GetGuildNameAsync(player).ConfigureAwait(false);
|
||||
if (guildName is not null && context.RegisteredGuilds.Contains(guildName))
|
||||
{
|
||||
await player.WarpToAsync(gate).ConfigureAwait(false);
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
gameContext.LoggerFactory.CreateLogger<CastleSiegeEventPlugIn>()
|
||||
.LogError(ex, "Castle Siege: error while warping registered members to the battle map.");
|
||||
}
|
||||
}
|
||||
|
||||
private static async ValueTask<string?> GetGuildNameAsync(Player player)
|
||||
{
|
||||
if (player.GuildStatus is not { } guildStatus)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (player.GameContext is IGameServerContext serverContext)
|
||||
{
|
||||
var guild = await serverContext.GuildServer.GetGuildAsync(guildStatus.GuildId).ConfigureAwait(false);
|
||||
if (guild?.Name is { Length: > 0 } name)
|
||||
{
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
return guildStatus.GuildId.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user