diff --git a/src/GameLogic/CastleSiege/CastleSiegeContext.cs b/src/GameLogic/CastleSiege/CastleSiegeContext.cs
index 61ff735..d9d985d 100644
--- a/src/GameLogic/CastleSiege/CastleSiegeContext.cs
+++ b/src/GameLogic/CastleSiege/CastleSiegeContext.cs
@@ -25,6 +25,7 @@ public class CastleSiegeContext
private bool _dirty;
private string? _crownHoldGuild;
private DateTime? _crownHoldStartUtc;
+ private bool _lastShieldDown;
/// Initializes a new instance of the class.
/// The cycle timing configuration.
@@ -217,28 +218,31 @@ public class CastleSiegeContext
/// How long the master must hold to capture.
public CrownTickResult TickCrownHold(string? eligibleGuild, bool masterHolding, DateTime now, TimeSpan holdDuration)
{
+ var shieldDown = this.Phase == CastleSiegePhase.Siege && eligibleGuild is not null;
+ var shieldChanged = shieldDown != this._lastShieldDown;
+ this._lastShieldDown = shieldDown;
+
if (this.Phase != CastleSiegePhase.Siege)
{
this._crownHoldGuild = null;
this._crownHoldStartUtc = null;
- return new CrownTickResult(false, CrownEvent.None, null);
+ return new CrownTickResult(false, shieldChanged, CrownEvent.None, null);
}
- var shieldDown = eligibleGuild is not null;
var wasHolding = this._crownHoldGuild is not null;
if (eligibleGuild is null || !masterHolding)
{
this._crownHoldGuild = null;
this._crownHoldStartUtc = null;
- return new CrownTickResult(shieldDown, wasHolding ? CrownEvent.HoldReset : CrownEvent.None, null);
+ return new CrownTickResult(shieldDown, shieldChanged, wasHolding ? CrownEvent.HoldReset : CrownEvent.None, null);
}
if (this._crownHoldGuild != eligibleGuild || this._crownHoldStartUtc is null)
{
this._crownHoldGuild = eligibleGuild;
this._crownHoldStartUtc = now;
- return new CrownTickResult(shieldDown, CrownEvent.HoldStarted, eligibleGuild);
+ return new CrownTickResult(shieldDown, shieldChanged, CrownEvent.HoldStarted, eligibleGuild);
}
if (now - this._crownHoldStartUtc.Value >= holdDuration)
@@ -247,10 +251,10 @@ public class CastleSiegeContext
this._crownHoldGuild = null;
this._crownHoldStartUtc = null;
this._dirty = true;
- return new CrownTickResult(shieldDown, CrownEvent.Captured, eligibleGuild);
+ return new CrownTickResult(shieldDown, shieldChanged, CrownEvent.Captured, eligibleGuild);
}
- return new CrownTickResult(shieldDown, CrownEvent.None, eligibleGuild);
+ return new CrownTickResult(shieldDown, shieldChanged, CrownEvent.None, eligibleGuild);
}
///
@@ -363,6 +367,7 @@ public class CastleSiegeContext
this._occupier = null;
this._crownHoldGuild = null;
this._crownHoldStartUtc = null;
+ this._lastShieldDown = false;
}
private ValueTask TransitionAsync(CastleSiegePhase phase, DateTime now)
@@ -393,6 +398,7 @@ public enum CrownEvent
/// The result of a crown-hold tick: the shield state and any event that occurred.
/// Whether the crown shield is currently down (both switches held, defenses cleared).
+/// Whether the shield state changed this tick (only then should the client be told).
/// The event that occurred this tick.
/// The guild the event refers to, if any.
-public readonly record struct CrownTickResult(bool ShieldDown, CrownEvent Event, string? Guild);
+public readonly record struct CrownTickResult(bool ShieldDown, bool ShieldChanged, CrownEvent Event, string? Guild);
diff --git a/src/GameLogic/PlugIns/PeriodicTasks/CastleSiegeEventPlugIn.cs b/src/GameLogic/PlugIns/PeriodicTasks/CastleSiegeEventPlugIn.cs
index 5aeafb4..9e15915 100644
--- a/src/GameLogic/PlugIns/PeriodicTasks/CastleSiegeEventPlugIn.cs
+++ b/src/GameLogic/PlugIns/PeriodicTasks/CastleSiegeEventPlugIn.cs
@@ -428,7 +428,12 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom
return;
}
- await player.InvokeViewPlugInAsync(p => p.SetCrownShieldAsync(crown.ShieldDown)).ConfigureAwait(false);
+ // Only tell the client when the shield state actually flips — the 0xB2/0x16 packet pops a modal
+ // message box, so sending it every tick would block player input.
+ if (crown.ShieldChanged)
+ {
+ await player.InvokeViewPlugInAsync(p => p.SetCrownShieldAsync(crown.ShieldDown)).ConfigureAwait(false);
+ }
switch (crown.Event)
{