Fix Menu click handler using wrong slot offset on page 2+ - #157
Merged
Conversation
Menu.open(int page) renders display slot i from virtual slot (page * inventory.getSize() + i), but MenuManager.onInventoryClick resolved clicks via Menu.getSlot(event.getSlot()) — the raw display slot with no page offset applied. On page 1 the two happened to coincide, but on page 2+ a click was routed to whatever Slot was registered at that same grid position on page 1, silently invoking the wrong handler instead of just failing to respond. Add Menu.getSlotOnCurrentPage(int), backed by a shared Menu.toVirtualSlot(page, pageSize, displaySlot) helper, and make both the renderer (open) and MenuManager's click handler go through it so they can't drift apart again. Nav buttons (getPrevButton/ getNextButton, PreviousPageButton/NextPageButton) were unaffected since they call menu.open(page ± 1) regardless of which Slot instance triggered them, which is why this presented as "content doesn't react" rather than a routing bug. Grepped the repo for other Menu.getSlot( callers and other raw-slot resolution paths (e.g. inventory drag handling): none exist. The other getSlot(...) call sites (YAMLMenu/YAMLListMenu) resolve a different overload, getSlot(String, T, Player), and are unrelated. Fixes #156
Travja
approved these changes
Aug 10, 2026
This was referenced Aug 11, 2026
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #156.
Menu/MenuManager's pagination framework had two places that disagreed about what a display slot index means, so clicks on page 2+ were routed to the wrongSlot.Menu.open(int page)) maps display slotito the virtual slotpage * inventory.getSize() + i.MenuManager.onInventoryClick) resolved clicks viaMenu.getSlot(event.getSlot())— the raw display slot, with no page offset.On page 1 (
page == 0) the two mappings coincide by accident, which is why this looked like a UX quirk rather than a bug. On page 2+ they diverge: the player sees the item at virtual slotpage*size + i, but clicking it invokes whateverSlotwas registered at virtual sloti— i.e. the same grid position on page 1. Since eachSlot's click handlers are typically closures over that slot's own data, this is a misrouted click, not a dead one: it can silently mutate whatever page 1's slot at that position was bound to.Page navigation buttons (
getPrevButton/getNextButton,PreviousPageButton/NextPageButton) were unaffected — they callmenu.open(getPage() ± 1)regardless of whichSlotinstance triggered them, so hitting "page 1's copy" of a nav button by mistake still behaved correctly.Fix
Went with the "single source of truth" option rather than patching the offset into
MenuManagerdirectly:Menu.getSlotOnCurrentPage(int displaySlot), backed by a new package-privateMenu.toVirtualSlot(page, pageSize, displaySlot)helper.MenuManager.onInventoryClicknow callsgetSlotOnCurrentPage(event.getSlot())instead of rawgetSlot(event.getSlot()).Menu.open(int page)'s render loop now also goes throughgetSlotOnCurrentPage(i)(settingthis.page = finalPagebefore the loop instead of after), so render and click resolve through the exact same code path and can't drift apart again — which is how this bug happened in the first place.Scope check
Grepped the whole repo (not just
codex-api) for other callers ofMenu.getSlot(: the only one wasMenuManager.onInventoryClick, now fixed. The othergetSlot(...)call sites (YAMLMenu/YAMLListMenu) are a different, unrelated overload —getSlot(String function, T parameter, Player player)— used to build slot definitions from YAML, not to resolve a raw Bukkit slot.Also checked for other raw-slot-to-
Slotresolution paths, e.g. drag handling:MenuManager.onInventoryDragonly cancels the event when the holder is aMenu; it never resolves aSlotobject, so it isn't affected by this bug.This is a framework-level fix scoped to
codex-api; no downstream consumers (e.g.divinity) were touched — they should just start working correctly once this ships.What I verified / couldn't verify
dev@d880ccb: confirmed the offset mismatch, confirmed the only two resolution sites (getSlotinMenuManager, and the render loop inMenu.open), confirmed nav buttons and drag handling are unaffected, confirmed the othergetSlotoverload is unrelated.MenuTest(codex-api/src/test/java/.../menu/MenuTest.java) coveringMenu.toVirtualSlot's page-offset arithmetic directly — the exact computation that diverged between render and click before this fix.hub.spigotmc.org,jitpack.io, and others used for WorldGuard/WorldEdit/Citizens/MythicMobs/PlaceholderAPI/etc.), and no artifacts beyond stray POM-lookup stubs were pre-cached locally, so neithermvn compilenormvn test(including the pre-existingReplacerTest/VersionManagerTest) could run here — this is a sandbox networking limitation, not something about the change itself. I'd appreciate a maintainer or CI confirming compilation and runningMenuTestfor real.Generated by Claude Code