feat(CS): on-map siege countdown timer (S6 client protocol C1 B2 17/1E)
Some checks failed
.NET Core / build (push) Has been cancelled
Some checks failed
.NET Core / build (push) Has been cancelled
Client already renders the siege-map countdown but never received the packets. New ICastleSiegeStatusViewPlugIn + RemoteView impl send the raw S6 packets: C1 B2 17 (battle start/stop flag, arms the countdown) and C1 B2 1E (remaining hour/minute). CastleSiegeEventPlugIn broadcasts them to players on the battle map every 10s during Siege and sends stop at Settlement. No client changes. +1 test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@ using System.Runtime.InteropServices;
|
||||
using MUnique.OpenMU.GameLogic.CastleSiege;
|
||||
using MUnique.OpenMU.GameLogic.NPC;
|
||||
using MUnique.OpenMU.GameLogic.Views;
|
||||
using MUnique.OpenMU.GameLogic.Views.CastleSiege;
|
||||
using MUnique.OpenMU.Interfaces;
|
||||
using MUnique.OpenMU.Pathfinding;
|
||||
using MUnique.OpenMU.Persistence;
|
||||
@@ -122,6 +123,10 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom
|
||||
await SpawnCastleDefensesAsync(gameContext, context).ConfigureAwait(false);
|
||||
await WarpRegisteredMembersToSiegeAsync(gameContext, context).ConfigureAwait(false);
|
||||
break;
|
||||
case CastleSiegePhase.Settlement:
|
||||
// Stop the on-map countdown for everyone still on the battle map.
|
||||
await BroadcastSiegeStateAsync(gameContext, false, 0, 0).ConfigureAwait(false);
|
||||
break;
|
||||
case CastleSiegePhase.Ownership when context.OwnerGuildName is { } owner:
|
||||
await AnnounceAsync(gameContext, $"The Castle Siege has ended. The castle now belongs to the guild '{owner}'!").ConfigureAwait(false);
|
||||
break;
|
||||
@@ -293,6 +298,16 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom
|
||||
|
||||
context.SetSwitchHolder(switchNumber, holder);
|
||||
}
|
||||
|
||||
// Keep the client's on-map countdown armed and in sync. Resend every 10s so players who just
|
||||
// loaded the battle map pick it up, without visibly resetting the second-counter too often.
|
||||
var now = DateTime.UtcNow;
|
||||
if ((int)(now - context.PhaseStartedUtc).TotalSeconds % 10 == 0)
|
||||
{
|
||||
var remaining = context.GetRemainingSiegeTime(now);
|
||||
var totalMinutes = (int)Math.Ceiling(remaining.TotalMinutes);
|
||||
await BroadcastSiegeStateAsync(gameContext, true, (byte)(totalMinutes / 60), (byte)(totalMinutes % 60)).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -301,6 +316,25 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends the Castle Siege countdown state to every player currently on the battle map (Valley of Loren).
|
||||
/// When <paramref name="started"/> is true it also (re)seeds the remaining hour/minute.
|
||||
/// </summary>
|
||||
private static ValueTask BroadcastSiegeStateAsync(IGameContext gameContext, bool started, byte hour, byte minute)
|
||||
=> gameContext.ForEachPlayerAsync(async player =>
|
||||
{
|
||||
if (player.CurrentMap?.Definition.Number != ValleyOfLorenMapNumber)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await player.InvokeViewPlugInAsync<ICastleSiegeStatusViewPlugIn>(p => p.SetBattleStateAsync(started)).ConfigureAwait(false);
|
||||
if (started)
|
||||
{
|
||||
await player.InvokeViewPlugInAsync<ICastleSiegeStatusViewPlugIn>(p => p.SetTimerAsync(hour, minute)).ConfigureAwait(false);
|
||||
}
|
||||
});
|
||||
|
||||
private static async Task WarpRegisteredMembersToSiegeAsync(IGameContext gameContext, CastleSiegeContext context)
|
||||
{
|
||||
try
|
||||
|
||||
Reference in New Issue
Block a user