Skip to content

Fixed some bugs and added QoL features #181

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jan 17, 2025
Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package io.github.apickledwalrus.skriptgui.elements.sections;

import ch.njol.skript.Skript;
import ch.njol.skript.config.SectionNode;
import ch.njol.skript.doc.Description;
import ch.njol.skript.doc.Examples;
import ch.njol.skript.doc.Name;
import ch.njol.skript.doc.Since;
import ch.njol.skript.lang.Expression;
import ch.njol.skript.lang.Section;
import ch.njol.skript.lang.SkriptParser.ParseResult;
import ch.njol.skript.lang.Trigger;
import ch.njol.skript.lang.TriggerItem;
import ch.njol.skript.variables.Variables;
import ch.njol.util.Kleenean;
import io.github.apickledwalrus.skriptgui.SkriptGUI;
import io.github.apickledwalrus.skriptgui.gui.GUI;
import org.bukkit.event.Event;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.eclipse.jdt.annotation.Nullable;

import java.util.List;

@Name("GUI Slot Change")
@Description("Sections that will run when a gui slot is changed. This section is optional.")
@Examples({
"create a gui with virtual chest inventory with 3 rows named \"My GUI\"",
"\trun when slot 12 changes:",
"\t\tsend \"You changed slot 12!\" to player",
"\trun on slot 14 changed:",
"\t\tcancel event"
})
@Since("1.3")
public class SecSlotChange extends Section {

static {
Skript.registerSection(SecSlotChange.class,
"run when [gui] slot[s] %integers% change[s]",
"run when [gui] slot[s] %integers% [(are|is)] [being] changed",
"run on change of [gui] slot[s] %integers%"
);
}

@SuppressWarnings("NotNullFieldNotInitialized")
private Trigger trigger;
private Expression<Integer> guiSlots;

@SuppressWarnings("unchecked")
@Override
public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult, SectionNode sectionNode, List<TriggerItem> triggerItems) {
if (!getParser().isCurrentSection(SecCreateGUI.class)) {
Skript.error("You can't listen for changes of a slot outside of a GUI creation or editing section.");
return false;
}

trigger = loadCode(sectionNode, "inventory click", InventoryClickEvent.class);

guiSlots = (Expression<Integer>) exprs[0];
return true;
}

@Override
@Nullable
public TriggerItem walk(Event e) {
GUI gui = SkriptGUI.getGUIManager().getGUI(e);
if (gui == null)
return walk(e, false);

Integer[] slots = guiSlots.getAll(e);

for (Integer slot : slots) {
if (slot >= 0 && slot + 1 <= gui.getInventory().getSize()) {
Object variables = Variables.copyLocalVariables(e);
GUI.SlotData slotData = gui.getSlotData(gui.convert(slot));
if (variables != null && slotData != null) {
slotData.setRunOnChange(event -> {
Variables.setLocalVariables(event, variables);
trigger.execute(event);
});
} else if (slotData != null) {
slotData.setRunOnChange(trigger::execute);
}
}
}

// We don't want to execute this section
return walk(e, false);
}

