Adds the HeykelSavasiTeam enum (None/Red/Blue) and the HeykelSavasiContext
skeleton (subclass of MiniGameContext) with team registration
(AssignTeam/GetTeam/PlayersOf/TeamCount) and a join-balance rule
(|Red-Blue| <= 2 after the prospective join), exposed as a pure static
IsJoinAllowed for fast unit testing without constructing the full context.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds HeykelSavasiMap (BaseMapInitializer, Number=90/World91) as an empty
terrain-only skeleton and registers it in GameMapsInitializer. Statue/guard
monster definitions and spawns are added by later Heykel Savasi tasks.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Six high-tier imported wings, one per class line: Wizard (242, Soul Master),
Magic (243, Duel Master), Elf (244, High Elf), Summoner (245, Dimension Master),
Warrior Cape (246, Lord Emperor + Fist Master) and Knight (247, Blade Master).
Each: base defense + defense-per-level, 3rd Wing Options, damage increase and
fast flight. Applied via update v99 (AddWarriorWingsUpdateSeason6), reusing the
shared "3rd Wing Options" option set to avoid Guid collisions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Switch from the basic CreateWing overload to the full one: base
defense 65/60, defense-per-level, wing options (HP recover / phys
damage / defense), damage increase, fast flight. Level req 100.
Same pattern as Arkania: terrain resources Terrain84-87.att, map
initializer classes, registration, and update-plugin (v97) that
creates each map + spawn gate + /move warp AND assigns it to the
game server configuration so it is hosted. Also fix the Arkania
update-plugin to assign the game server config (was missing).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Client tried to load World84 for map 83; MU client world index is
server map number + 1 (Lorencia 0 -> World1). Arkania client assets
are World83, so the server map number is 82 and the terrain resource
is Terrain83.att (number+1).
End-to-end import of one pilot map across server (OpenMU C# map
initializer + Terrain83.att resource + DB update-plugin), PC client
(World83/Object83 assets), and web (no changes; MapEditor renders
seeded terrain, user adds spawns/NPCs). Other 4 maps + mobile are
follow-up from the proven template.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
MoveHomeAsync wrote PositionX/PositionY/CurrentMap/Rotation on the character
record directly, which is a partial copy of Player.PlaceAtGateAsync: it placed
the player but skipped removing him from the map and telling the client.
Player.Position is backed by those very fields, so the coordinates jumped on the
server while the client never got a map change. The client then interpolated a
walk to the new spot and the character visibly slid across the map after a reset.
WarpToAsync does the same placement plus the map removal and the map change
notification, and it handles respawning on the same map. It is the path every
other caller uses (duel room, gate NPCs, mini games, castle siege portal).
The existing tests all ran with MoveHome = false, which is why this path was
never covered. The new test pins the notification: it fails on the old code
because MapChangeAsync is never invoked.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The packet definition declares C2HeaderWithSubCode, but the committed
doc was generated from an older C1 header and never refreshed. This is
the doc generator's output for the current XML, so the tree stays clean
after a build.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Without RESOLVE_IP the ConnectServer advertises the public IP, which the
emulator cannot reach, so server select hangs. Default to 10.0.2.2 and
allow an override for real devices on the LAN.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two new weapons placed at free item slots so they never collide with
standard Season 6 items. The client name DB (Item_eng.bmd) is the source
of tooltip name/stats, so only free slots are usable for custom items.
Both seed paths are covered:
- Fresh DB: Weapons.Initialize() calls the new CreateImperialWeapons050().
- Existing DB: AddImperialWeapons050UpdateSeason6 (UpdateVersion 95),
applied from the AdminPanel Updates page, with an idempotency guard.
Imperial Sword is a Magic Gladiator weapon (copy of 0/28), Imperial Staff
is a Wizard weapon (copy of 5/31).
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 generic plugin-config editor couldn't edit IList<DayOfWeek> and showed durations
in milliseconds + the runtime Persisted* state. Add [JsonIgnore] proxy properties that
the AutoFields editor CAN render: OpenDays ([Flags] enum -> checkboxes), Registration/
Preparation/Siege minutes + CrownHold seconds (int inputs). Hide the underlying TimeSpan/
list/Persisted* fields with [Browsable(false)]. Runtime JSON shape is unchanged (proxies
are JsonIgnore) so no persisted-config break.
- 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>
Land of Trials (31) has no IsSpawnGate, so SafeZoneSpawnGate was null -> portal
said 'not available'. Fall back to the map's single entry ExitGate (60-69,10-19,
the real map entrance) so owner-guild members actually warp in.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Valley of Loren Guard NPC now warps castle-owning guild members into the
hunting ground (map 31); non-owners get the sealed/owner-only reason. Provides
the in-world entry point that was missing (talking to Guard was unimplemented).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ownership reward: only members of the current castle-owning guild may warp into
Land of Trials (map 31); sealed while unowned; GMs bypass. Enforced in both
WarpAction and WarpGateAction (// ADAMU-CUSTOM). Tax system deferred per user.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>