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>
|
/// <summary>
|
||||||
/// Evaluates the throne capture: if the siege is running, all defenses are destroyed, the throne is not
|
/// Attempts to capture the throne for a guild (called when a member registers at the Sinior/Crown NPC).
|
||||||
/// yet taken, and one guild holds BOTH Crown Switches at once, that guild captures the throne.
|
/// 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>
|
/// </summary>
|
||||||
/// <returns>The guild that just captured the throne (for announcing), or <c>null</c> if nothing changed.</returns>
|
/// <param name="guildName">The capturing guild's name.</param>
|
||||||
public string? EvaluateCapture()
|
/// <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];
|
if (this._occupier is not null)
|
||||||
var holder218 = this._switchHolders[218];
|
|
||||||
if (holder217 is not null && holder217 == holder218)
|
|
||||||
{
|
{
|
||||||
this._occupier = holder217;
|
return (false, this._occupier == guildName
|
||||||
return holder217;
|
? "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>
|
/// <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;
|
private const int SwitchHoldRange = 3;
|
||||||
|
|
||||||
// Castle defenses spawned at siege start — destructibles the attackers must break: (number, x, y).
|
// 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 =
|
private static readonly (short Number, byte X, byte Y)[] DefenseSpawns =
|
||||||
{
|
{
|
||||||
(CastleGateNumber, 160, 180),
|
(CastleGateNumber, 168, 200),
|
||||||
(CastleGateNumber, 190, 180),
|
(CastleGateNumber, 184, 200),
|
||||||
(GuardianStatueNumber, 170, 206),
|
(GuardianStatueNumber, 170, 208),
|
||||||
(GuardianStatueNumber, 182, 206),
|
(GuardianStatueNumber, 182, 208),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Crown Switch positions on Valley of Loren (from the map init) — held by standing on them: (number, x, y).
|
// 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);
|
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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -79,18 +79,18 @@ public class CastleSiegeContextTest
|
|||||||
// Both switches held but defenses still up -> no capture.
|
// Both switches held but defenses still up -> no capture.
|
||||||
ctx.SetSwitchHolder(217, "Attackers");
|
ctx.SetSwitchHolder(217, "Attackers");
|
||||||
ctx.SetSwitchHolder(218, "Attackers");
|
ctx.SetSwitchHolder(218, "Attackers");
|
||||||
Assert.That(ctx.EvaluateCapture(), Is.Null);
|
Assert.That(ctx.TryCaptureThrone("Attackers").Success, Is.False);
|
||||||
|
|
||||||
ctx.NotifyDefenseDestroyed();
|
ctx.NotifyDefenseDestroyed();
|
||||||
ctx.NotifyDefenseDestroyed();
|
ctx.NotifyDefenseDestroyed();
|
||||||
|
|
||||||
// Only one switch held -> still no capture.
|
// Only one switch held -> still no capture.
|
||||||
ctx.SetSwitchHolder(218, null);
|
ctx.SetSwitchHolder(218, null);
|
||||||
Assert.That(ctx.EvaluateCapture(), Is.Null);
|
Assert.That(ctx.TryCaptureThrone("Attackers").Success, Is.False);
|
||||||
|
|
||||||
// Both switches held by the same guild + defenses down -> capture.
|
// Both switches held by the same guild + defenses down -> capture at the Sinior/Crown.
|
||||||
ctx.SetSwitchHolder(218, "Attackers");
|
ctx.SetSwitchHolder(218, "Attackers");
|
||||||
Assert.That(ctx.EvaluateCapture(), Is.EqualTo("Attackers"));
|
Assert.That(ctx.TryCaptureThrone("Attackers").Success, Is.True);
|
||||||
Assert.That(ctx.OccupierGuildName, Is.EqualTo("Attackers"));
|
Assert.That(ctx.OccupierGuildName, Is.EqualTo("Attackers"));
|
||||||
|
|
||||||
await ctx.TickAsync(T0.AddMinutes(20)); // Siege -> Settlement
|
await ctx.TickAsync(T0.AddMinutes(20)); // Siege -> Settlement
|
||||||
@@ -108,17 +108,18 @@ public class CastleSiegeContextTest
|
|||||||
ctx.SetDefenseCount(0);
|
ctx.SetDefenseCount(0);
|
||||||
ctx.SetSwitchHolder(217, "A");
|
ctx.SetSwitchHolder(217, "A");
|
||||||
ctx.SetSwitchHolder(218, "B");
|
ctx.SetSwitchHolder(218, "B");
|
||||||
Assert.That(ctx.EvaluateCapture(), Is.Null);
|
Assert.That(ctx.TryCaptureThrone("A").Success, Is.False);
|
||||||
|
Assert.That(ctx.TryCaptureThrone("B").Success, Is.False);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Tests that switch holding outside the siege phase is a no-op.</summary>
|
/// <summary>Tests that capturing the throne outside the siege phase is a no-op.</summary>
|
||||||
[Test]
|
[Test]
|
||||||
public void SwitchHoldOutsideSiegeIsNoOp()
|
public void ThroneCaptureOutsideSiegeIsNoOp()
|
||||||
{
|
{
|
||||||
var ctx = new CastleSiegeContext(Config());
|
var ctx = new CastleSiegeContext(Config());
|
||||||
ctx.SetSwitchHolder(217, "A");
|
ctx.SetSwitchHolder(217, "A");
|
||||||
ctx.SetSwitchHolder(218, "A");
|
ctx.SetSwitchHolder(218, "A");
|
||||||
Assert.That(ctx.EvaluateCapture(), Is.Null);
|
Assert.That(ctx.TryCaptureThrone("A").Success, Is.False);
|
||||||
Assert.That(ctx.OccupierGuildName, Is.Null);
|
Assert.That(ctx.OccupierGuildName, Is.Null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user