The only description of a chat command was its usage string, which is
meant to be read by a human. A user interface which wants to offer the
commands to a player needs the parts separately: the command, what it
does, and one entry per parameter.
Add ChatCommandInfo and ChatCommandParameterInfo, built from the
metadata which is already there - ChatCommandHelpAttribute for the
command and its required character status, ArgumentAttribute for the
short names and whether a parameter is required, ValidValuesAttribute
for the accepted values. Name and description come from the display
attribute, so they are returned in the language of the player.
The description which is passed to ChatCommandHelpAttribute was never
stored anywhere. Keep it as a fallback for commands which have no
display attribute, instead of discarding it.
The reset info command had its texts hard coded in English. It now
refers to resources like every other chat command does.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MSpK6jkyF8ZS5nYXyGwYxA
Two TvT (Heykel Savasi) event fixes found during mobile+PC client work:
- PeriodicTaskBasePlugIn: a forced start (GM /starths) no longer blocked while a
previous run's TaskDuration window is still open. When an event finished early
(team won / empty / before the 20-min duration) players are warped out but the
periodic state stayed Started with a future NextRunUtc, so the guard returned on
every tick and a re-start only worked after the full duration or a server
restart. Force-finish the stale run when _isStartForced so the same execution
falls through and prepares a fresh one; the NextRunUtc wait now applies to
scheduled runs only.
- HeykelSavasiTeamSelectHandlerPlugIn: IsEncryptionExpected => false. The AdaMu
client sends client-to-server packets unencrypted (like walk / animation /
talk-to-npc), so the server was dropping the team-select (JOIN) packet.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Bosses: 306/309/357/459 spawn once at (55,42)/(32,42)/(31,59)/(56,58) after 3 total statues broken.
- Guards/bosses spawn in a walkable BOX (retry) instead of a fixed point; several statue-guard
offsets landed on non-walkable terrain and silently failed, leaving statues unbreakable.
- Guard-alive gate uses the ACTUAL spawned count so a statue can always reach 0 -> breakable.
- HeykelSavasiStartPlugIn.OnStartedAsync force-disposes a lingering finished context so GM /starths
restarts the event immediately (no server restart).
- Statue-break + join messages translated to English.
A forced start (e.g. /starths) was blocked for the whole TaskDuration after a
run even though the event had already ended, so re-starting needed a server
restart. ForceStart now skips the 'previous event still running' guard;
scheduled auto-starts still respect it.
SendOpenedNotificationsAsync only looped per full minute, so a <1min entrance
(e.g. a 30s test window) sent no golden 'event opened' notification. Now sends
one up front when TotalMinutes==0. >=1min windows unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
With multiple game servers each has its own map instances, so the siege ran
independently on all of them - a guild could win uncontested on an empty server's
Valley of Loren. Now the siege (tick/spawn/battle/registration) runs only on the
server whose Id == config.CastleSiegeServerId (AdminPanel-editable). Other servers
skip the siege and just mirror the shared castle owner from config so the hunting-map
gate + castle flag rewards still work everywhere. The Guardsman on non-siege servers
tells players which server to switch to.
- The occupier can no longer re-register its own throne (canCapture = eligible != occupier),
which caused a repeating 60s panel loop; only a DIFFERENT guild can contest.
- The 60s registration panel (0xB2/0x15) is now sent ONLY to the master on the crown,
not broadcast to switch-holders/other players. Shield(0x16) + capture(0x18) still broadcast.
+1 test (15 total).
The shield packet pops a modal message box on the client; sending it every tick
re-opened the modal continuously and froze player input at siege start. Track the
shield state in the context and only push the packet when it actually flips.
Break all gates + hold both switches (defenses down) -> the Crown shield drops
(C1 B2 16=0). The guild master then stands on the Crown (176,212) and holds for
CrownHoldDuration (default 60s, client shows a 60s countdown via C1 B2 15) to
capture; capture broadcasts C1 B2 18 + golden text and sets the occupier. Losing a
switch or leaving the crown resets the hold (contestable until the siege timer ends).
Sinior (223) is now informational guidance. +2 tests (14 total).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move the auto-schedule from context-owned state to the plugin config (single source
of truth): RegistrationOpenDays + RegistrationOpenTimes + Registration/Preparation/
SiegeDuration are all editable in the AdminPanel plugin config and take effect live
(context refreshes its config reference each tick via UpdateConfiguration). /csschedule
now writes the config. Removed the duplicate Persisted schedule fields. 12 tests pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#1 Castle flags now show the owner guild's logo: resolve owner name -> guild logo
(cached), broadcast C1 B9 02 (32-byte mark) to players on the castle map every 15s.
#3 War atmosphere: spawn catapult NPCs 221/222 at siege start (client renders them
as siege weapons); the 0xB2/0x17 start flag already flips the map to warzone mode.
All raw S6 packets, no client changes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Client already renders the siege-map countdown but never received the packets.
New ICastleSiegeStatusViewPlugIn + RemoteView impl send the raw S6 packets:
C1 B2 17 (battle start/stop flag, arms the countdown) and C1 B2 1E (remaining
hour/minute). CastleSiegeEventPlugIn broadcasts them to players on the battle map
every 10s during Siege and sends stop at Settlement. No client changes. +1 test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The siege can now auto-open registration on scheduled days/time instead of only
manual /csphase. Schedule is context-owned state (like owner), persisted across
restarts via the same config-JSON path. New GM command /csschedule sets/views/clears
it (e.g. /csschedule Sunday 20:00, UTC). +2 unit tests (11 total).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CreateNewContext(config) returns a caching context whose returned config objects
aren't tracked by that context, so SetConfiguration+SaveChanges was a silent no-op
(DB row never updated). Load a fresh change-tracked PlugInConfiguration by id in a
non-caching typed context, rewrite the JSON, save. Adds a confirmation log line.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Castle Siege state was in-memory, so the castle owner (and thus the P4 hunting-map
reward) reset on every server restart/redeploy. Now the context marks itself dirty
on any persistable change (phase transition, registration, owner set); the plugin's
periodic tick writes a snapshot into its own PlugInConfiguration CustomConfiguration
(a JSON blob already stored in PostgreSQL - no schema migration) and restores it on
startup. Battle state (defenses/switches/occupier) stays transient. +2 unit tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>