feat(CS-P3): throne registration via Sinior/Crown talk (223/216) after defenses down + both switches held; reachable defense positions
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:
@@ -169,26 +169,38 @@ public class CastleSiegeContext
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the throne capture: if the siege is running, all defenses are destroyed, the throne is not
|
||||
/// yet taken, and one guild holds BOTH Crown Switches at once, that guild captures the throne.
|
||||
/// Attempts to capture the throne for a guild (called when a member registers at the Sinior/Crown NPC).
|
||||
/// Succeeds only during the siege when the throne is free, all castle defenses are destroyed, and the
|
||||
/// guild is currently holding BOTH Crown Switches (a member standing on each).
|
||||
/// </summary>
|
||||
/// <returns>The guild that just captured the throne (for announcing), or <c>null</c> if nothing changed.</returns>
|
||||
public string? EvaluateCapture()
|
||||
/// <param name="guildName">The capturing guild's name.</param>
|
||||
/// <returns>Whether it succeeded and a human-readable reason/result message.</returns>
|
||||
public (bool Success, string Reason) TryCaptureThrone(string guildName)
|
||||
{
|
||||
if (this.Phase != CastleSiegePhase.Siege || this._occupier is not null || this._defensesRemaining > 0)
|
||||
if (this.Phase != CastleSiegePhase.Siege)
|
||||
{
|
||||
return null;
|
||||
return (false, "The siege is not running.");
|
||||
}
|
||||
|
||||
var holder217 = this._switchHolders[217];
|
||||
var holder218 = this._switchHolders[218];
|
||||
if (holder217 is not null && holder217 == holder218)
|
||||
if (this._occupier is not null)
|
||||
{
|
||||
this._occupier = holder217;
|
||||
return holder217;
|
||||
return (false, this._occupier == guildName
|
||||
? "Your guild already holds the throne."
|
||||
: $"The throne is already held by '{this._occupier}'.");
|
||||
}
|
||||
|
||||
return null;
|
||||
if (this._defensesRemaining > 0)
|
||||
{
|
||||
return (false, $"Destroy the castle defenses first ({this._defensesRemaining} remaining).");
|
||||
}
|
||||
|
||||
if (this._switchHolders[217] != guildName || this._switchHolders[218] != guildName)
|
||||
{
|
||||
return (false, "Your guild must be holding BOTH Crown Switches at once (stand a member on each).");
|
||||
}
|
||||
|
||||
this._occupier = guildName;
|
||||
return (true, "throne captured");
|
||||
}
|
||||
|
||||
/// <summary>Returns a human-readable status summary for admin display.</summary>
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
// <copyright file="CastleSiegeThroneCaptureTalkPlugIn.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 registering on the throne at the Sinior (223) / Crown (216) NPCs on Valley of Loren.
|
||||
/// A registered guild takes the throne only when it has destroyed all castle defenses (gates + statues)
|
||||
/// AND is holding both Crown Switches (a member standing on each). The guild on the throne when the siege
|
||||
/// ends becomes the castle owner. Capturing broadcasts a server-wide golden message.
|
||||
/// </summary>
|
||||
[Guid("CA5710A0-7A1B-4C2D-8E3F-000000000223")]
|
||||
[PlugIn]
|
||||
[Display(Name = "Castle Siege Throne (Sinior/Crown)", Description = "Registers a guild on the throne (Sinior 223 / Crown 216) once defenses are down and both switches held.")]
|
||||
public class CastleSiegeThroneCaptureTalkPlugIn : IPlayerTalkToNpcPlugIn
|
||||
{
|
||||
private static readonly short[] ThroneNpcNumbers = { 223, 216 };
|
||||
|
||||
/// <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);
|
||||
if (success)
|
||||
{
|
||||
await player.GameContext.ForEachPlayerAsync(p =>
|
||||
p.InvokeViewPlugInAsync<IShowMessagePlugIn>(v =>
|
||||
v.ShowMessageAsync($"Guild '{guildName}' has CAPTURED THE THRONE! They will win the castle if they hold it until the siege ends.", MessageType.GoldenCenter)).AsTask()).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ShowAsync(player, reason).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private static ValueTask ShowAsync(Player player, string text)
|
||||
=> player.InvokeViewPlugInAsync<IShowMessagePlugIn>(p => p.ShowMessageAsync(text, MessageType.BlueNormal));
|
||||
}
|
||||
@@ -30,12 +30,14 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom
|
||||
private const int SwitchHoldRange = 3;
|
||||
|
||||
// Castle defenses spawned at siege start — destructibles the attackers must break: (number, x, y).
|
||||
// Positions are in the throne-room area (near the crown ~176,212 and the switches ~167-184,194-195),
|
||||
// where players can actually reach and hit them (the earlier statue spot 170/182,206 was confirmed reachable).
|
||||
private static readonly (short Number, byte X, byte Y)[] DefenseSpawns =
|
||||
{
|
||||
(CastleGateNumber, 160, 180),
|
||||
(CastleGateNumber, 190, 180),
|
||||
(GuardianStatueNumber, 170, 206),
|
||||
(GuardianStatueNumber, 182, 206),
|
||||
(CastleGateNumber, 168, 200),
|
||||
(CastleGateNumber, 184, 200),
|
||||
(GuardianStatueNumber, 170, 208),
|
||||
(GuardianStatueNumber, 182, 208),
|
||||
};
|
||||
|
||||
// Crown Switch positions on Valley of Loren (from the map init) — held by standing on them: (number, x, y).
|
||||
@@ -209,12 +211,6 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom
|
||||
|
||||
context.SetSwitchHolder(switchNumber, holder);
|
||||
}
|
||||
|
||||
var captured = context.EvaluateCapture();
|
||||
if (captured is not null)
|
||||
{
|
||||
await AnnounceAsync(gameContext, $"Guild '{captured}' has CAPTURED THE THRONE! They win the castle if they hold it until the siege ends.").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user