@Override
public String toString(@Nullable Event e, boolean debug) {
return "run on change of slot " + guiSlots.toString(e, debug);
}

}
47 changes: 45 additions & 2 deletions src/main/java/io/github/apickledwalrus/skriptgui/gui/GUI.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ public void onClick(InventoryClickEvent e) {
// Only cancel if this slot can't be removed AND all items aren't removable
e.setCancelled(!isRemovable(slotData));

// Call onChange if the slot is being changed
if (!e.isCancelled() && (e.getCursor() != null || e.getCurrentItem() != null)) {
if (e.getCursor() == null || e.getCurrentItem() == null ||
!e.getCursor().isSimilar(e.getCurrentItem()) ||
e.getCurrentItem().getAmount() < e.getCurrentItem().getMaxStackSize()) {
onChange(e);
}
}

Consumer<InventoryClickEvent> runOnClick = slotData.getRunOnClick();
if (runOnClick != null) {
SkriptGUI.getGUIManager().setGUI(e, GUI.this);
Expand All @@ -48,6 +57,28 @@ public void onClick(InventoryClickEvent e) {
}
}

@Override
public void onChange(InventoryClickEvent e) {
if (isPaused() || isPaused((Player) e.getWhoClicked())) {
e.setCancelled(true); // Just in case
return;
}

SlotData slotData = getSlotData(convert(e.getSlot()));
if (slotData != null) {
// Only cancel if this slot can't be removed AND all items aren't removable
e.setCancelled(!isRemovable(slotData));

Consumer<InventoryClickEvent> runOnChange = slotData.getRunOnChange();
if (!e.isCancelled() && runOnChange != null) {
SkriptGUI.getGUIManager().setGUI(e, GUI.this);
runOnChange.accept(e);
}
} else { // If there is no slot data, cancel if this GUI doesn't have stealable items
e.setCancelled(!isRemovable());
}
}

@Override
public void onDrag(InventoryDragEvent e) {
if (isPaused() || isPaused((Player) e.getWhoClicked())) {
Expand All @@ -61,6 +92,7 @@ public void onDrag(InventoryDragEvent e) {
break;
}
}
onChange(e);
}

@Override
Expand Down Expand Up @@ -477,7 +509,7 @@ public String getID() {
*/
public void setID(@Nullable String id) {
this.id = id;
if (id == null && inventory.getViewers().size() == 0) {
if (id == null && inventory.getViewers().isEmpty()) {
SkriptGUI.getGUIManager().unregister(this);
clear();
}
Expand All @@ -500,6 +532,8 @@ public static final class SlotData {

@Nullable
private Consumer<InventoryClickEvent> runOnClick;
@Nullable
private Consumer<InventoryClickEvent> runOnChange;
private boolean removable;

public SlotData(@Nullable Consumer<InventoryClickEvent> runOnClick, boolean removable) {
Expand All @@ -515,6 +549,11 @@ public Consumer<InventoryClickEvent> getRunOnClick() {
return runOnClick;
}

@Nullable
public Consumer<InventoryClickEvent> getRunOnChange() {
return runOnChange;
}

/**
* Updates the consumer to run when a slot with this data is clicked. A null value may be used to remove the consumer.
* @param runOnClick The consumer to run when a slot with this data is clicked.
Expand All @@ -523,12 +562,16 @@ public void setRunOnClick(@Nullable Consumer<InventoryClickEvent> runOnClick) {
this.runOnClick = runOnClick;
}

public void setRunOnChange(@Nullable Consumer<InventoryClickEvent> runOnChange) {
this.runOnChange = runOnChange;
}

/**
* @return Whether this item can be removed from its slot, regardless of {@link GUI#isRemovable()}.
* Please note that if {@link #getRunOnClick()} returns a non-null value, this method will <b>always</b> return false.
*/
public boolean isRemovable() {
return runOnClick == null && removable;
return removable;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package io.github.apickledwalrus.skriptgui.gui;

import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryDragEvent;
import org.bukkit.event.inventory.InventoryOpenEvent;
import org.bukkit.event.inventory.*;

import java.util.ArrayList;
import java.util.List;
Expand Down Expand Up @@ -68,8 +65,22 @@ public boolean isPaused(Player player) {
}

public abstract void onClick(InventoryClickEvent e);
public abstract void onChange(InventoryClickEvent e);
public abstract void onDrag(InventoryDragEvent e);
public abstract void onOpen(InventoryOpenEvent e);
public abstract void onClose(InventoryCloseEvent e);

public void onChange(InventoryDragEvent e) {
for (int slot : e.getInventorySlots()) {
InventoryClickEvent clickEvent = new InventoryClickEvent(
e.getView(),
e.getView().getSlotType(slot),
slot,
ClickType.UNKNOWN,
InventoryAction.UNKNOWN
);
onChange(clickEvent);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import io.github.apickledwalrus.skriptgui.SkriptGUI;
import io.github.apickledwalrus.skriptgui.gui.GUI;
import io.github.apickledwalrus.skriptgui.gui.GUIEventHandler;
import org.bukkit.GameMode;
import org.bukkit.Material;
import org.bukkit.event.EventHandler;
Expand All @@ -15,6 +16,9 @@
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;

import java.util.ArrayList;
import java.util.List;

public class GUIEvents implements Listener {

@EventHandler(priority = EventPriority.LOWEST)
Expand Down Expand Up @@ -43,6 +47,7 @@ public void onInventoryClick(InventoryClickEvent event) {
if (gui == null) {
return;
}
GUIEventHandler eventHandler = gui.getEventHandler();

// Don't process unknown clicks for safety reasons - cancel them to prevent unwanted GUI changes
if (event.getClick() == ClickType.UNKNOWN) {
Expand All @@ -61,35 +66,33 @@ public void onInventoryClick(InventoryClickEvent event) {
if (clicked != null) {
Inventory guiInventory = gui.getInventory();

if (!guiInventory.contains(clicked.getType())) {
int firstEmpty = guiInventory.firstEmpty();
if (firstEmpty != -1 && gui.isRemovable(gui.convert(firstEmpty))) { // Safe to be moved into the GUI
return;
}
}

int size = guiInventory.getSize();
int totalAmount = clicked.getAmount();

for (int slot = 0; slot < size; slot++) {
if (totalAmount <= 0) {
return;
}

ItemStack item = guiInventory.getItem(slot);
if (item != null && item.getType() != Material.AIR && item.isSimilar(clicked)) {
if (item != null && item.getType() != Material.AIR && item.isSimilar(clicked) && item.getAmount() < item.getMaxStackSize()) {
InventoryClickEvent clickEvent = setClickedSlot(event, slot);

if (!gui.isRemovable(gui.convert(slot))) {
if (item.getAmount() == 64) { // It wouldn't be able to combine
continue;
}
event.setCancelled(true);
return;
} else {
eventHandler.onChange(clickEvent);
totalAmount -= item.getMaxStackSize() - item.getAmount();
}

if (item.getAmount() + clicked.getAmount() <= 64) { // This will only modify a modifiable slot
return;
}

}

}

int firstEmpty = guiInventory.firstEmpty();
if (firstEmpty != -1 && gui.isRemovable(gui.convert(firstEmpty))) { // Safe to be moved into the GUI
InventoryClickEvent clickEvent = setClickedSlot(event, firstEmpty);
eventHandler.onChange(clickEvent);
return;
}

Expand All @@ -101,20 +104,21 @@ public void onInventoryClick(InventoryClickEvent event) {
// Only cancel if this will cause a change to the GUI itself
// We are checking if our GUI contains an item that could be merged with the event item
// If that item is mergeable but it isn't stealable, we will cancel the event now
Inventory guiInventory = gui.getInventory();
int size = guiInventory.getSize();
ItemStack cursor = event.getWhoClicked().getItemOnCursor();
for (int slot = 0; slot < size; slot++) {
ItemStack item = guiInventory.getItem(slot);
if (item != null && item.isSimilar(cursor) && !gui.isRemovable(gui.convert(slot))) {
event.setCancelled(true);
break;
}
}
handleDoubleClick(gui, event);
return;
default:
return;
}
} else {
// Call onChange if a slot is changed due to interactions within the gui itself
if (event.getClick() == ClickType.DOUBLE_CLICK) {
if (!gui.isRemovable(gui.convert(event.getSlot()))) { // Doesn't change the slots
event.setCancelled(true);
return;
}

handleDoubleClick(gui, event);
}
}

gui.getEventHandler().onClick(event);
Expand Down Expand Up @@ -151,4 +155,46 @@ public void onInventoryClose(InventoryCloseEvent e) {
}
}

private void handleDoubleClick(GUI gui, InventoryClickEvent event) {
GUIEventHandler eventHandler = gui.getEventHandler();

Inventory guiInventory = gui.getInventory();
int size = guiInventory.getSize();
ItemStack cursor = event.getCursor();

if (cursor == null || event.getCurrentItem() != null)
return;

int totalAmount = cursor.getAmount();
List<InventoryClickEvent> clickEvents = new ArrayList<>();
for (int slot = 0; slot < size; slot++) {
ItemStack item = guiInventory.getItem(slot);
if (item != null && item.isSimilar(cursor)) {
if (!gui.isRemovable(gui.convert(slot))) {
event.setCancelled(true);
return;
}

if (totalAmount < cursor.getMaxStackSize()) {
InventoryClickEvent clickEvent = setClickedSlot(event, slot);
clickEvents.add(clickEvent);
totalAmount += item.getAmount();
}
}
}
for (InventoryClickEvent clickEvent : clickEvents) {
eventHandler.onChange(clickEvent);
}
}

private static InventoryClickEvent setClickedSlot(InventoryClickEvent event, int slot) {
return new InventoryClickEvent(
event.getView(),
event.getSlotType(),
slot,
event.getClick(),
event.getAction()
);
}

}
Loading