// // Licensed under the MIT License. See LICENSE file in the project root for full license information. // namespace MUnique.OpenMU.Tests.CastleSiege; using MUnique.OpenMU.GameLogic.CastleSiege; /// /// Tests for the Castle Siege phase state machine (time-driven, injected clock). /// [TestFixture] public class CastleSiegeContextTest { private static readonly DateTime T0 = new(2026, 1, 1, 12, 0, 0, DateTimeKind.Utc); /// Tests that a fresh context starts in the ownership (resting) phase. [Test] public void StartsInOwnership() { var ctx = new CastleSiegeContext(Config()); Assert.That(ctx.Phase, Is.EqualTo(CastleSiegePhase.Ownership)); } /// Tests that force-starting moves the state machine into registration. [Test] public async Task ForceStartMovesToRegistrationAsync() { var ctx = new CastleSiegeContext(Config()); await ctx.ForceStartRegistrationAsync(T0); Assert.That(ctx.Phase, Is.EqualTo(CastleSiegePhase.Registration)); } /// Tests that registration advances to preparation once its duration elapses. [Test] public async Task RegistrationAdvancesToPreparationAfterDurationAsync() { var ctx = new CastleSiegeContext(Config()); await ctx.ForceStartRegistrationAsync(T0); await ctx.TickAsync(T0.AddMinutes(4)); // still within registration Assert.That(ctx.Phase, Is.EqualTo(CastleSiegePhase.Registration)); await ctx.TickAsync(T0.AddMinutes(5)); // registration duration elapsed Assert.That(ctx.Phase, Is.EqualTo(CastleSiegePhase.Preparation)); } /// Tests a full cycle: registration -> preparation -> siege -> settlement -> ownership. [Test] public async Task FullCycleReturnsToOwnershipAsync() { var ctx = new CastleSiegeContext(Config()); await ctx.ForceStartRegistrationAsync(T0); await ctx.TickAsync(T0.AddMinutes(5)); // -> Preparation await ctx.TickAsync(T0.AddMinutes(7)); // +2 prep -> Siege Assert.That(ctx.Phase, Is.EqualTo(CastleSiegePhase.Siege)); await ctx.TickAsync(T0.AddMinutes(17)); // +10 siege -> Settlement await ctx.TickAsync(T0.AddMinutes(17)); // Settlement -> Ownership Assert.That(ctx.Phase, Is.EqualTo(CastleSiegePhase.Ownership)); } /// Tests that guilds can be registered (by name) during the registration phase. [Test] public async Task RegisterGuildCollectsNamesDuringRegistrationAsync() { var ctx = new CastleSiegeContext(Config()); await ctx.ForceStartRegistrationAsync(T0); ctx.RegisterGuild("Attackers"); Assert.That(ctx.RegisteredGuilds, Does.Contain("Attackers")); } /// Tests the full siege objective chain: destroy statues, hold both switches, then capture the throne. [Test] public async Task FullSiegeObjectiveChainToOwnershipAsync() { var ctx = new CastleSiegeContext(Config()); await ctx.ForceStartRegistrationAsync(T0); await ctx.ForcePhaseAsync(CastleSiegePhase.Siege, T0); ctx.SetStatueCount(2); // Throne blocked until statues destroyed + both switches held. Assert.That(ctx.TryCaptureThrone("Attackers").Success, Is.False); ctx.NotifyStatueDestroyed(); ctx.NotifyStatueDestroyed(); ctx.HoldSwitch(217, "Attackers"); Assert.That(ctx.TryCaptureThrone("Attackers").Success, Is.False); // only one switch held ctx.HoldSwitch(218, "Attackers"); Assert.That(ctx.TryCaptureThrone("Attackers").Success, Is.True); // both switches + statues down Assert.That(ctx.OccupierGuildName, Is.EqualTo("Attackers")); await ctx.TickAsync(T0.AddMinutes(20)); // Siege -> Settlement await ctx.TickAsync(T0.AddMinutes(20)); // Settlement -> Ownership Assert.That(ctx.OwnerGuildName, Is.EqualTo("Attackers")); } /// Tests that a rival guild holding only one switch cannot steal the throne. [Test] public async Task ThroneRequiresBothSwitchesBySameGuildAsync() { var ctx = new CastleSiegeContext(Config()); await ctx.ForceStartRegistrationAsync(T0); await ctx.ForcePhaseAsync(CastleSiegePhase.Siege, T0); ctx.SetStatueCount(0); ctx.HoldSwitch(217, "A"); ctx.HoldSwitch(218, "B"); Assert.That(ctx.TryCaptureThrone("A").Success, Is.False); Assert.That(ctx.TryCaptureThrone("B").Success, Is.False); } /// Tests that objective actions outside the siege phase are no-ops. [Test] public void ObjectiveActionsOutsideSiegeAreNoOp() { var ctx = new CastleSiegeContext(Config()); ctx.HoldSwitch(217, "A"); Assert.That(ctx.TryCaptureThrone("A").Success, Is.False); Assert.That(ctx.OccupierGuildName, Is.Null); } private static CastleSiegeConfiguration Config() => new() { RegistrationDuration = TimeSpan.FromMinutes(5), PreparationDuration = TimeSpan.FromMinutes(2), SiegeDuration = TimeSpan.FromMinutes(10), }; }