feat(CS): authentic Crown-hold throne capture (S6 protocol) replacing Sinior-talk
Some checks failed
.NET Core / build (push) Has been cancelled

Break all gates + hold both switches (defenses down) -> the Crown shield drops
(C1 B2 16=0). The guild master then stands on the Crown (176,212) and holds for
CrownHoldDuration (default 60s, client shows a 60s countdown via C1 B2 15) to
capture; capture broadcasts C1 B2 18 + golden text and sets the occupier. Losing a
switch or leaving the crown resets the hold (contestable until the siege timer ends).
Sinior (223) is now informational guidance. +2 tests (14 total).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Acentech Dev
2026-07-15 15:04:10 +03:00
parent 4651ef232c
commit 3dd4881406
7 changed files with 322 additions and 8 deletions

View File

@@ -23,6 +23,8 @@ public class CastleSiegeContext
private string? _occupier;
private int _defensesRemaining;
private bool _dirty;
private string? _crownHoldGuild;
private DateTime? _crownHoldStartUtc;
/// <summary>Initializes a new instance of the <see cref="CastleSiegeContext"/> class.</summary>
/// <param name="configuration">The cycle timing configuration.</param>
@@ -188,6 +190,69 @@ public class CastleSiegeContext
return remaining > TimeSpan.Zero ? remaining : TimeSpan.Zero;
}
/// <summary>
/// Returns the guild that currently holds BOTH crown switches while all castle defenses are down (so the
/// crown's shield is dropped for them), or null. Only meaningful during the siege.
/// </summary>
public string? GetShieldEligibleGuild()
{
if (this.Phase != CastleSiegePhase.Siege || this._defensesRemaining > 0)
{
return null;
}
var holder = this._switchHolders[217];
return holder is not null && holder == this._switchHolders[218] ? holder : null;
}
/// <summary>
/// Advances the crown-hold capture. <paramref name="eligibleGuild"/> is the guild with both switches held
/// and no defenses left (shield down); <paramref name="masterHolding"/> is whether that guild's master is
/// standing on the crown. Captures the throne for the guild once it has held for <paramref name="holdDuration"/>.
/// Losing a switch or the master leaving the crown resets the hold (contestable until the siege ends).
/// </summary>
/// <param name="eligibleGuild">The guild with both switches and no defenses, or null.</param>
/// <param name="masterHolding">Whether that guild's master is on the crown.</param>
/// <param name="now">The current UTC time.</param>
/// <param name="holdDuration">How long the master must hold to capture.</param>
public CrownTickResult TickCrownHold(string? eligibleGuild, bool masterHolding, DateTime now, TimeSpan holdDuration)
{
if (this.Phase != CastleSiegePhase.Siege)
{
this._crownHoldGuild = null;
this._crownHoldStartUtc = null;
return new CrownTickResult(false, 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);
}
if (this._crownHoldGuild != eligibleGuild || this._crownHoldStartUtc is null)
{
this._crownHoldGuild = eligibleGuild;
this._crownHoldStartUtc = now;
return new CrownTickResult(shieldDown, CrownEvent.HoldStarted, eligibleGuild);
}
if (now - this._crownHoldStartUtc.Value >= holdDuration)
{
this._occupier = eligibleGuild;
this._crownHoldGuild = null;
this._crownHoldStartUtc = null;
this._dirty = true;
return new CrownTickResult(shieldDown, CrownEvent.Captured, eligibleGuild);
}
return new CrownTickResult(shieldDown, CrownEvent.None, eligibleGuild);
}
/// <summary>
/// Returns whether the persistable state changed since the last call, resetting the flag.
/// Called each tick by the plugin to decide whether to write state to the database.
@@ -296,6 +361,8 @@ public class CastleSiegeContext
this._switchHolders[218] = null;
this._defensesRemaining = 0;
this._occupier = null;
this._crownHoldGuild = null;
this._crownHoldStartUtc = null;
}
private ValueTask TransitionAsync(CastleSiegePhase phase, DateTime now)
@@ -307,3 +374,25 @@ public class CastleSiegeContext
return ValueTask.CompletedTask;
}
}
/// <summary>The event produced by a single <see cref="CastleSiegeContext.TickCrownHold"/> call.</summary>
public enum CrownEvent
{
/// <summary>Nothing changed this tick.</summary>
None,
/// <summary>A guild master just started holding the crown (start the client countdown).</summary>
HoldStarted,
/// <summary>The crown was held long enough — the guild captured the throne.</summary>
Captured,
/// <summary>An in-progress hold was interrupted (switch lost or master left the crown).</summary>
HoldReset,
}
/// <summary>The result of a crown-hold tick: the shield state and any event that occurred.</summary>
/// <param name="ShieldDown">Whether the crown shield is currently down (both switches held, defenses cleared).</param>
/// <param name="Event">The event that occurred this tick.</param>
/// <param name="Guild">The guild the event refers to, if any.</param>
public readonly record struct CrownTickResult(bool ShieldDown, CrownEvent Event, string? Guild);