fix(CS): stop crown re-capture loop + show seal panel only to the master
Some checks failed
.NET Core / build (push) Has been cancelled

- The occupier can no longer re-register its own throne (canCapture = eligible != occupier),
  which caused a repeating 60s panel loop; only a DIFFERENT guild can contest.
- The 60s registration panel (0xB2/0x15) is now sent ONLY to the master on the crown,
  not broadcast to switch-holders/other players. Shield(0x16) + capture(0x18) still broadcast.
+1 test (15 total).
This commit is contained in:
Acentech Dev
2026-07-15 16:22:13 +03:00
parent e5dfa24585
commit f135e90899
3 changed files with 64 additions and 42 deletions

View File

@@ -359,7 +359,7 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom
// Crown-hold capture: when a guild holds both switches with every gate down, the crown shield drops;
// that guild's master then holds the crown for CrownHoldDuration to take the throne (contestable).
var eligible = context.GetShieldEligibleGuild();
var masterOnCrown = false;
Player? masterPlayer = null;
if (eligible is not null)
{
foreach (var player in map.GetAttackablesInRange(CrownPosition, CrownHoldRange).OfType<Player>())
@@ -367,18 +367,35 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom
if (player.GuildStatus?.Position == GuildPosition.GuildMaster
&& await GetGuildNameAsync(player).ConfigureAwait(false) == eligible)
{
masterOnCrown = true;
masterPlayer = player;
break;
}
}
}
var holdDuration = context.Configuration.CrownHoldDuration;
var crown = context.TickCrownHold(eligible, masterOnCrown, now, holdDuration);
await BroadcastCrownAsync(gameContext, crown).ConfigureAwait(false);
if (crown.Event == CrownEvent.Captured && crown.Guild is { } capturedGuild)
var crown = context.TickCrownHold(eligible, masterPlayer is not null, now, context.Configuration.CrownHoldDuration);
// Shield drop/raise -> everyone on the battle map, but only when it flips (the packet pops a modal).
if (crown.ShieldChanged)
{
await AnnounceAsync(gameContext, $"Guild '{capturedGuild}' has taken the Crown and now holds the throne!").ConfigureAwait(false);
await ForEachOnBattleMapAsync(gameContext, p => p.SetCrownShieldAsync(crown.ShieldDown)).ConfigureAwait(false);
}
switch (crown.Event)
{
case CrownEvent.HoldStarted when masterPlayer is not null:
// The 60-second registration panel is shown ONLY to the master taking the crown.
await masterPlayer.InvokeViewPlugInAsync<ICastleSiegeStatusViewPlugIn>(p => p.SetCrownRegistAsync(0, 0)).ConfigureAwait(false);
break;
case CrownEvent.HoldReset when masterPlayer is not null:
await masterPlayer.InvokeViewPlugInAsync<ICastleSiegeStatusViewPlugIn>(p => p.SetCrownRegistAsync(2, 0)).ConfigureAwait(false);
break;
case CrownEvent.Captured when crown.Guild is { } captured:
await ForEachOnBattleMapAsync(gameContext, p => p.AnnounceSealCapturedAsync(captured)).ConfigureAwait(false);
await AnnounceAsync(gameContext, $"Guild '{captured}' has taken the Crown and now holds the throne!").ConfigureAwait(false);
break;
default:
break;
}
// Keep the client's on-map countdown armed and in sync. Resend every 10s so players who just
@@ -416,40 +433,12 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom
}
});
/// <summary>
/// Sends the crown shield state (each tick) and any crown-hold event (start/reset/capture) to every player
/// currently on the battle map.
/// </summary>
private static ValueTask BroadcastCrownAsync(IGameContext gameContext, CrownTickResult crown)
=> gameContext.ForEachPlayerAsync(async player =>
{
if (player.CurrentMap?.Definition.Number != ValleyOfLorenMapNumber)
{
return;
}
// 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<ICastleSiegeStatusViewPlugIn>(p => p.SetCrownShieldAsync(crown.ShieldDown)).ConfigureAwait(false);
}
switch (crown.Event)
{
case CrownEvent.HoldStarted:
await player.InvokeViewPlugInAsync<ICastleSiegeStatusViewPlugIn>(p => p.SetCrownRegistAsync(0, 0)).ConfigureAwait(false);
break;
case CrownEvent.HoldReset:
await player.InvokeViewPlugInAsync<ICastleSiegeStatusViewPlugIn>(p => p.SetCrownRegistAsync(2, 0)).ConfigureAwait(false);
break;
case CrownEvent.Captured when crown.Guild is { } guild:
await player.InvokeViewPlugInAsync<ICastleSiegeStatusViewPlugIn>(p => p.AnnounceSealCapturedAsync(guild)).ConfigureAwait(false);
break;
default:
break;
}
});
/// <summary>Invokes the Castle Siege status view for every player currently on the battle map.</summary>
private static ValueTask ForEachOnBattleMapAsync(IGameContext gameContext, Func<ICastleSiegeStatusViewPlugIn, ValueTask> action)
=> gameContext.ForEachPlayerAsync(player =>
player.CurrentMap?.Definition.Number == ValleyOfLorenMapNumber
? player.InvokeViewPlugInAsync<ICastleSiegeStatusViewPlugIn>(action).AsTask()
: Task.CompletedTask);
private async ValueTask BroadcastCastleFlagAsync(IGameContext gameContext, CastleSiegeContext context)
{