feat(CS-P3): full siege objective chain - guardian statues (Destructible spawn) + dual Crown Switch hold + throne capture gating
This commit is contained in:
@@ -11,8 +11,10 @@ namespace MUnique.OpenMU.GameLogic.CastleSiege;
|
||||
public class CastleSiegeContext
|
||||
{
|
||||
private readonly List<string> _registeredGuilds = new();
|
||||
private readonly Dictionary<short, string> _switchHolders = new();
|
||||
private DateTime _phaseStartedUtc;
|
||||
private string? _occupier;
|
||||
private int _statuesRemaining;
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="CastleSiegeContext"/> class.</summary>
|
||||
/// <param name="configuration">The cycle timing configuration.</param>
|
||||
@@ -95,6 +97,8 @@ public class CastleSiegeContext
|
||||
public ValueTask ForceStartRegistrationAsync(DateTime now)
|
||||
{
|
||||
this._registeredGuilds.Clear();
|
||||
this._switchHolders.Clear();
|
||||
this._statuesRemaining = 0;
|
||||
this._occupier = null;
|
||||
return this.TransitionAsync(CastleSiegePhase.Registration, now);
|
||||
}
|
||||
@@ -110,6 +114,8 @@ public class CastleSiegeContext
|
||||
public ValueTask ResetAsync(DateTime now)
|
||||
{
|
||||
this._registeredGuilds.Clear();
|
||||
this._switchHolders.Clear();
|
||||
this._statuesRemaining = 0;
|
||||
this._occupier = null;
|
||||
return this.TransitionAsync(CastleSiegePhase.Ownership, now);
|
||||
}
|
||||
@@ -129,16 +135,65 @@ public class CastleSiegeContext
|
||||
/// <param name="guildName">The owner guild name, or null to clear.</param>
|
||||
public void SetOwner(string? guildName) => this.OwnerGuildName = guildName;
|
||||
|
||||
/// <summary>P3: a registered guild captures the throne during the siege. No-op outside the siege phase.</summary>
|
||||
/// <param name="guildName">The capturing guild's name.</param>
|
||||
public void CaptureThrone(string guildName)
|
||||
/// <summary>Gets the number of guardian statues still standing (must be 0 before the throne can be taken).</summary>
|
||||
public int StatuesRemaining => this._statuesRemaining;
|
||||
|
||||
/// <summary>Sets how many guardian statues defend the castle this siege (called when they are spawned).</summary>
|
||||
/// <param name="count">The statue count.</param>
|
||||
public void SetStatueCount(int count) => this._statuesRemaining = count > 0 ? count : 0;
|
||||
|
||||
/// <summary>Notifies that a guardian statue was destroyed. Lowers the remaining count (min 0).</summary>
|
||||
public void NotifyStatueDestroyed()
|
||||
{
|
||||
if (this._statuesRemaining > 0)
|
||||
{
|
||||
this._statuesRemaining--;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// P3: a registered guild member activates one of the two Crown Switches (217/218) during the siege.
|
||||
/// The throne can only be taken by a guild holding BOTH switches. No-op outside the siege phase.
|
||||
/// </summary>
|
||||
/// <param name="switchNumber">The Crown Switch NPC number (217 or 218).</param>
|
||||
/// <param name="guildName">The activating guild's name.</param>
|
||||
public void HoldSwitch(short switchNumber, string guildName)
|
||||
{
|
||||
if (this.Phase == CastleSiegePhase.Siege)
|
||||
{
|
||||
this._occupier = guildName;
|
||||
this._switchHolders[switchNumber] = guildName;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// P3: attempts to capture the throne for a guild. Succeeds only during the siege when all guardian
|
||||
/// statues are destroyed and the guild holds both Crown Switches.
|
||||
/// </summary>
|
||||
/// <param name="guildName">The capturing guild's name.</param>
|
||||
/// <returns>A tuple of success and a human-readable reason/result message.</returns>
|
||||
public (bool Success, string Reason) TryCaptureThrone(string guildName)
|
||||
{
|
||||
if (this.Phase != CastleSiegePhase.Siege)
|
||||
{
|
||||
return (false, "The siege is not running.");
|
||||
}
|
||||
|
||||
if (this._statuesRemaining > 0)
|
||||
{
|
||||
return (false, $"Destroy the guardian statues first ({this._statuesRemaining} remaining).");
|
||||
}
|
||||
|
||||
var holdsBoth = this._switchHolders.TryGetValue(217, out var s217) && s217 == guildName
|
||||
&& this._switchHolders.TryGetValue(218, out var s218) && s218 == guildName;
|
||||
if (!holdsBoth)
|
||||
{
|
||||
return (false, "Your guild must hold both Crown Switches (217 and 218) at once.");
|
||||
}
|
||||
|
||||
this._occupier = guildName;
|
||||
return (true, "throne captured");
|
||||
}
|
||||
|
||||
/// <summary>Returns a human-readable status summary for admin display.</summary>
|
||||
public string GetStatusText()
|
||||
=> $"CS phase={this.Phase}, owner={this.OwnerGuildName ?? "(none)"}, "
|
||||
|
||||
77
src/GameLogic/CastleSiege/CastleSiegeCrownTalkPlugIn.cs
Normal file
77
src/GameLogic/CastleSiege/CastleSiegeCrownTalkPlugIn.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
// <copyright file="CastleSiegeCrownTalkPlugIn.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 throne NPCs — Crown (216) and Sinior (223) — on Valley of Loren.
|
||||
/// A registered guild takes the throne only when it has destroyed all guardian statues AND holds both
|
||||
/// Crown Switches (217/218). The guild on the throne when the siege ends becomes the castle owner.
|
||||
/// </summary>
|
||||
[Guid("CA5710A0-7A1B-4C2D-8E3F-000000000216")]
|
||||
[PlugIn]
|
||||
[Display(Name = "Castle Siege Crown/Throne", Description = "Captures the throne (Crown 216 / Sinior 223) once statues are down and both switches held.")]
|
||||
public class CastleSiegeCrownTalkPlugIn : IPlayerTalkToNpcPlugIn
|
||||
{
|
||||
private static readonly short[] ThroneNpcNumbers = { 216, 223 };
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask PlayerTalksToNpcAsync(Player player, NonPlayerCharacter npc, NpcTalkEventArgs eventArgs)
|
||||
{
|
||||
if (!ThroneNpcNumbers.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 (player.GuildStatus is not { } guildStatus)
|
||||
{
|
||||
await ShowAsync(player, "Only members of a registered guild can take 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;
|
||||
}
|
||||
|
||||
var (success, reason) = context.TryCaptureThrone(guildName);
|
||||
await ShowAsync(
|
||||
player,
|
||||
success
|
||||
? $"Your guild '{guildName}' has CAPTURED THE THRONE! Hold it until the siege ends to win the castle!"
|
||||
: reason).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static ValueTask ShowAsync(Player player, string text)
|
||||
=> player.InvokeViewPlugInAsync<IShowMessagePlugIn>(p => p.ShowMessageAsync(text, MessageType.BlueNormal));
|
||||
}
|
||||
@@ -70,8 +70,8 @@ public class CastleSiegeThroneTalkPlugIn : IPlayerTalkToNpcPlugIn
|
||||
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);
|
||||
context.HoldSwitch(npc.Definition.Number, guildName);
|
||||
await ShowAsync(player, $"Your guild '{guildName}' is holding this Crown Switch. Hold BOTH switches (217 and 218) and destroy the guardian statues, then take the throne!").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static ValueTask ShowAsync(Player player, string text)
|
||||
|
||||
Reference in New Issue
Block a user