From 9f2078d7243d1b7d20e9213861b6c2d257e1bc88 Mon Sep 17 00:00:00 2001 From: Acentech Dev Date: Wed, 15 Jul 2026 00:48:09 +0300 Subject: [PATCH] feat(CS-P3): throne capture + settlement winner in state machine + tests --- .../CastleSiege/CastleSiegeContext.cs | 24 ++++++++++++++++++- .../CastleSiege/CastleSiegeContextTest.cs | 24 +++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/GameLogic/CastleSiege/CastleSiegeContext.cs b/src/GameLogic/CastleSiege/CastleSiegeContext.cs index ff72647..54026db 100644 --- a/src/GameLogic/CastleSiege/CastleSiegeContext.cs +++ b/src/GameLogic/CastleSiege/CastleSiegeContext.cs @@ -12,6 +12,7 @@ public class CastleSiegeContext { private readonly List _registeredGuilds = new(); private DateTime _phaseStartedUtc; + private string? _occupier; /// Initializes a new instance of the class. /// The cycle timing configuration. @@ -36,6 +37,9 @@ public class CastleSiegeContext /// Gets the guild names registered for the current cycle. public IReadOnlyList RegisteredGuilds => this._registeredGuilds; + /// Gets the guild currently holding the throne during the siege (P3), or null. + public string? OccupierGuildName => this._occupier; + /// Advances the state machine based on the current time. /// The current UTC time. public ValueTask TickAsync(DateTime now) @@ -71,7 +75,13 @@ public class CastleSiegeContext break; case CastleSiegePhase.Settlement: - // P1: no battle -> no winner determination yet. Settle immediately back to ownership. + // Winner = guild holding the throne at siege end (P3). If none captured, owner unchanged. + if (this._occupier is { } occupier) + { + this.SetOwner(occupier); + } + + this._occupier = null; return this.TransitionAsync(CastleSiegePhase.Ownership, now); default: break; @@ -85,6 +95,7 @@ public class CastleSiegeContext public ValueTask ForceStartRegistrationAsync(DateTime now) { this._registeredGuilds.Clear(); + this._occupier = null; return this.TransitionAsync(CastleSiegePhase.Registration, now); } @@ -99,6 +110,7 @@ public class CastleSiegeContext public ValueTask ResetAsync(DateTime now) { this._registeredGuilds.Clear(); + this._occupier = null; return this.TransitionAsync(CastleSiegePhase.Ownership, now); } @@ -117,6 +129,16 @@ public class CastleSiegeContext /// The owner guild name, or null to clear. public void SetOwner(string? guildName) => this.OwnerGuildName = guildName; + /// P3: a registered guild captures the throne during the siege. No-op outside the siege phase. + /// The capturing guild's name. + public void CaptureThrone(string guildName) + { + if (this.Phase == CastleSiegePhase.Siege) + { + this._occupier = guildName; + } + } + /// Returns a human-readable status summary for admin display. public string GetStatusText() => $"CS phase={this.Phase}, owner={this.OwnerGuildName ?? "(none)"}, " diff --git a/tests/MUnique.OpenMU.Tests/CastleSiege/CastleSiegeContextTest.cs b/tests/MUnique.OpenMU.Tests/CastleSiege/CastleSiegeContextTest.cs index a03311d..7e05c7d 100644 --- a/tests/MUnique.OpenMU.Tests/CastleSiege/CastleSiegeContextTest.cs +++ b/tests/MUnique.OpenMU.Tests/CastleSiege/CastleSiegeContextTest.cs @@ -67,6 +67,30 @@ public class CastleSiegeContextTest Assert.That(ctx.RegisteredGuilds, Does.Contain("Attackers")); } + /// Tests that the throne-holding guild at siege end becomes the owner on settlement (P3). + [Test] + public async Task ThroneCaptureDuringSiegeBecomesOwnerOnSettlementAsync() + { + var ctx = new CastleSiegeContext(Config()); + await ctx.ForceStartRegistrationAsync(T0); + await ctx.ForcePhaseAsync(CastleSiegePhase.Siege, T0); + ctx.CaptureThrone("Attackers"); + Assert.That(ctx.OccupierGuildName, Is.EqualTo("Attackers")); + await ctx.TickAsync(T0.AddMinutes(20)); // Siege -> Settlement + await ctx.TickAsync(T0.AddMinutes(20)); // Settlement -> Ownership (owner assigned) + Assert.That(ctx.Phase, Is.EqualTo(CastleSiegePhase.Ownership)); + Assert.That(ctx.OwnerGuildName, Is.EqualTo("Attackers")); + } + + /// Tests that capturing the throne outside the siege phase is a no-op. + [Test] + public void CaptureThroneOutsideSiegeIsNoOp() + { + var ctx = new CastleSiegeContext(Config()); + ctx.CaptureThrone("Attackers"); + Assert.That(ctx.OccupierGuildName, Is.Null); + } + private static CastleSiegeConfiguration Config() => new() { RegistrationDuration = TimeSpan.FromMinutes(5),