Files
Acentech Dev f8e856c7c6
Some checks failed
.NET Core / build (push) Has been cancelled
feat(castle-siege): operate the Crown Switches by clicking, capture the crown by holding it
The switches used to be held by simply standing near them, and the crown captured
by standing near it - clicking a switch only produced the client's "not implemented
yet" message. This drives both from the original interaction instead:

- Clicking a Crown Switch starts an operation which completes after
  CastleSiegeSettings.SwitchPushSeconds (15) and keeps the switch for the guild
  until its operator leaves the switch's area. One player per switch; anybody else
  clicking it is told another team is on it (C1 B2 14 state 2).
- While one guild holds both switches the crown's shield drops for it, and its
  guild master captures the throne by CLICKING the crown and holding it for
  CrownHoldTimeSeconds - seeded to 60 now, to match the countdown the client's
  registration panel hardcodes. The throne stays contestable until the siege ends.
- The shield now depends on the switches alone, as in the original; the gates and
  statues remain what they always were, the obstacle in the way.

The switch info packet (C1 B2 20) is broadcast before any switch-state packet
because the client's "switch released" handler reads its switch table without
checking that it exists - that table is only allocated when the info packet
arrives, so the wrong order crashes the client.

Also fixed while in here:
- The crown registration panel could never be closed: the cancel was only sent
  while the master still stood on the crown, which is precisely when the hold does
  NOT break. The panel is now closed for the player it was opened for.
- A contested switch was decided by enumeration order.
- Panels opened by the siege are closed when it ends.
- TryCaptureThrone was dead code carrying a second, diverged rule set.
- /csphase advertised the pre-refactor phase names to the client.
- The periodic broadcasts keyed off "UtcNow.Second % n", which silently skips when
  a tick runs late; they count ticks now.

The unit tests never compiled against the refactored model - they are migrated to
the state machine and guild ids, and cover the new switch and crown rules. A new
test proves update 105 writes the configuration into an existing database.

ApplyPendingUpdatesTool applies pending configuration updates without the admin
panel; it is [Explicit], so it never runs in a normal test pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 21:44:12 +03:00

59 lines
2.6 KiB
C#

// <copyright file="ApplyPendingUpdatesTool.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.Persistence.Initialization.Tests;
using Microsoft.Extensions.Logging.Abstractions;
using MUnique.OpenMU.Interfaces;
using MUnique.OpenMU.Persistence.EntityFramework;
using MUnique.OpenMU.Persistence.EntityFramework.Json;
using MUnique.OpenMU.Persistence.Initialization.Updates;
using MUnique.OpenMU.PlugIns;
/// <summary>
/// A manual tool, not a test: applies the pending configuration updates to the database configured in
/// <c>ConnectionSettings.xml</c> - the same thing the admin panel's "Updates" page does, for when there is
/// no browser at hand. It is <see cref="ExplicitAttribute"/> so it never runs in a normal test pass.
/// </summary>
[TestFixture]
[Explicit("Writes to the configured database. Run it deliberately, not as part of the test suite.")]
internal class ApplyPendingUpdatesTool
{
/// <summary>
/// Applies every configuration update which is not installed yet.
/// </summary>
[Test]
public async Task ApplyPendingUpdatesAsync()
{
// The server registers these at startup; without them the configuration JSON cannot be read back.
JsonConverterRegistry.RegisterConverter(new LocalizedStringJsonConverter());
JsonConverterRegistry.RegisterConverter(new BinaryAsHexJsonConverter());
var loggerFactory = new NullLoggerFactory();
var contextProvider = new PersistenceContextProvider(loggerFactory, null);
var plugInManager = new PlugInManager(null, loggerFactory, null, null);
plugInManager.DiscoverAndRegisterPlugIns();
var service = new DataUpdateService(contextProvider, plugInManager);
var pending = (await service.DetermineAvailableUpdatesAsync().ConfigureAwait(false)).ToList();
TestContext.Out.WriteLine($"Pending updates: {pending.Count}");
foreach (var update in pending)
{
TestContext.Out.WriteLine($" {(int)update.Version} - {update.Name}");
}
if (pending.Count == 0)
{
return;
}
var progress = new Progress<(UpdateVersion CurrentUpdatingVersion, bool IsCompleted)>(
p => TestContext.Out.WriteLine($" applying {(int)p.CurrentUpdatingVersion} completed={p.IsCompleted}"));
await service.ApplyUpdatesAsync(pending, progress).ConfigureAwait(false);
var left = await service.DetermineAvailableUpdatesAsync().ConfigureAwait(false);
Assert.That(left, Is.Empty, "all updates should be installed now");
}
}