Skip to content

Commit

Permalink
Add Slot Change Section (#181)
Browse files Browse the repository at this point in the history
  • Loading branch information
MissingReports authored Jan 17, 2025
1 parent 11b6e23 commit 247b9c3
Show file tree
Hide file tree
Showing 4 changed files with 227 additions and 32 deletions.
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()
);
}

}

0 comments on commit 247b9c3

Please sign in to comment.