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>
Moves AdaMu's working Castle Siege onto the upstream data model that the
previous commit introduced, without changing how the siege plays.
State model
- CastleSiegePhase is replaced by DataModel's CastleSiegeState, whose values are
exactly what the game client's CASTLESIEGE_STATE enum expects. The cycle now
runs Idle1(0) -> RegisterGuild(1) -> Ready(6) -> Start(7) -> End(8) ->
EndCycle(9) -> Idle1(0).
- Idle2(2), RegisterMark(3), Idle3(4) and Notify(5) keep their numbers for client
compatibility but are never entered: AdaMu registers guilds directly and has no
Mark of Lord step.
Guild identity
- Guilds are now identified by their persistent Guid instead of by name, so a
rename (or a delete and re-create under the same name) can no longer hand
castle ownership to the wrong guild. Names are carried alongside only for
display and for the packets that send a name to the client.
- Interfaces.Guild deliberately has no id and the guild server's short ids are
in-memory only, so the persistent id is resolved through the guild name once
and cached per process. This avoids adding a method to IGuildServer, which
upstream keeps changing.
Persistence
- The castle owner is stored in the CastleSiegeData row and the registrations in
CastleSiegeGuildRegistration rows, replacing the previous plugin-configuration
JSON blob. Only the current state and when it started still ride on the plugin
configuration, because they have no column in the upstream schema.
Castle NPCs
- The hard-coded gate, catapult, crown and switch coordinates are gone. They are
read from GameConfiguration.CastleSiegeConfiguration, seeded by
CastleSiegeInitializer. Definitions flagged IsPersistedToDatabase are the
breakable defenses and count towards the throne, which additionally brings in
the 4 guardian statues the previous implementation did not spawn.
- The crown hold time now comes from the seeded configuration instead of the
plugin settings.
The AdaMu operational settings (cycle durations, registration fee, designated
server id, auto-open schedule) moved to a renamed CastleSiegeSettings class, so
they no longer collide with upstream's CastleSiegeConfiguration entity.
Verified: full server build succeeds with 0 errors.
Not yet done: the 0xB2 0x00 CastleSiegeState request handler, and the docker /
local run.