Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,26 @@ public Slot getSlot(int i) {
return slots.get(i);
}

/**
* Resolves a raw display slot (0..inventory size, as seen by the player/Bukkit)
* to the {@link Slot} bound to it on the currently open page. This applies the
* same page offset that {@link #open(int)} uses to render the page, so click
* handling and rendering can't drift out of sync.
*/
@Nullable
public Slot getSlotOnCurrentPage(int displaySlot) {
return slots.get(toVirtualSlot(this.page, this.inventory.getSize(), displaySlot));
}

/**
* Maps a page-relative display slot to its virtual index in {@link #slots}.
* Shared by {@link #open(int)} (render) and {@link #getSlotOnCurrentPage(int)}
* (click) so the two can't disagree about what a display slot means.
*/
static int toVirtualSlot(int page, int pageSize, int displaySlot) {
return page * pageSize + displaySlot;
}

public void openSync() {
new BukkitRunnable() {
@Override
Expand All @@ -106,17 +126,16 @@ public void open(int page) {
inventory = Bukkit.createInventory(Menu.this, rows * 9, title
.replace("%page%", String.valueOf(finalPage + 1))
.replace("%pages%", String.valueOf(getPages())));
Menu.this.page = finalPage;
for (int i = 0, last = Menu.this.inventory.getSize(); i < last; i++) {
Slot slot = slots.get(finalPage * Menu.this.inventory.getSize() + i);
Slot slot = getSlotOnCurrentPage(i);
if (slot != null) {
inventory.setItem(i, slot.getItemStack());
}
}
Menu.this.opening = true;
player.openInventory(inventory);
Menu.this.opening = false;
Menu.this.page = finalPage;

}

public void openSubMenu(Menu menu) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public void onInventoryClick(InventoryClickEvent event) {
InventoryHolder holder = inventory.getHolder();
if (holder instanceof Menu) {
event.setCancelled(true);
Slot slot = ((Menu) holder).getSlot(event.getSlot());
Slot slot = ((Menu) holder).getSlotOnCurrentPage(event.getSlot());
if (slot != null) {
switch (event.getClick()) {
case LEFT -> slot.onLeftClick();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package studio.magemonkey.codex.manager.api.menu;

import org.junit.jupiter.api.Test;

/**
* Covers the page-offset math shared by {@link Menu#open(int)} (render) and
* {@link Menu#getSlotOnCurrentPage(int)} (click resolution). Regression test for
* https://github.com/magemonkeystudio/codex/issues/156, where render and click
* disagreed about what a display slot meant on page 2+.
*/
class MenuTest {

@Test
void toVirtualSlot_firstPageIsUnshifted() {
assert Menu.toVirtualSlot(0, 54, 0) == 0;
assert Menu.toVirtualSlot(0, 54, 5) == 5;
assert Menu.toVirtualSlot(0, 54, 53) == 53;
}

@Test
void toVirtualSlot_laterPagesAreShiftedByPageSize() {
assert Menu.toVirtualSlot(1, 54, 0) == 54;
assert Menu.toVirtualSlot(1, 54, 5) == 59;
assert Menu.toVirtualSlot(2, 54, 5) == 113;
}

@Test
void toVirtualSlot_matchesAcrossRenderAndClickForSameDisplaySlot() {
// The exact scenario from #156: a display slot on page 2 must resolve to the
// same virtual slot whether it's being rendered (open) or clicked (MenuManager).
int pageSize = 9;
int displaySlot = 3;
int page = 2;

int renderVirtualSlot = Menu.toVirtualSlot(page, pageSize, displaySlot);
int clickVirtualSlot = Menu.toVirtualSlot(page, pageSize, displaySlot);

assert renderVirtualSlot == clickVirtualSlot;
assert renderVirtualSlot == 21;
}

@Test
void toVirtualSlot_differentPagesDoNotCollideForSameDisplaySlot() {
int pageSize = 9;
int displaySlot = 3;

int page1Slot = Menu.toVirtualSlot(0, pageSize, displaySlot);
int page2Slot = Menu.toVirtualSlot(1, pageSize, displaySlot);

assert page1Slot != page2Slot;
}
}
Loading