Skip to content

feat: send the map's discriminator (Devil Square stage) to the extended client - #7

Open
Mosch0512 wants to merge 6 commits into
masterfrom
feat/map-discriminator
Open

Mosch0512 wants to merge 6 commits into
masterfrom
feat/map-discriminator

Conversation

@Mosch0512

@Mosch0512 Mosch0512 commented Oct 29, 2026 •

Copy link
Copy Markdown
Owner

MuMain needs to know which stage of Devil Square a player is in: its monster data can make a monster attack differently on one stage (map variants, MuMain's monsters design D67), and the map number alone does not tell the stages apart. OpenMU already does: the seven Devil Square stages are map definitions on number 9 (stages 1–4) and 32 (stages 5–7) with Discriminator 1 to 7. This sends that discriminator to the extended client.

The original MapChanged packet stays as it was (coding rules, rule 2): the extended client gets new or longer …Extended packets.

Commits

  1. feat(packets): MapChangedExtended carries the map's discriminator — MapChangedExtended (C3 1C 0F, 16 bytes) is MapChanged with MapDiscriminator appended at index 15, after the 15 bytes of the original. A client can tell by the length whether a server sends it: an older server sends the 15-byte MapChanged, whose bytes 10 to 14 are not initialized.
  2. feat(gameserver): map change and teleport send the discriminator to the extended client — MapChangeExtendedPlugIn and TeleportExtendedPlugIn ([MinimumClient(106, 3)]) send MapChangedExtended with the discriminator of the player's map; other clients keep MapChangePlugIn / TeleportPlugIn and the original packet. A discriminator which doesn't fit into a byte is sent as 0, not cut to the one of another map. Plugin names and descriptions in en, de and zh-CN.
  3. feat: the extended client gets the discriminator when it enters the world — CharacterInformationExtended (C3 F3 03) gets MapDiscriminator at index 92 and is 93 bytes long. A character which loses its connection in Devil Square can log in again and rejoin its stage without a map change; the mini game puts it on the stage's map before the character information is sent, so this is how the client learns the stage then.
  4. test(packets): ReadPackets moves through the packets in a while loop — the test's packet loop no longer moves a for counter in its body (Sonar S127, reported by Codacy on feat: send the map's discriminator (Devil Square stage) to the extended client MUnique/OpenMU#1346).

The structs, connection extensions and packet tests come from the project's XSLT transformations. The markdown documentation comes from GenerateMarkdownDocs.xslt, run with Saxon (the build runs it with npx xslt3); on master's XML it reproduces all 336 committed packet documents byte for byte.

Testing

  • MapDiscriminatorRemoteViewTests: a remote player logs in through PlayerTestHelper and warps to a Devil Square stage. The extended client gets MapChangedExtended with the stage for a map change, a teleport and a failed map change (also for discriminators 257 and -1, which it gets as 0), and CharacterInformationExtended with the stage when it enters the world on a stage. A Season 6 client gets the 15-byte MapChanged.
  • Each test fails when the three plugins send 0 instead of the discriminator.
  • MUnique.OpenMU.Network.Packets.Tests: 650/650.
  • MUnique.OpenMU.Tests: 2507 passed, 2 skipped, 3 failed. The 3 are Discord text tests (GuildShowsMasterAndOnlineMembersAsync, WhoShowsCharacterAsync, SetupReportListsCreatedAdoptedAndFailed), which expect \n and get \r\n on Windows; they don't touch this change.
  • Each of the first three commits builds on its own; after the fourth, MapDiscriminatorRemoteViewTests passes again (5/5).

The MuMain side (Mosch0512/MuMain#135) has to follow: read the discriminator at index 15 of a 16-byte C3 1C 0F, and at index 92 of a 93-byte C3 F3 03.

Mosch0512 added a commit to Mosch0512/MuMain that referenced this pull request Oct 29, 2026
Devil Square's stages and Play in Monster Attacks, asked while the attacks
PR was open:

- Q-MN70: OpenMU sends the extended client the discriminator of its map in
  MapChanged byte 10 (Mosch0512/OpenMU#7), the stage of Devil Square; other
  clients get 0 there as before. Q-MN66's "not needed" changes.
- Q-MN71: map lists name a stage as {"map": 9, "stage": 2}; a number names
  all its stages; the picker shows the seven stages one by one.
- Q-MN72: a group's levels are maps of their own or stages of one map.
- Q-MN73: Play on each skill row and Play next attack, in Monster Preview
  with the attack sounds; skill effects come with the skill tools.
Mosch0512 added a commit to Mosch0512/MuMain that referenced this pull request Oct 29, 2026
Devil Square 1-4 are stages of map 9 and 5-7 of map 32, so data could pick
"Devil Square 1-4" but not "Devil Square 2" (Q-MN70, Q-MN71).

- MapChanged (C3 1C 0F) carries the stage at byte 10 from OpenMU to the
  extended client (Mosch0512/OpenMU#7); GetTeleportStage reads it when the
  packet is long enough. The map manager keeps it next to the server map
  (GetServerStage, 0 where WorldActive was set alone); entering the world
  and a revival set 0.
- Every map list in data (map variants, mapTextureFolders, a part's maps,
  soundEventMaps) may name a stage, {"map": 9, "stage": 2}, next to plain
  numbers; a number names all its stages. Data::Maps::MapRef reads, writes
  and matches them in one place. A map and its stage in two variants of a
  monster is fatal like a map twice.
- The map picker lists Devil Square's seven stages in its row; any of them
  can be picked, a map whose every stage is picked is written as its
  number, and the row's info names the stages.
GameMapDefinition.Discriminator tells maps with the same number apart:
the stages of Devil Square are seven definitions on maps 9 (1 to 4) and
32 (5 to 7). The extended client needs it to know the stage it is in.

MapChangedExtended (C3 1C 0F) is MapChanged with the discriminator
appended at index 15, after the 15 bytes of the original, which stays
as it was. A client can tell by the length whether a server sends it:
an older server sends the 15-byte MapChanged, whose bytes 10 to 14 are
not initialized.

The structs, the connection extension and the packet tests come from
the XSLT transformations of the project, the markdown documentation
from GenerateMarkdownDocs.xslt run with Saxon (the build runs it with
npx xslt3).
…he extended client

MuMain (the extended client, 106.3) needs to know which stage of Devil
Square a player is in: its monster data can make a monster attack
differently on one stage, and the map number alone does not tell the
stages of a map apart.

MapChangeExtendedPlugIn and TeleportExtendedPlugIn send MapChangedExtended
with the discriminator of the player's map. Other clients keep
MapChangePlugIn and TeleportPlugIn, and the original MapChanged. A
discriminator which doesn't fit into a byte is sent as 0, instead of
being cut to the one of another map.

Tests: the extended client gets the discriminator with a map change, a
teleport and a failed map change, a Season 6 client the original packet.
PlayerTestHelper can now log in any player, so the remote player of the
tests enters the world the usual way instead of getting its character
set by reflection.
…inator

A character which loses its connection during Devil Square can log in
again and rejoin the game on its stage. That needs no map change: the
mini game puts the character on the map of its stage before the
character information is sent, so MapChangedExtended never tells the
client the stage then.

CharacterInformationExtended (C3 F3 03) gets the discriminator at
index 92, which makes it 93 bytes long, and UpdateCharacterStatsExtendedPlugIn
sends the one of the character's map. As with MapChangedExtended, a
client can tell by the length whether a server sends it.

The structs, the connection extension and the packet tests come from
the XSLT transformations of the project, the markdown documentation
from GenerateMarkdownDocs.xslt run with Saxon.

Test: the extended client gets the discriminator when it enters the
world on a stage of Devil Square.
@Mosch0512
Mosch0512 force-pushed the feat/map-discriminator branch from 36c2e9c to 4d58c21 Compare October 29, 2026 12:36
Mosch0512 added a commit to Mosch0512/MuMain that referenced this pull request Oct 29, 2026
…ing the world

Mosch0512/OpenMU#7 no longer writes the discriminator into byte 10 of the
original MapChanged, which stays as it was (OpenMU's coding rule 2). The
extended client gets it at the end of two packets that are one byte longer
than the originals:

- MapChangedExtended (C3 1C 0F, 16 bytes) at 15, with a map change or a
  teleport. GetTeleportStage reads it there; the 15-byte MapChanged has
  none, and its bytes 10 to 14 aren't initialized.
- CharacterInformationExtended (C3 F3 03, 93 bytes) at 92, when the
  character enters the world. A character which lost its connection in
  Devil Square logs in again on its stage's map without a map change, so
  entering the world sets the stage from the packet (GetJoinMapServerStage)
  instead of 0, before LoadWorld loads the stage's map parts. The struct
  stays 92 bytes, so safe_cast still takes the packet of a server which
  doesn't send the stage.

Q-MN70 in the design and docs/monster-data.md name the two packets.
The for loop moved its counter in the body (Sonar S127, reported by Codacy
on MUnique#1346). Same packets, same failure without a header.
The generated senders write only the fields of a packet, so its unused
bytes kept what an earlier packet left in the reused output buffer:
bytes 10 to 14 of MapChangedExtended and byte 89 of
CharacterInformationExtended (review of MUnique#1346).

The map change, the teleport and the character information of the
extended client write into a span which is cleared first, as
OpenNpcWindowPlugIn does. The remote view tests write into memory which
is filled with 0xFF, and a test checks the unused bytes.
CancelPendingFlush and Complete had empty bodies (Sonar S1186) and FlushAsync
ignored its cancellation token (Sonar S1172), reported by Codacy on
MUnique#1346. The empty methods now say why nothing happens, and a
flush with a cancelled token throws, as it does on a real pipe.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant