Skip to content

Commit 247b9c3

Browse files
Add Slot Change Section (#181)
1 parent 11b6e23 commit 247b9c3

File tree

4 files changed

+227
-32
lines changed

4 files changed

+227
-32
lines changed
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package io.github.apickledwalrus.skriptgui.elements.sections;
2+
3+
import ch.njol.skript.Skript;
4+
import ch.njol.skript.config.SectionNode;
5+
import ch.njol.skript.doc.Description;
6+
import ch.njol.skript.doc.Examples;
7+
import ch.njol.skript.doc.Name;
8+
import ch.njol.skript.doc.Since;
9+
import ch.njol.skript.lang.Expression;
10+
import ch.njol.skript.lang.Section;
11+
import ch.njol.skript.lang.SkriptParser.ParseResult;
12+
import ch.njol.skript.lang.Trigger;
13+
import ch.njol.skript.lang.TriggerItem;
14+
import ch.njol.skript.variables.Variables;
15+
import ch.njol.util.Kleenean;
16+
import io.github.apickledwalrus.skriptgui.SkriptGUI;
17+
import io.github.apickledwalrus.skriptgui.gui.GUI;
18+
import org.bukkit.event.Event;
19+
import org.bukkit.event.inventory.InventoryClickEvent;
20+
import org.eclipse.jdt.annotation.Nullable;
21+
22+
import java.util.List;
23+
24+
@Name("GUI Slot Change")
25+
@Description("Sections that will run when a gui slot is changed. This section is optional.")
26+
@Examples({
27+
"create a gui with virtual chest inventory with 3 rows named \"My GUI\"",
28+
"\trun when slot 12 changes:",
29+
"\t\tsend \"You changed slot 12!\" to player",
30+
"\trun on slot 14 changed:",
31+
"\t\tcancel event"
32+
})
33+
@Since("1.3")
34+
public class SecSlotChange extends Section {
35+
36+
static {
37+
Skript.registerSection(SecSlotChange.class,
38+
"run when [gui] slot[s] %integers% change[s]",
39+
"run when [gui] slot[s] %integers% [(are|is)] [being] changed",
40+
"run on change of [gui] slot[s] %integers%"
41+
);
42+
}
43+
44+
@SuppressWarnings("NotNullFieldNotInitialized")
45+
private Trigger trigger;
46+
private Expression<Integer> guiSlots;
47+
48+
@SuppressWarnings("unchecked")
49+
@Override
50+
public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult, SectionNode sectionNode, List<TriggerItem> triggerItems) {
51+
if (!getParser().isCurrentSection(SecCreateGUI.class)) {
52+
Skript.error("You can't listen for changes of a slot outside of a GUI creation or editing section.");
53+
return false;
54+
}
55+
56+
trigger = loadCode(sectionNode, "inventory click", InventoryClickEvent.class);
57+
58+
guiSlots = (Expression<Integer>) exprs[0];
59+
return true;
60+
}
61+
62+
@Override
63+
@Nullable
64+
public TriggerItem walk(Event e) {
65+
GUI gui = SkriptGUI.getGUIManager().getGUI(e);
66+
if (gui == null)
67+
return walk(e, false);
68+
69+
Integer[] slots = guiSlots.getAll(e);
70+
71+
for (Integer slot : slots) {
72+
if (slot >= 0 && slot + 1 <= gui.getInventory().getSize()) {
73+
Object variables = Variables.copyLocalVariables(e);
74+
GUI.SlotData slotData = gui.getSlotData(gui.convert(slot));
75+
if (variables != null && slotData != null) {
76+
slotData.setRunOnChange(event -> {
77+
Variables.setLocalVariables(event, variables);
78+
trigger.execute(event);
79+
});
80+
} else if (slotData != null) {
81+
slotData.setRunOnChange(trigger::execute);
82+
}
83+
}
84+
}
85+
86+
// We don't want to execute this section
87+
return walk(e, false);
88+
}
89+
90+
@Override
91+
public String toString(@Nullable Event e, boolean debug) {
92+
return "run on change of slot " + guiSlots.toString(e, debug);
93+
}
94+
95+
}

src/main/java/io/github/apickledwalrus/skriptgui/gui/GUI.java

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,15 @@ public void onClick(InventoryClickEvent e) {
3838
// Only cancel if this slot can't be removed AND all items aren't removable
3939
e.setCancelled(!isRemovable(slotData));
4040

41+
// Call onChange if the slot is being changed
42+
if (!e.isCancelled() && (e.getCursor() != null || e.getCurrentItem() != null)) {
43+
if (e.getCursor() == null || e.getCurrentItem() == null ||
44+
!e.getCursor().isSimilar(e.getCurrentItem()) ||
45+
e.getCurrentItem().getAmount() < e.getCurrentItem().getMaxStackSize()) {
46+
onChange(e);
47+
}
48+
}
49+
4150
Consumer<InventoryClickEvent> runOnClick = slotData.getRunOnClick();
4251
if (runOnClick != null) {
4352
SkriptGUI.getGUIManager().setGUI(e, GUI.this);
@@ -48,6 +57,28 @@ public void onClick(InventoryClickEvent e) {
4857
}
4958
}
5059

60+
@Override
61+
public void onChange(InventoryClickEvent e) {
62+
if (isPaused() || isPaused((Player) e.getWhoClicked())) {
63+
e.setCancelled(true); // Just in case
64+
return;
65+
}
66+
67+
SlotData slotData = getSlotData(convert(e.getSlot()));
68+
if (slotData != null) {
69+
// Only cancel if this slot can't be removed AND all items aren't removable
70+
e.setCancelled(!isRemovable(slotData));
71+
72+
Consumer<InventoryClickEvent> runOnChange = slotData.getRunOnChange();
73+
if (!e.isCancelled() && runOnChange != null) {
74+
SkriptGUI.getGUIManager().setGUI(e, GUI.this);
75+
runOnChange.accept(e);
76+
}
77+
} else { // If there is no slot data, cancel if this GUI doesn't have stealable items
78+
e.setCancelled(!isRemovable());
79+
}
80+
}
81+
5182
@Override
5283
public void onDrag(InventoryDragEvent e) {
5384
if (isPaused() || isPaused((Player) e.getWhoClicked())) {
@@ -61,6 +92,7 @@ public void onDrag(InventoryDragEvent e) {
6192
break;
6293
}
6394
}
95+
onChange(e);
6496
}
6597

6698
@Override
@@ -477,7 +509,7 @@ public String getID() {
477509
*/
478510
public void setID(@Nullable String id) {
479511
this.id = id;
480-
if (id == null && inventory.getViewers().size() == 0) {
512+
if (id == null && inventory.getViewers().isEmpty()) {
481513
SkriptGUI.getGUIManager().unregister(this);
482514
clear();
483515
}
@@ -500,6 +532,8 @@ public static final class SlotData {
500532

501533
@Nullable
502534
private Consumer<InventoryClickEvent> runOnClick;
535+
@Nullable
536+
private Consumer<InventoryClickEvent> runOnChange;
503537
private boolean removable;
504538

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

552+
@Nullable
553+
public Consumer<InventoryClickEvent> getRunOnChange() {
554+
return runOnChange;
555+
}
556+
518557
/**
519558
* Updates the consumer to run when a slot with this data is clicked. A null value may be used to remove the consumer.
520559
* @param runOnClick The consumer to run when a slot with this data is clicked.
@@ -523,12 +562,16 @@ public void setRunOnClick(@Nullable Consumer<InventoryClickEvent> runOnClick) {
523562
this.runOnClick = runOnClick;
524563
}
525564

565+
public void setRunOnChange(@Nullable Consumer<InventoryClickEvent> runOnChange) {
566+
this.runOnChange = runOnChange;
567+
}
568+
526569
/**
527570
* @return Whether this item can be removed from its slot, regardless of {@link GUI#isRemovable()}.
528571
* Please note that if {@link #getRunOnClick()} returns a non-null value, this method will <b>always</b> return false.
529572
*/
530573
public boolean isRemovable() {
531-
return runOnClick == null && removable;
574+
return removable;
532575
}
533576

534577
/**

src/main/java/io/github/apickledwalrus/skriptgui/gui/GUIEventHandler.java

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
package io.github.apickledwalrus.skriptgui.gui;
22

33
import org.bukkit.entity.Player;
4-
import org.bukkit.event.inventory.InventoryClickEvent;
5-
import org.bukkit.event.inventory.InventoryCloseEvent;
6-
import org.bukkit.event.inventory.InventoryDragEvent;
7-
import org.bukkit.event.inventory.InventoryOpenEvent;
4+
import org.bukkit.event.inventory.*;
85

96
import java.util.ArrayList;
107
import java.util.List;
@@ -68,8 +65,22 @@ public boolean isPaused(Player player) {
6865
}
6966

7067
public abstract void onClick(InventoryClickEvent e);
68+
public abstract void onChange(InventoryClickEvent e);
7169
public abstract void onDrag(InventoryDragEvent e);
7270
public abstract void onOpen(InventoryOpenEvent e);
7371
public abstract void onClose(InventoryCloseEvent e);
7472

73+
public void onChange(InventoryDragEvent e) {
74+
for (int slot : e.getInventorySlots()) {
75+
InventoryClickEvent clickEvent = new InventoryClickEvent(
76+
e.getView(),
77+
e.getView().getSlotType(slot),
78+
slot,
79+
ClickType.UNKNOWN,
80+
InventoryAction.UNKNOWN
81+
);
82+
onChange(clickEvent);
83+
}
84+
}
85+
7586
}

src/main/java/io/github/apickledwalrus/skriptgui/gui/events/GUIEvents.java

Lines changed: 72 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import io.github.apickledwalrus.skriptgui.SkriptGUI;
44
import io.github.apickledwalrus.skriptgui.gui.GUI;
5+
import io.github.apickledwalrus.skriptgui.gui.GUIEventHandler;
56
import org.bukkit.GameMode;
67
import org.bukkit.Material;
78
import org.bukkit.event.EventHandler;
@@ -15,6 +16,9 @@
1516
import org.bukkit.inventory.Inventory;
1617
import org.bukkit.inventory.ItemStack;
1718

19+
import java.util.ArrayList;
20+
import java.util.List;
21+
1822
public class GUIEvents implements Listener {
1923

2024
@EventHandler(priority = EventPriority.LOWEST)
@@ -43,6 +47,7 @@ public void onInventoryClick(InventoryClickEvent event) {
4347
if (gui == null) {
4448
return;
4549
}
50+
GUIEventHandler eventHandler = gui.getEventHandler();
4651

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

64-
if (!guiInventory.contains(clicked.getType())) {
65-
int firstEmpty = guiInventory.firstEmpty();
66-
if (firstEmpty != -1 && gui.isRemovable(gui.convert(firstEmpty))) { // Safe to be moved into the GUI
67-
return;
68-
}
69-
}
70-
7169
int size = guiInventory.getSize();
70+
int totalAmount = clicked.getAmount();
7271

7372
for (int slot = 0; slot < size; slot++) {
73+
if (totalAmount <= 0) {
74+
return;
75+
}
76+
7477
ItemStack item = guiInventory.getItem(slot);
75-
if (item != null && item.getType() != Material.AIR && item.isSimilar(clicked)) {
78+
if (item != null && item.getType() != Material.AIR && item.isSimilar(clicked) && item.getAmount() < item.getMaxStackSize()) {
79+
InventoryClickEvent clickEvent = setClickedSlot(event, slot);
80+
7681
if (!gui.isRemovable(gui.convert(slot))) {
77-
if (item.getAmount() == 64) { // It wouldn't be able to combine
78-
continue;
79-
}
8082
event.setCancelled(true);
8183
return;
84+
} else {
85+
eventHandler.onChange(clickEvent);
86+
totalAmount -= item.getMaxStackSize() - item.getAmount();
8287
}
83-
84-
if (item.getAmount() + clicked.getAmount() <= 64) { // This will only modify a modifiable slot
85-
return;
86-
}
87-
8888
}
89+
8990
}
9091

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

@@ -101,20 +104,21 @@ public void onInventoryClick(InventoryClickEvent event) {
101104
// Only cancel if this will cause a change to the GUI itself
102105
// We are checking if our GUI contains an item that could be merged with the event item
103106
// If that item is mergeable but it isn't stealable, we will cancel the event now
104-
Inventory guiInventory = gui.getInventory();
105-
int size = guiInventory.getSize();
106-
ItemStack cursor = event.getWhoClicked().getItemOnCursor();
107-
for (int slot = 0; slot < size; slot++) {
108-
ItemStack item = guiInventory.getItem(slot);
109-
if (item != null && item.isSimilar(cursor) && !gui.isRemovable(gui.convert(slot))) {
110-
event.setCancelled(true);
111-
break;
112-
}
113-
}
107+
handleDoubleClick(gui, event);
114108
return;
115109
default:
116110
return;
117111
}
112+
} else {
113+
// Call onChange if a slot is changed due to interactions within the gui itself
114+
if (event.getClick() == ClickType.DOUBLE_CLICK) {
115+
if (!gui.isRemovable(gui.convert(event.getSlot()))) { // Doesn't change the slots
116+
event.setCancelled(true);
117+
return;
118+
}
119+
120+
handleDoubleClick(gui, event);
121+
}
118122
}
119123

120124
gui.getEventHandler().onClick(event);
@@ -151,4 +155,46 @@ public void onInventoryClose(InventoryCloseEvent e) {
151155
}
152156
}
153157

158+
private void handleDoubleClick(GUI gui, InventoryClickEvent event) {
159+
GUIEventHandler eventHandler = gui.getEventHandler();
160+
161+
Inventory guiInventory = gui.getInventory();
162+
int size = guiInventory.getSize();
163+
ItemStack cursor = event.getCursor();
164+
165+
if (cursor == null || event.getCurrentItem() != null)
166+
return;
167+
168+
int totalAmount = cursor.getAmount();
169+
List<InventoryClickEvent> clickEvents = new ArrayList<>();
170+
for (int slot = 0; slot < size; slot++) {
171+
ItemStack item = guiInventory.getItem(slot);
172+
if (item != null && item.isSimilar(cursor)) {
173+
if (!gui.isRemovable(gui.convert(slot))) {
174+
event.setCancelled(true);
175+
return;
176+
}
177+
178+
if (totalAmount < cursor.getMaxStackSize()) {
179+
InventoryClickEvent clickEvent = setClickedSlot(event, slot);
180+
clickEvents.add(clickEvent);
181+
totalAmount += item.getAmount();
182+
}
183+
}
184+
}
185+
for (InventoryClickEvent clickEvent : clickEvents) {
186+
eventHandler.onChange(clickEvent);
187+
}
188+
}
189+
190+
private static InventoryClickEvent setClickedSlot(InventoryClickEvent event, int slot) {
191+
return new InventoryClickEvent(
192+
event.getView(),
193+
event.getSlotType(),
194+
slot,
195+
event.getClick(),
196+
event.getAction()
197+
);
198+
}
199+
154200
}

0 commit comments

Comments
 (0)