diff --git a/src/main/java/net/theevilreaper/aves/hotbar/HotBarLayout.java b/src/main/java/net/theevilreaper/aves/hotbar/HotBarLayout.java new file mode 100644 index 00000000..639ec818 --- /dev/null +++ b/src/main/java/net/theevilreaper/aves/hotbar/HotBarLayout.java @@ -0,0 +1,72 @@ +package net.theevilreaper.aves.hotbar; + +import net.minestom.server.entity.Player; +import net.minestom.server.item.ItemStack; + +import java.util.Arrays; + +/** + * Represents a hotbar layout consisting of nine-item slots (indices 0–8). + * Each slot is initially filled with {@link ItemStack#AIR} and can be + * overwritten individually via {@link #set(int, ItemStack)}. + *
+ * Use {@link #apply(Player)} to write the layout into a player's inventory. + * + * @author theEvilReaper + * @version 1.14.0 + * @since 0.1.0 + */ +public final class HotBarLayout { + + private static final int HOTBAR_SIZE = 9; + + private final ItemStack[] items = new ItemStack[HOTBAR_SIZE]; + + /** + * Creates a new layout with all slots set to {@link ItemStack#AIR}. + */ + public HotBarLayout() { + Arrays.fill(items, ItemStack.AIR); + } + + /** + * Places the given item at the specified hotbar slot. + * + * @param slot the slot index (0–8) + * @param item the item to place + * @return this layout, for chaining + * @throws IndexOutOfBoundsException if the slot is not in range 0–8 + */ + public HotBarLayout set(int slot, ItemStack item) { + if (slot < 0 || slot >= HOTBAR_SIZE) { + throw new IndexOutOfBoundsException("Slot must be between 0 and 8, got: " + slot); + } + items[slot] = item; + return this; + } + + /** + * Returns the item at the given hotbar slot. + * + * @param slot the slot index (0–8) + * @return the item at the slot + * @throws IndexOutOfBoundsException if the slot is not in range 0–8 + */ + public ItemStack get(int slot) { + if (slot < 0 || slot >= HOTBAR_SIZE) { + throw new IndexOutOfBoundsException("Slot must be between 0 and 8, got: " + slot); + } + return items[slot]; + } + + /** + * Writes this layout into the player's inventory (slots 0–8). + * + * @param player the player whose hotbar is updated + */ + public void apply(Player player) { + for (int i = 0; i < items.length; i++) { + player.getInventory().setItemStack(i, items[i]); + } + } +} diff --git a/src/main/java/net/theevilreaper/aves/hotbar/package-info.java b/src/main/java/net/theevilreaper/aves/hotbar/package-info.java new file mode 100644 index 00000000..a2357c99 --- /dev/null +++ b/src/main/java/net/theevilreaper/aves/hotbar/package-info.java @@ -0,0 +1,4 @@ +@NotNullByDefault +package net.theevilreaper.aves.hotbar; + +import org.jetbrains.annotations.NotNullByDefault; \ No newline at end of file diff --git a/src/test/java/net/theevilreaper/aves/hotbar/HotBarLayoutTest.java b/src/test/java/net/theevilreaper/aves/hotbar/HotBarLayoutTest.java new file mode 100644 index 00000000..84003172 --- /dev/null +++ b/src/test/java/net/theevilreaper/aves/hotbar/HotBarLayoutTest.java @@ -0,0 +1,79 @@ +package net.theevilreaper.aves.hotbar; + +import net.minestom.server.item.ItemStack; +import net.minestom.server.item.Material; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class HotBarLayoutTest { + + @Test + void testDefaultSlotsAreAir() { + var layout = new HotBarLayout(); + for (int i = 0; i < 9; i++) { + assertEquals(ItemStack.AIR, layout.get(i), "Slot " + i + " should default to AIR"); + } + } + + @Test + void testSetAndGet() { + var item = ItemStack.of(Material.DIAMOND); + var layout = new HotBarLayout().set(4, item); + assertEquals(item, layout.get(4)); + } + + @Test + void testSetIsChainable() { + var first = ItemStack.of(Material.COMPASS); + var second = ItemStack.of(Material.CLOCK); + var layout = new HotBarLayout() + .set(7, first) + .set(8, second); + + assertEquals(first, layout.get(7)); + assertEquals(second, layout.get(8)); + } + + @Test + void testSetOverwritesPreviousItem() { + var original = ItemStack.of(Material.STONE); + var replacement = ItemStack.of(Material.DIAMOND); + + var layout = new HotBarLayout() + .set(0, original) + .set(0, replacement); + + assertEquals(replacement, layout.get(0)); + } + + @Test + void testUntouchedSlotsRemainAir() { + var layout = new HotBarLayout() + .set(3, ItemStack.of(Material.COMPASS)) + .set(8, ItemStack.of(Material.CLOCK)); + + for (int i = 0; i < 9; i++) { + if (i != 3 && i != 8) { + assertEquals(ItemStack.AIR, layout.get(i), "Slot " + i + " should still be AIR"); + } + } + } + + @ParameterizedTest(name = "Invalid slot index: {0}") + @ValueSource(ints = {-1, 9, 10, -100, Integer.MAX_VALUE}) + void testSetWithInvalidSlotThrows(int slot) { + var layout = new HotBarLayout(); + assertThrows(IndexOutOfBoundsException.class, () -> layout.set(slot, ItemStack.AIR)); + } + + @ParameterizedTest(name = "Invalid get index: {0}") + @ValueSource(ints = {-1, 9, 10, -100, Integer.MAX_VALUE}) + void testGetWithInvalidSlotThrows(int slot) { + var layout = new HotBarLayout(); + assertThrows(IndexOutOfBoundsException.class, () -> layout.get(slot)); + } +}