You can create slots where the player can put items.
Create and add slots like any other component.
private static final Slot SLOT_1 = new Slot(5, 1);
private static final Slot SLOT_2 = new Slot(6, 1);
private static final Slot SLOT_3 = new Slot(7, 2);
public static final CustomGuiType TYPE = CustomGuiType.builder()
.title(Component.text("Example GUI"))
.height(3)
.add(SLOT_1, SLOT_2, SLOT_3)
.build();
Praeter will handle item movement automatically.
You can access the item currently in a slot from the slot's state:
SLOT_1.get(this).getItemStack();
You can also change the item by calling setItemStack
, for example in the
constructor to set the starting item that the player can take.
You may also create a StaticSlot
that the player can not change, but retains
the same appearance as a slot.
You may want to create a group of slots, for example to store many items.
Instead of creating each slot individually, which can get quite repetitive,
you can use a SlotGroup
.
public class ExampleGui extends CustomGui {
private static final SlotGroup<Slot> LEFT_SLOTS = new SlotGroup(0, 0, 3, 6, Slot::new);
private static final SlotGroup<Slot> RIGHT_SLOTS = new SlotGroup(6, 0, 3, 6, Slot::new);
public static final CustomGuiType TYPE = CustomGuiType.builder()
.title(Component.text("A lot of slots."))
.height(6)
.add(LEFT_SLOTS, RIGHT_SLOTS)
.build();
public ExampleGui() {
super(TYPE);
LEFT_SLOTS.getSlot(0).get(this).setItemStack(new ItemStack(Material.DIAMOND));
}
}
This creates a total of 36 slots. They can be accessed by their index by
calling getSlot(...)
, which returns the Slot
. The index is relative
to the slot group.
You can change Slot::new
to StaticSlot::new
, or your own Slot
subclass,
to create instances of that instead.
You can create custom slots to customize which items are allowed in the slot, when the slot can be changed, and to call a method when the slot is changed.
The following is a slot where the player can only insert diamonds.
public class DiamondSlot extends Slot {
public DiamondSlot(int x, int y) {
super(x, y);
}
@Override
public boolean canHold(@NotNull ItemStack itemStack) {
return itemStack.getType() == Material.DIAMOND;
}
}
The following is a slot that the player cannot change. The item stack can only
be changed by calling setItemStack
on the state.
public class StaticSlot extends Slot {
public StaticSlot(int x, int y) {
super(x, y);
}
@Override
public boolean mayChange(HumanEntity player) {
return false;
}
}
The methods above are on the component, but sometimes you may want to create a slot where for example changing it is sometimes possible, and sometimes not. The methods therefore also exist on the state and can be overridden there.
public class BucketSlot extends Slot {
private FontSequence lock;
public BucketSlot(int x, int y) {
super(x, y);
}
@Override
public void onSetup(RenderSetupContext context) throws IOException {
// Call super to render the normal slot
super.onSetup(context);
// Render a bucket outline
context.getBackground().drawImage(new NamespacedKey("example", "gui/bucket_outline.png"), 1, 1);
// Prepare the lock icon
this.lock = context.newFontSequence()
.drawImage(new NamespacedKey("example", "gui/lock.png"), 1, 1)
.build();
}
public enum BucketType {
LAVA,
WATER
}
@Override
public Slot.State createState() {
return new BucketSlot.State();
}
@Override
public BucketSlot.State get(CustomGui gui) {
return (State) super.get(gui);
}
public class State extends Slot.State {
private BucketType bucketType = BucketType.WATER;
private boolean locked;
@Override
public void onRender(RenderDispatcher renderDispatcher) {
// We don't need super.onRender, the background is already rendered automatically
if (locked) {
renderDispatcher.render(lock);
}
}
// Note how these methods are now overridden in Slot.State, not Slot
@Override
public boolean canHold(@NotNull ItemStack itemStack) {
return switch (this.bucketType) {
case WATER -> itemStack.getType() == Material.WATER_BUCKET;
case LAVA -> itemStack.getType() == Material.LAVA_BUCKET;
};
}
@Override
public boolean mayChange(HumanEntity player) {
return !locked || player.hasPermission("example.override_lock");
}
// getters, setters ...
}
}
This component can dynamically determine whether water or lava buckets can be
placed in the slot and can also be locked. When that happens a lock icon will
appear and the player can no longer change the contents of the slot (nether add
nor remove items). Unless the player has the example.override_lock
permission,
in which case they can always change the slot. Note that items always display on
top, so the lock may not be visible if there are items in the way.
Code for GUI
public class BucketGui extends CustomGui {
public static final BucketSlot BUCKET_SLOT = new BucketSlot(4, 0);
public static final Button CHANGE_TYPE = new Button("Change type", 0, 0, 4, 1);
public static final Button TOGGLE_LOCK = new Button("Toggle lock", 5, 0, 4, 1);
public static final CustomGuiType TYPE = CustomGuiType.builder()
.title(Component.text("Bucket Slot Gui"))
.height(2)
.add(BUCKET_SLOT, CHANGE_TYPE, TOGGLE_LOCK)
.build();
public BucketGui() {
super(TYPE);
BucketSlot.State bucketSlot = BUCKET_SLOT.get(this);
CHANGE_TYPE.get(this).setOnClick(context -> {
bucketSlot.setBucketType(
bucketSlot.getBucketType() == BucketSlot.BucketType.WATER
? BucketSlot.BucketType.LAVA
: BucketSlot.BucketType.WATER
);
});
TOGGLE_LOCK.get(this).setOnClick(context -> {
bucketSlot.setLocked(!bucketSlot.isLocked());
update();
});
}
}
Additionally, there is an onChange
method on the Slot.State
. This method is
called whenever the item in the slot changes.
...
public class State extends Slot.State {
@Override
public void onChange(HumanEntity player) {
ItemStack itemStack = getItemStack();
if (itemStack != null && itemStack.getType() == Material.TNT) {
player.getLocation().createExplosion(3);
}
}
}
The method is called after the change has happened (meaning getItemStack
returns the new item), and the gui will update
shortly after to render the new changes.