diff --git a/src/GameLogic/CastleSiege/CastleSiegeContext.cs b/src/GameLogic/CastleSiege/CastleSiegeContext.cs index 8441b79..2e581ad 100644 --- a/src/GameLogic/CastleSiege/CastleSiegeContext.cs +++ b/src/GameLogic/CastleSiege/CastleSiegeContext.cs @@ -5,16 +5,23 @@ namespace MUnique.OpenMU.GameLogic.CastleSiege; /// -/// In-memory Castle Siege phase state machine (P1 skeleton: no battle/persistence). +/// In-memory Castle Siege phase state machine and battle contention (P3). /// Time is injected via method parameters so it can be tested deterministically. +/// Battle rule: attackers must destroy all castle defenses (gates + guardian statues) and then hold BOTH +/// Crown Switches at the same time — the switches are held by standing on them (evaluated per tick by the +/// plugin), and once both are held by one guild with the defenses down, that guild captures the throne. +/// The throne holder when the siege ends becomes the castle owner. /// public class CastleSiegeContext { + /// The Crown Switch NPC numbers on Valley of Loren; both must be held to take the throne. + public static readonly short[] SwitchNumbers = { 217, 218 }; + private readonly List _registeredGuilds = new(); - private readonly Dictionary _switchHolders = new(); + private readonly Dictionary _switchHolders = new() { { 217, null }, { 218, null } }; private DateTime _phaseStartedUtc; private string? _occupier; - private int _statuesRemaining; + private int _defensesRemaining; /// Initializes a new instance of the class. /// The cycle timing configuration. @@ -36,12 +43,15 @@ public class CastleSiegeContext /// Gets the current owner guild name, or null if unowned. public string? OwnerGuildName { get; private set; } - /// 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; + /// Gets the number of castle defenses (gates + statues) still standing; the throne needs 0. + public int DefensesRemaining => this._defensesRemaining; + + /// Gets the guild names registered for the current cycle. + public IReadOnlyList RegisteredGuilds => this._registeredGuilds; + /// Advances the state machine based on the current time. /// The current UTC time. public ValueTask TickAsync(DateTime now) @@ -77,13 +87,13 @@ public class CastleSiegeContext break; case CastleSiegePhase.Settlement: - // Winner = guild holding the throne at siege end (P3). If none captured, owner unchanged. + // Winner = guild holding the throne at siege end. If none captured, owner unchanged. if (this._occupier is { } occupier) { this.SetOwner(occupier); } - this._occupier = null; + this.ClearBattleState(); return this.TransitionAsync(CastleSiegePhase.Ownership, now); default: break; @@ -97,9 +107,7 @@ public class CastleSiegeContext public ValueTask ForceStartRegistrationAsync(DateTime now) { this._registeredGuilds.Clear(); - this._switchHolders.Clear(); - this._statuesRemaining = 0; - this._occupier = null; + this.ClearBattleState(); return this.TransitionAsync(CastleSiegePhase.Registration, now); } @@ -109,14 +117,12 @@ public class CastleSiegeContext public ValueTask ForcePhaseAsync(CastleSiegePhase phase, DateTime now) => this.TransitionAsync(phase, now); - /// Admin: resets to the ownership (resting) phase and clears registrations. + /// Admin: resets to the ownership (resting) phase and clears registrations/battle state. /// The current UTC time. public ValueTask ResetAsync(DateTime now) { this._registeredGuilds.Clear(); - this._switchHolders.Clear(); - this._statuesRemaining = 0; - this._occupier = null; + this.ClearBattleState(); return this.TransitionAsync(CastleSiegePhase.Ownership, now); } @@ -135,69 +141,70 @@ public class CastleSiegeContext /// The owner guild name, or null to clear. public void SetOwner(string? guildName) => this.OwnerGuildName = guildName; - /// Gets the number of guardian statues still standing (must be 0 before the throne can be taken). - public int StatuesRemaining => this._statuesRemaining; + /// Sets how many castle defenses (gates + guardian statues) exist this siege (when spawned). + /// The defense count. + public void SetDefenseCount(int count) => this._defensesRemaining = count > 0 ? count : 0; - /// Sets how many guardian statues defend the castle this siege (called when they are spawned). - /// The statue count. - public void SetStatueCount(int count) => this._statuesRemaining = count > 0 ? count : 0; - - /// Notifies that a guardian statue was destroyed. Lowers the remaining count (min 0). - public void NotifyStatueDestroyed() + /// Notifies that a castle defense (gate/statue) was destroyed. Lowers the remaining count (min 0). + public void NotifyDefenseDestroyed() { - if (this._statuesRemaining > 0) + if (this._defensesRemaining > 0) { - this._statuesRemaining--; + this._defensesRemaining--; } } /// - /// P3: a registered guild member activates one of the two Crown Switches (217/218) during the siege. - /// The throne can only be taken by a guild holding BOTH switches. No-op outside the siege phase. + /// Sets which guild is currently standing on (holding) a Crown Switch. Called every tick by the plugin + /// based on player positions. Pass null when no registered member stands on it. No-op outside the siege. /// /// The Crown Switch NPC number (217 or 218). - /// The activating guild's name. - public void HoldSwitch(short switchNumber, string guildName) + /// The holding guild's name, or null. + public void SetSwitchHolder(short switchNumber, string? guildName) { - if (this.Phase == CastleSiegePhase.Siege) + if (this.Phase == CastleSiegePhase.Siege && this._switchHolders.ContainsKey(switchNumber)) { this._switchHolders[switchNumber] = guildName; } } /// - /// P3: attempts to capture the throne for a guild. Succeeds only during the siege when all guardian - /// statues are destroyed and the guild holds both Crown Switches. + /// Evaluates the throne capture: if the siege is running, all defenses are destroyed, the throne is not + /// yet taken, and one guild holds BOTH Crown Switches at once, that guild captures the throne. /// - /// The capturing guild's name. - /// A tuple of success and a human-readable reason/result message. - public (bool Success, string Reason) TryCaptureThrone(string guildName) + /// The guild that just captured the throne (for announcing), or null if nothing changed. + public string? EvaluateCapture() { - if (this.Phase != CastleSiegePhase.Siege) + if (this.Phase != CastleSiegePhase.Siege || this._occupier is not null || this._defensesRemaining > 0) { - return (false, "The siege is not running."); + return null; } - if (this._statuesRemaining > 0) + var holder217 = this._switchHolders[217]; + var holder218 = this._switchHolders[218]; + if (holder217 is not null && holder217 == holder218) { - return (false, $"Destroy the guardian statues first ({this._statuesRemaining} remaining)."); + this._occupier = holder217; + return holder217; } - var holdsBoth = this._switchHolders.TryGetValue(217, out var s217) && s217 == guildName - && this._switchHolders.TryGetValue(218, out var s218) && s218 == guildName; - if (!holdsBoth) - { - return (false, "Your guild must hold both Crown Switches (217 and 218) at once."); - } - - this._occupier = guildName; - return (true, "throne captured"); + return null; } /// Returns a human-readable status summary for admin display. public string GetStatusText() => $"CS phase={this.Phase}, owner={this.OwnerGuildName ?? "(none)"}, " - + $"registered={this._registeredGuilds.Count} [{string.Join(", ", this._registeredGuilds)}]"; + + $"registered={this._registeredGuilds.Count} [{string.Join(", ", this._registeredGuilds)}], " + + $"defenses={this._defensesRemaining}, throne={this._occupier ?? "(none)"}, " + + $"switch217={this._switchHolders[217] ?? "-"}, switch218={this._switchHolders[218] ?? "-"}"; + + private void ClearBattleState() + { + this._switchHolders[217] = null; + this._switchHolders[218] = null; + this._defensesRemaining = 0; + this._occupier = null; + } private ValueTask TransitionAsync(CastleSiegePhase phase, DateTime now) { diff --git a/src/GameLogic/CastleSiege/CastleSiegeCrownTalkPlugIn.cs b/src/GameLogic/CastleSiege/CastleSiegeCrownTalkPlugIn.cs deleted file mode 100644 index a5f17c2..0000000 --- a/src/GameLogic/CastleSiege/CastleSiegeCrownTalkPlugIn.cs +++ /dev/null @@ -1,77 +0,0 @@ -// -// Licensed under the MIT License. See LICENSE file in the project root for full license information. -// - -namespace MUnique.OpenMU.GameLogic.CastleSiege; - -using System.Linq; -using System.Runtime.InteropServices; -using MUnique.OpenMU.GameLogic.NPC; -using MUnique.OpenMU.GameLogic.PlugIns; -using MUnique.OpenMU.GameLogic.PlugIns.PeriodicTasks; -using MUnique.OpenMU.GameLogic.Views; -using MUnique.OpenMU.Interfaces; -using MUnique.OpenMU.PlugIns; - -/// -/// Handles talking to the Castle Siege throne NPCs — Crown (216) and Sinior (223) — on Valley of Loren. -/// A registered guild takes the throne only when it has destroyed all guardian statues AND holds both -/// Crown Switches (217/218). The guild on the throne when the siege ends becomes the castle owner. -/// -[Guid("CA5710A0-7A1B-4C2D-8E3F-000000000216")] -[PlugIn] -[Display(Name = "Castle Siege Crown/Throne", Description = "Captures the throne (Crown 216 / Sinior 223) once statues are down and both switches held.")] -public class CastleSiegeCrownTalkPlugIn : IPlayerTalkToNpcPlugIn -{ - private static readonly short[] ThroneNpcNumbers = { 216, 223 }; - - /// - public async ValueTask PlayerTalksToNpcAsync(Player player, NonPlayerCharacter npc, NpcTalkEventArgs eventArgs) - { - if (!ThroneNpcNumbers.Contains(npc.Definition.Number)) - { - return; - } - - eventArgs.HasBeenHandled = true; - - var context = CastleSiegeEventPlugIn.TryGetContext(player.GameContext); - if (context is null) - { - await ShowAsync(player, "Castle Siege is not active on this server.").ConfigureAwait(false); - return; - } - - if (player.GuildStatus is not { } guildStatus) - { - await ShowAsync(player, "Only members of a registered guild can take the throne.").ConfigureAwait(false); - return; - } - - var guildName = guildStatus.GuildId.ToString(); - if (player.GameContext is IGameServerContext serverContext) - { - var guild = await serverContext.GuildServer.GetGuildAsync(guildStatus.GuildId).ConfigureAwait(false); - if (guild?.Name is { Length: > 0 } name) - { - guildName = name; - } - } - - if (!context.RegisteredGuilds.Contains(guildName)) - { - await ShowAsync(player, "Your guild is not registered for this Castle Siege.").ConfigureAwait(false); - return; - } - - var (success, reason) = context.TryCaptureThrone(guildName); - await ShowAsync( - player, - success - ? $"Your guild '{guildName}' has CAPTURED THE THRONE! Hold it until the siege ends to win the castle!" - : reason).ConfigureAwait(false); - } - - private static ValueTask ShowAsync(Player player, string text) - => player.InvokeViewPlugInAsync(p => p.ShowMessageAsync(text, MessageType.BlueNormal)); -} diff --git a/src/GameLogic/CastleSiege/CastleSiegeThroneTalkPlugIn.cs b/src/GameLogic/CastleSiege/CastleSiegeThroneTalkPlugIn.cs deleted file mode 100644 index 59c361f..0000000 --- a/src/GameLogic/CastleSiege/CastleSiegeThroneTalkPlugIn.cs +++ /dev/null @@ -1,79 +0,0 @@ -// -// Licensed under the MIT License. See LICENSE file in the project root for full license information. -// - -namespace MUnique.OpenMU.GameLogic.CastleSiege; - -using System.Linq; -using System.Runtime.InteropServices; -using MUnique.OpenMU.GameLogic.NPC; -using MUnique.OpenMU.GameLogic.PlugIns; -using MUnique.OpenMU.GameLogic.PlugIns.PeriodicTasks; -using MUnique.OpenMU.GameLogic.Views; -using MUnique.OpenMU.Interfaces; -using MUnique.OpenMU.PlugIns; - -/// -/// Handles talking to the Castle Siege 'Crown Switch' NPCs (217, 218) on Valley of Loren: while the -/// siege phase is running, a registered guild member using a switch captures the throne for their guild. -/// The guild holding the throne when the siege ends becomes the castle owner (see ). -/// -[Guid("CA5710A0-7A1B-4C2D-8E3F-000000000217")] -[PlugIn] -[Display(Name = "Castle Siege Crown Switch", Description = "Captures the throne for a registered guild during the siege (Crown Switch NPCs 217/218).")] -public class CastleSiegeThroneTalkPlugIn : IPlayerTalkToNpcPlugIn -{ - private static readonly short[] CrownSwitchNumbers = { 217, 218 }; - - /// - public async ValueTask PlayerTalksToNpcAsync(Player player, NonPlayerCharacter npc, NpcTalkEventArgs eventArgs) - { - if (!CrownSwitchNumbers.Contains(npc.Definition.Number)) - { - return; - } - - eventArgs.HasBeenHandled = true; - - var context = CastleSiegeEventPlugIn.TryGetContext(player.GameContext); - if (context is null) - { - await ShowAsync(player, "Castle Siege is not active on this server.").ConfigureAwait(false); - return; - } - - if (context.Phase != CastleSiegePhase.Siege) - { - await ShowAsync(player, "The siege is not running right now.").ConfigureAwait(false); - return; - } - - if (player.GuildStatus is not { } guildStatus) - { - await ShowAsync(player, "Only members of a registered guild can capture the throne.").ConfigureAwait(false); - return; - } - - var guildName = guildStatus.GuildId.ToString(); - if (player.GameContext is IGameServerContext serverContext) - { - var guild = await serverContext.GuildServer.GetGuildAsync(guildStatus.GuildId).ConfigureAwait(false); - if (guild?.Name is { Length: > 0 } name) - { - guildName = name; - } - } - - if (!context.RegisteredGuilds.Contains(guildName)) - { - await ShowAsync(player, "Your guild is not registered for this Castle Siege.").ConfigureAwait(false); - return; - } - - context.HoldSwitch(npc.Definition.Number, guildName); - await ShowAsync(player, $"Your guild '{guildName}' is holding this Crown Switch. Hold BOTH switches (217 and 218) and destroy the guardian statues, then take the throne!").ConfigureAwait(false); - } - - private static ValueTask ShowAsync(Player player, string text) - => player.InvokeViewPlugInAsync(p => p.ShowMessageAsync(text, MessageType.BlueNormal)); -} diff --git a/src/GameLogic/PlugIns/PeriodicTasks/CastleSiegeEventPlugIn.cs b/src/GameLogic/PlugIns/PeriodicTasks/CastleSiegeEventPlugIn.cs index 8915b28..e0df12b 100644 --- a/src/GameLogic/PlugIns/PeriodicTasks/CastleSiegeEventPlugIn.cs +++ b/src/GameLogic/PlugIns/PeriodicTasks/CastleSiegeEventPlugIn.cs @@ -11,6 +11,7 @@ using MUnique.OpenMU.GameLogic.CastleSiege; using MUnique.OpenMU.GameLogic.NPC; using MUnique.OpenMU.GameLogic.Views; using MUnique.OpenMU.Interfaces; +using MUnique.OpenMU.Pathfinding; using MUnique.OpenMU.PlugIns; /// @@ -24,8 +25,25 @@ using MUnique.OpenMU.PlugIns; public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustomConfiguration, ISupportDefaultCustomConfiguration { private const ushort ValleyOfLorenMapNumber = 30; - private const short GuardianStatueNumber = 132; // reuse the "Statue of Saint" destructible definition - private static readonly (byte X, byte Y)[] StatuePositions = { (170, 206), (182, 206) }; + private const short CastleGateNumber = 131; // reuse BloodCastle "Castle Gate" destructible definition + private const short GuardianStatueNumber = 132; // reuse BloodCastle "Statue of Saint" destructible definition + private const int SwitchHoldRange = 3; + + // Castle defenses spawned at siege start — destructibles the attackers must break: (number, x, y). + private static readonly (short Number, byte X, byte Y)[] DefenseSpawns = + { + (CastleGateNumber, 160, 180), + (CastleGateNumber, 190, 180), + (GuardianStatueNumber, 170, 206), + (GuardianStatueNumber, 182, 206), + }; + + // Crown Switch positions on Valley of Loren (from the map init) — held by standing on them: (number, x, y). + private static readonly (short SwitchNumber, byte X, byte Y)[] SwitchPositions = + { + (217, 167, 194), + (218, 184, 195), + }; private static readonly ConcurrentDictionary Contexts = new(); @@ -57,6 +75,12 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom }); await context.TickAsync(DateTime.UtcNow).ConfigureAwait(false); + + // During the siege, evaluate the Crown Switches (held by standing on them) and the throne capture every tick. + if (context.Phase == CastleSiegePhase.Siege) + { + await ProcessSiegeTickAsync(gameContext, context).ConfigureAwait(false); + } } /// @@ -79,8 +103,8 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom await AnnounceAsync(gameContext, "Castle Siege registration is now open! Guild masters, register at the Guardsman in the Valley of Loren.").ConfigureAwait(false); break; case CastleSiegePhase.Siege: - await AnnounceAsync(gameContext, "The Castle Siege has begun! Destroy the guardian statues, hold both Crown Switches, then take the throne!").ConfigureAwait(false); - await SpawnGuardianStatuesAsync(gameContext, context).ConfigureAwait(false); + await AnnounceAsync(gameContext, "The Castle Siege has begun! Break the castle gates and guardian statues, then hold BOTH Crown Switches to take the throne!").ConfigureAwait(false); + await SpawnCastleDefensesAsync(gameContext, context).ConfigureAwait(false); await WarpRegisteredMembersToSiegeAsync(gameContext, context).ConfigureAwait(false); break; case CastleSiegePhase.Ownership when context.OwnerGuildName is { } owner: @@ -101,36 +125,41 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom => gameContext.ForEachPlayerAsync(player => player.InvokeViewPlugInAsync(p => p.ShowMessageAsync(message, MessageType.GoldenCenter)).AsTask()); - private static async Task SpawnGuardianStatuesAsync(IGameContext gameContext, CastleSiegeContext context) + private static async Task SpawnCastleDefensesAsync(IGameContext gameContext, CastleSiegeContext context) { try { if (gameContext is not GameContext concrete) { - context.SetStatueCount(0); + context.SetDefenseCount(0); return; } var map = await gameContext.GetMapAsync(ValleyOfLorenMapNumber).ConfigureAwait(false); - var statueDef = gameContext.Configuration.Monsters.FirstOrDefault(m => m.Number == GuardianStatueNumber); - if (map is null || statueDef is null) + if (map is null) { - context.SetStatueCount(0); + context.SetDefenseCount(0); return; } var spawned = 0; - for (var i = 0; i < StatuePositions.Length; i++) + for (var i = 0; i < DefenseSpawns.Length; i++) { - var pos = StatuePositions[i]; + var spawn = DefenseSpawns[i]; + var definition = gameContext.Configuration.Monsters.FirstOrDefault(m => m.Number == spawn.Number); + if (definition is null) + { + continue; + } + var spawnArea = new MonsterSpawnArea { - MonsterDefinition = statueDef, + MonsterDefinition = definition, Quantity = 1, - X1 = pos.X, - X2 = pos.X, - Y1 = pos.Y, - Y2 = pos.Y, + X1 = spawn.X, + X2 = spawn.X, + Y1 = spawn.Y, + Y2 = spawn.Y, Direction = Direction.South, SpawnTrigger = SpawnTrigger.OnceAtEventStart, }; @@ -138,18 +167,59 @@ public sealed class CastleSiegeEventPlugIn : IPeriodicTaskPlugIn, ISupportCustom var npc = await concrete.MapInitializer.InitializeSpawnAsync(1000 + i, map, spawnArea).ConfigureAwait(false); if (npc is AttackableNpcBase attackable) { - attackable.Died += (_, _) => context.NotifyStatueDestroyed(); + attackable.Died += (_, _) => context.NotifyDefenseDestroyed(); spawned++; } } - context.SetStatueCount(spawned); + context.SetDefenseCount(spawned); } catch (Exception ex) { gameContext.LoggerFactory.CreateLogger() - .LogError(ex, "Castle Siege: error while spawning guardian statues."); - context.SetStatueCount(0); + .LogError(ex, "Castle Siege: error while spawning castle defenses."); + context.SetDefenseCount(0); + } + } + + private static async Task ProcessSiegeTickAsync(IGameContext gameContext, CastleSiegeContext context) + { + try + { + var map = await gameContext.GetMapAsync(ValleyOfLorenMapNumber).ConfigureAwait(false); + if (map is null) + { + return; + } + + // Each Crown Switch is held by whichever registered guild currently has a member standing on it. + foreach (var (switchNumber, x, y) in SwitchPositions) + { + string? holder = null; + var nearby = map.GetAttackablesInRange(new Point(x, y), SwitchHoldRange).OfType(); + foreach (var player in nearby) + { + var guildName = await GetGuildNameAsync(player).ConfigureAwait(false); + if (guildName is not null && context.RegisteredGuilds.Contains(guildName)) + { + holder = guildName; + break; + } + } + + context.SetSwitchHolder(switchNumber, holder); + } + + var captured = context.EvaluateCapture(); + if (captured is not null) + { + await AnnounceAsync(gameContext, $"Guild '{captured}' has CAPTURED THE THRONE! They win the castle if they hold it until the siege ends.").ConfigureAwait(false); + } + } + catch (Exception ex) + { + gameContext.LoggerFactory.CreateLogger() + .LogError(ex, "Castle Siege: error during siege tick processing."); } } diff --git a/tests/MUnique.OpenMU.Tests/CastleSiege/CastleSiegeContextTest.cs b/tests/MUnique.OpenMU.Tests/CastleSiege/CastleSiegeContextTest.cs index 1035379..0fb0179 100644 --- a/tests/MUnique.OpenMU.Tests/CastleSiege/CastleSiegeContextTest.cs +++ b/tests/MUnique.OpenMU.Tests/CastleSiege/CastleSiegeContextTest.cs @@ -67,25 +67,30 @@ public class CastleSiegeContextTest Assert.That(ctx.RegisteredGuilds, Does.Contain("Attackers")); } - /// Tests the full siege objective chain: destroy statues, hold both switches, then capture the throne. + /// Tests the full siege objective chain: destroy defenses, then hold both switches to 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); + ctx.SetDefenseCount(2); - // Throne blocked until statues destroyed + both switches held. - Assert.That(ctx.TryCaptureThrone("Attackers").Success, Is.False); + // Both switches held but defenses still up -> no capture. + ctx.SetSwitchHolder(217, "Attackers"); + ctx.SetSwitchHolder(218, "Attackers"); + Assert.That(ctx.EvaluateCapture(), Is.Null); - ctx.NotifyStatueDestroyed(); - ctx.NotifyStatueDestroyed(); - ctx.HoldSwitch(217, "Attackers"); - Assert.That(ctx.TryCaptureThrone("Attackers").Success, Is.False); // only one switch held + ctx.NotifyDefenseDestroyed(); + ctx.NotifyDefenseDestroyed(); - ctx.HoldSwitch(218, "Attackers"); - Assert.That(ctx.TryCaptureThrone("Attackers").Success, Is.True); // both switches + statues down + // Only one switch held -> still no capture. + ctx.SetSwitchHolder(218, null); + Assert.That(ctx.EvaluateCapture(), Is.Null); + + // Both switches held by the same guild + defenses down -> capture. + ctx.SetSwitchHolder(218, "Attackers"); + Assert.That(ctx.EvaluateCapture(), Is.EqualTo("Attackers")); Assert.That(ctx.OccupierGuildName, Is.EqualTo("Attackers")); await ctx.TickAsync(T0.AddMinutes(20)); // Siege -> Settlement @@ -93,27 +98,27 @@ public class CastleSiegeContextTest Assert.That(ctx.OwnerGuildName, Is.EqualTo("Attackers")); } - /// Tests that a rival guild holding only one switch cannot steal the throne. + /// Tests that two different guilds each holding one switch cannot capture 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); + ctx.SetDefenseCount(0); + ctx.SetSwitchHolder(217, "A"); + ctx.SetSwitchHolder(218, "B"); + Assert.That(ctx.EvaluateCapture(), Is.Null); } - /// Tests that objective actions outside the siege phase are no-ops. + /// Tests that switch holding outside the siege phase is a no-op. [Test] - public void ObjectiveActionsOutsideSiegeAreNoOp() + public void SwitchHoldOutsideSiegeIsNoOp() { var ctx = new CastleSiegeContext(Config()); - ctx.HoldSwitch(217, "A"); - Assert.That(ctx.TryCaptureThrone("A").Success, Is.False); + ctx.SetSwitchHolder(217, "A"); + ctx.SetSwitchHolder(218, "A"); + Assert.That(ctx.EvaluateCapture(), Is.Null); Assert.That(ctx.OccupierGuildName, Is.Null); }