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
72 changes: 72 additions & 0 deletions src/main/java/net/theevilreaper/aves/hotbar/HotBarLayout.java
Original file line number Diff line number Diff line change
@@ -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)}.
* <p>
* 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]);
}
}
}
4 changes: 4 additions & 0 deletions src/main/java/net/theevilreaper/aves/hotbar/package-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@NotNullByDefault
package net.theevilreaper.aves.hotbar;

import org.jetbrains.annotations.NotNullByDefault;
79 changes: 79 additions & 0 deletions src/test/java/net/theevilreaper/aves/hotbar/HotBarLayoutTest.java
Original file line number Diff line number Diff line change
@@ -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));
}
}