Skip to content

feat(bedrock): render confirm and text input as native forms - #28

Open
hbrombeer wants to merge 1 commit into
mainfrom
feat/bedrock-forms
Open

feat(bedrock): render confirm and text input as native forms#28
hbrombeer wants to merge 1 commit into
mainfrom
feat/bedrock-forms

Conversation

@hbrombeer

Copy link
Copy Markdown
Member

Implements variant A from library-gui — Bedrock GUI Concept.

Why translation is not an option

A themed GUI draws its panels with private-use glyphs positioned by negative advance, composited by a Java core shader pinned to PackFormat(88). Geyser translates containers, not rendering — it can show a Bedrock player 54 slots and the items in them, but it runs no Java core shader and carries neither the font nor the advance model. What arrives is a bare container with missing-glyph text where the interface should be. Broken, not plainer.

What this changes

confirmGui modal form. The contract already matches: onCancel runs on dismissal too, which is exactly the form's close semantics
anvilInput custom form with one input. The only place Bedrock ends up better served than Java, whose path needs the client to send its rename field on roughly every keystroke and only once slot 0 holds an item — the existing doc comment already concedes it is "unverified against a live client"

Both keep their signatures. The branch lives in an overridden open(), so every existing call site is untouched and the choice of surface is made where it belongs rather than by the caller.

Floodgate is not a dependency

That looked like the blocker — Floodgate ships no Minestom implementation. It turns out not to matter: Floodgate's own backend-to-client path is the floodgate:form plugin-message channel, and its proxy handler forwards a message from a server straight to the client, then routes the response back whenever the form id has the 0x8000 bit clear. That bit means "sent by a proxy", so a backend keeps it clear and owns its own id space.

Read out of FormChannel.createFormData in GeyserMC/Floodgate, not assumed:

send      byte 0     form type   0 = form, 1 = modal, 2 = custom_form
          byte 1..2  form id, big-endian short, high bit clear
          byte 3..N  the form's JSON, UTF-8

response  byte 0..1  form id
          byte 2..N  response data, UTF-8

Verification

  • 8 new tests pin the wire format against Cumulus's own codecs — every key and type value read out of ModalFormCodec, CustomFormCodec, FormType and ComponentType. A rename upstream is a form that silently never appears, which is a test's job to catch rather than a player's.
  • Escaping is covered explicitly: a player who types a quote would otherwise truncate the JSON, and the client drops the form with nothing in any log.
  • Full existing suite green; spotlessCheck clean.

Still to verify against a live client

Reading a protocol is not running it. Two things this PR cannot prove on its own: that Velocity forwards the channel from a Minestom backend without dropping it, and that Geyser accepts a form whose id was minted by something other than Floodgate. Bedrock is live in stage, so both are one join away from an answer.

BedrockForms.install() must be called once at startup, or forms are sent and no response ever arrives.

Deliberately not covered

Menus in general still need ordering and labels the slot-based DSL does not carry, and genuinely spatial GUIs have no form equivalent at all. Both keep rendering as translated containers — see the concept page's option B for when that changes.

A themed GUI cannot be translated to Bedrock. The theme draws its panels with
private-use glyphs positioned by negative advance and composited by a Java
core shader; Geyser translates containers, not rendering, and the Bedrock
client has neither. What arrives is a bare container with missing-glyph text
where the interface should be — broken rather than plainer.

Bedrock's native answer is a form, and the two constructs with exact form
equivalents are also the two worst on Bedrock today:

  confirmGui  -> modal form.  Its contract already matches: onCancel runs on
                 dismissal too, which is the form's own close semantics.
  anvilInput  -> custom form with one input. The only place Bedrock ends up
                 better served than Java, whose path needs the client to send
                 its rename field on roughly every keystroke and only once
                 slot 0 holds an item.

Both keep their signatures. The branch lives in an overridden open(), so every
existing call site is untouched and the choice of surface is made where it
belongs rather than by the caller.

Floodgate is NOT a dependency, and does not need to be. Its own
backend-to-client path is the floodgate:form plugin-message channel: the proxy
forwards a message from a server straight to the client, and routes the
response back whenever the form id has the 0x8000 bit clear — that bit means
"sent by a proxy", so a backend keeps it clear and owns its own id space. Read
out of FormChannel.createFormData rather than assumed.

The wire format is pinned by tests against Cumulus's own codecs, including the
escaping: a player who types a quote would otherwise truncate the JSON and the
form would be dropped by the client with nothing in any log.

Not covered here, deliberately: menus in general still need ordering and
labels the slot-based DSL does not carry, and genuinely spatial GUIs have no
form equivalent at all. Both stay translated containers.
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