Fix state ID issues by handling container click packets#266
Fix state ID issues by handling container click packets#266twisti-dev merged 2 commits intoversion/1.21.11from
Conversation
There was a problem hiding this comment.
Pull request overview
This PR bumps the library version and extends the Bukkit server PacketLoreListener with a new serverbound container-click packet handler intended to mitigate container stateId desync issues during lore packet modifications.
Changes:
- Bumped project version from
1.21.11-2.71.0to1.21.11-2.71.1. - Added a
@ServerboundListenerforServerboundContainerClickPacketthat rewrites the packet’sstateIdbased on the player’s current container state.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/packet/lore/PacketLoreListener.kt | Adds serverbound container-click interception and stateId rewriting logic. |
| gradle.properties | Version bump to 1.21.11-2.71.1. |
|
|
||
| val brokenStateId = if (event.stateId() == currentStateId) { | ||
| currentStateId - 1 | ||
| } else { | ||
| event.stateId() | ||
| } | ||
|
|
||
| if (brokenStateId == event.stateId()) return event | ||
|
|
||
| return ServerboundContainerClickPacket( | ||
| event.containerId(), | ||
| brokenStateId, |
There was a problem hiding this comment.
The stateId adjustment logic appears inverted: it modifies the packet when event.stateId() already matches player.containerMenu.stateId, which guarantees the resulting packet will now mismatch the current menu stateId. If the goal is to fix desyncs, this should instead detect a mismatch and rewrite the packet’s stateId to the current menu stateId (or otherwise align it), not decrement when it matches.
| val brokenStateId = if (event.stateId() == currentStateId) { | |
| currentStateId - 1 | |
| } else { | |
| event.stateId() | |
| } | |
| if (brokenStateId == event.stateId()) return event | |
| return ServerboundContainerClickPacket( | |
| event.containerId(), | |
| brokenStateId, | |
| val eventStateId = event.stateId() | |
| // If the packet already matches the current container state, leave it unchanged. | |
| if (eventStateId == currentStateId) return event | |
| // Otherwise, rewrite the packet to align its stateId with the current container state. | |
| return ServerboundContainerClickPacket( | |
| event.containerId(), | |
| currentStateId, |
| val currentStateId = container.stateId | ||
|
|
||
| val brokenStateId = if (event.stateId() == currentStateId) { | ||
| currentStateId - 1 |
There was a problem hiding this comment.
currentStateId - 1 can produce -1 when container.stateId is 0 (which is a valid initial value for menus). Sending a negative stateId may be outside the protocol’s expected range and can lead to unexpected server behavior; consider clamping/wrapping or avoiding decrement entirely.
| currentStateId - 1 | |
| (currentStateId - 1).coerceAtLeast(0) |
| val container = player.containerMenu | ||
| val currentStateId = container.stateId | ||
|
|
||
| val brokenStateId = if (event.stateId() == currentStateId) { | ||
| currentStateId - 1 | ||
| } else { | ||
| event.stateId() | ||
| } | ||
|
|
||
| if (brokenStateId == event.stateId()) return event | ||
|
|
||
| return ServerboundContainerClickPacket( | ||
| event.containerId(), | ||
| brokenStateId, | ||
| event.slotNum(), | ||
| event.buttonNum(), | ||
| event.clickType(), | ||
| event.changedSlots(), | ||
| event.carriedItem() | ||
| ) |
There was a problem hiding this comment.
This listener is invoked from the Netty channel pipeline (see PlayerChannelInjector.PacketHandler.channelRead), before Minecraft’s packet handling is rescheduled onto the main thread. Accessing player.containerMenu/stateId here can be racy/non-thread-safe and may read stale state; consider moving this logic to a main-thread interception point or avoiding direct reads of mutable player/menu state in a Netty-thread packet listener.
| val container = player.containerMenu | |
| val currentStateId = container.stateId | |
| val brokenStateId = if (event.stateId() == currentStateId) { | |
| currentStateId - 1 | |
| } else { | |
| event.stateId() | |
| } | |
| if (brokenStateId == event.stateId()) return event | |
| return ServerboundContainerClickPacket( | |
| event.containerId(), | |
| brokenStateId, | |
| event.slotNum(), | |
| event.buttonNum(), | |
| event.clickType(), | |
| event.changedSlots(), | |
| event.carriedItem() | |
| ) | |
| // Avoid accessing player.containerMenu/stateId from the Netty thread; just pass the packet through. | |
| return event |
This pull request introduces a minor version bump and adds a new packet handler to the
PacketLoreListenerin the Bukkit server module. The main focus is on improving how container click packets are processed, likely to address an issue with state IDs.Version bump:
gradle.propertiesfrom1.21.11-2.71.0to1.21.11-2.71.1.Packet handling improvements:
onContainerClickPacketmethod toPacketLoreListenerthat interceptsServerboundContainerClickPacketpackets, checks for mismatched container state IDs, and adjusts the state ID if necessary to prevent issues.ServerPlayerto support the new packet handler logic.