From e5dfa2458554d78c768712a25f5793ca587aafa7 Mon Sep 17 00:00:00 2001 From: Acentech Dev Date: Wed, 15 Jul 2026 15:36:51 +0300 Subject: [PATCH] fix(CS): send crown shield packet (0xB2/0x16) only on change, not every tick The shield packet pops a modal message box on the client; sending it every tick re-opened the modal continuously and froze player input at siege start. Track the shield state in the context and only push the packet when it actually flips. --- .../CastleSiege/CastleSiegeContext.cs | 20 ++++++++++++------- .../PeriodicTasks/CastleSiegeEventPlugIn.cs | 7 ++++++- 2 files changed, 19 insertions(+), 8 deletions(-) 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) {