Skip to content

Commit 6599c5f

Browse files
committed
Add support for nicer interaction event handling of player inventories
1 parent 0c35762 commit 6599c5f

File tree

4 files changed

+109
-13
lines changed

4 files changed

+109
-13
lines changed

examples/src/main/kotlin/com/noxcrew/interfaces/example/ChangingTitleExampleInterface.kt

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
package com.noxcrew.interfaces.example
22

3-
import com.noxcrew.interfaces.drawable.Drawable
3+
import com.noxcrew.interfaces.drawable.Drawable.Companion.drawable
44
import com.noxcrew.interfaces.element.StaticElement
55
import com.noxcrew.interfaces.interfaces.Interface
66
import com.noxcrew.interfaces.interfaces.buildChestInterface
7-
import com.noxcrew.interfaces.interfaces.buildCombinedInterface
87
import com.noxcrew.interfaces.properties.interfaceProperty
98
import net.kyori.adventure.text.Component
109
import org.bukkit.Material
@@ -17,6 +16,9 @@ public class ChangingTitleExampleInterface : RegistrableInterface {
1716
override fun create(): Interface<*, *> = buildChestInterface {
1817
rows = 1
1918

19+
// Allow clicking the player inventory but not anything in the top inventory
20+
allowClickingOwnInventoryIfClickingEmptySlotsIsPrevented = true
21+
2022
val numberProperty = interfaceProperty(0)
2123
var number by numberProperty
2224

@@ -26,9 +28,11 @@ public class ChangingTitleExampleInterface : RegistrableInterface {
2628
val item = ItemStack(Material.STICK)
2729
.name("number -> $number")
2830

29-
pane[0, 4] = StaticElement(Drawable.drawable(item)) {
31+
pane[0, 4] = StaticElement(drawable(item)) {
3032
number += 1
3133
}
34+
35+
pane[0, 6] = StaticElement(drawable(Material.COMPASS))
3236
}
3337
}
3438
}

examples/src/main/kotlin/com/noxcrew/interfaces/example/ExamplePlugin.kt

+7
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,13 @@ public class ExamplePlugin : JavaPlugin(), Listener {
154154
}
155155

156156
private fun playerInterface() = buildPlayerInterface {
157+
// Use modern logic to only cancel the item interaction and not block interactions while
158+
// using this interface
159+
onlyCancelItemInteraction = true
160+
161+
// Prioritise block interactions!
162+
prioritiseBlockInteractions = true
163+
157164
withTransform { pane, _ ->
158165
val item = ItemStack(Material.COMPASS).name("interfaces example")
159166

interfaces/src/main/kotlin/com/noxcrew/interfaces/InterfacesListeners.kt

+89-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.noxcrew.interfaces
22

3+
import com.destroystokyo.paper.MaterialSetTag
4+
import com.destroystokyo.paper.MaterialTags
35
import com.github.benmanes.caffeine.cache.Cache
46
import com.github.benmanes.caffeine.cache.Caffeine
57
import com.noxcrew.interfaces.Constants.SCOPE
@@ -16,9 +18,11 @@ import io.papermc.paper.event.player.AsyncChatEvent
1618
import kotlinx.coroutines.launch
1719
import net.kyori.adventure.text.Component
1820
import org.bukkit.Bukkit
21+
import org.bukkit.Material
22+
import org.bukkit.NamespacedKey
23+
import org.bukkit.block.Block
1924
import org.bukkit.entity.HumanEntity
2025
import org.bukkit.entity.Player
21-
import org.bukkit.event.Cancellable
2226
import org.bukkit.event.Event
2327
import org.bukkit.event.EventHandler
2428
import org.bukkit.event.EventPriority
@@ -69,6 +73,60 @@ public class InterfacesListeners private constructor(private val plugin: Plugin)
6973
Reason.UNKNOWN,
7074
Reason.PLUGIN
7175
)
76+
77+
/** An incomplete set of blocks that have some interaction when clicked on. */
78+
private val CLICKABLE_BLOCKS: MaterialSetTag =
79+
MaterialSetTag(NamespacedKey("interfaces", "clickable-blocks"))
80+
.add(
81+
MaterialTags.WOODEN_DOORS,
82+
MaterialTags.WOODEN_TRAPDOORS,
83+
MaterialTags.FENCE_GATES,
84+
MaterialSetTag.BUTTONS,
85+
)
86+
// Add blocks with inventories
87+
.add(
88+
Material.CHEST,
89+
Material.ENDER_CHEST,
90+
Material.TRAPPED_CHEST,
91+
Material.BARREL,
92+
Material.FURNACE,
93+
Material.BLAST_FURNACE,
94+
Material.SMOKER,
95+
Material.CRAFTING_TABLE,
96+
Material.LOOM,
97+
Material.CARTOGRAPHY_TABLE,
98+
Material.ENCHANTING_TABLE,
99+
Material.SMITHING_TABLE,
100+
)
101+
.add(Material.LEVER)
102+
.add(Material.CAKE)
103+
// Add copper doors & trapdoors as they do not have their own tags
104+
.add(
105+
Material.COPPER_DOOR,
106+
Material.EXPOSED_COPPER_DOOR,
107+
Material.WEATHERED_COPPER_DOOR,
108+
Material.OXIDIZED_COPPER_DOOR,
109+
)
110+
.add(
111+
Material.WAXED_COPPER_DOOR,
112+
Material.WAXED_EXPOSED_COPPER_DOOR,
113+
Material.WAXED_WEATHERED_COPPER_DOOR,
114+
Material.WAXED_OXIDIZED_COPPER_DOOR,
115+
)
116+
.add(
117+
Material.COPPER_TRAPDOOR,
118+
Material.EXPOSED_COPPER_TRAPDOOR,
119+
Material.WEATHERED_COPPER_TRAPDOOR,
120+
Material.OXIDIZED_COPPER_TRAPDOOR,
121+
)
122+
.add(
123+
Material.WAXED_COPPER_TRAPDOOR,
124+
Material.WAXED_EXPOSED_COPPER_TRAPDOOR,
125+
Material.WAXED_WEATHERED_COPPER_TRAPDOOR,
126+
Material.WAXED_OXIDIZED_COPPER_TRAPDOOR,
127+
)
128+
// You can click signs to edit them
129+
.add(MaterialTags.SIGNS)
72130
}
73131

74132
/** Stores data for a single chat query. */
@@ -183,7 +241,11 @@ public class InterfacesListeners private constructor(private val plugin: Plugin)
183241
val view = convertHolderToInterfaceView(holder) ?: return
184242
val clickedPoint = clickedPoint(view, event) ?: return
185243
val isPlayerInventory = (event.clickedInventory ?: event.inventory).holder is Player
186-
handleClick(view, clickedPoint, event.click, event, event.hotbarButton, isPlayerInventory)
244+
245+
// Run base click handling
246+
if (handleClick(view, clickedPoint, event.click, event.hotbarButton, isPlayerInventory)) {
247+
event.isCancelled = true
248+
}
187249

188250
// If the event is not cancelled we add extra prevention checks if any of the involved
189251
// slots are not allowed to be modified!
@@ -263,6 +325,10 @@ public class InterfacesListeners private constructor(private val plugin: Plugin)
263325
setOpenInterface(event.player.uniqueId, null)
264326
}
265327

328+
/** Returns whether [block] will trigger some interaction if clicked with [item]. */
329+
private fun hasInteraction(block: Block, item: ItemStack): Boolean =
330+
CLICKABLE_BLOCKS.isTagged(block)
331+
266332
@EventHandler(priority = EventPriority.LOW)
267333
public fun onInteract(event: PlayerInteractEvent) {
268334
if (event.action == Action.PHYSICAL) return
@@ -271,6 +337,13 @@ public class InterfacesListeners private constructor(private val plugin: Plugin)
271337
val player = event.player
272338
val view = getOpenInterface(player.uniqueId) ?: return
273339

340+
// If we are prioritizing block interactions we assure they are not happening first
341+
if (view.builder.prioritiseBlockInteractions) {
342+
// This is a bit messy because Bukkit doesn't cleanly give access to the block interactions. If you are
343+
// using this setting feel free to PR more logic into this method.
344+
if (event.clickedBlock != null && hasInteraction(event.clickedBlock!!, event.item ?: ItemStack.empty())) return
345+
}
346+
274347
val clickedPoint = view.backing.relativizePlayerInventorySlot(
275348
if (event.hand == EquipmentSlot.HAND) {
276349
GridPoint.at(3, player.inventory.heldItemSlot)
@@ -289,7 +362,14 @@ public class InterfacesListeners private constructor(private val plugin: Plugin)
289362
return
290363
}
291364

292-
handleClick(view, clickedPoint, click, event, -1, true)
365+
if (handleClick(view, clickedPoint, click, -1, true)) {
366+
// Support modern behavior where we don't interfere with block interactions
367+
if (view.builder.onlyCancelItemInteraction) {
368+
event.setUseItemInHand(Event.Result.DENY)
369+
} else {
370+
event.isCancelled = true
371+
}
372+
}
293373
}
294374

295375
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
@@ -412,25 +492,23 @@ public class InterfacesListeners private constructor(private val plugin: Plugin)
412492
view: AbstractInterfaceView<*, *, *>,
413493
clickedPoint: GridPoint,
414494
click: ClickType,
415-
event: Cancellable,
416495
slot: Int,
417496
isPlayerInventory: Boolean
418-
) {
497+
): Boolean {
419498
// Determine the type of click, if nothing was clicked we allow it
420499
val raw = view.pane.getRaw(clickedPoint)
421500

422501
// Optionally cancel clicking on other slots
423502
if (raw == null) {
424503
if (view.builder.preventClickingEmptySlots && !(view.builder.allowClickingOwnInventoryIfClickingEmptySlotsIsPrevented && isPlayerInventory)) {
425-
event.isCancelled = true
504+
return true
426505
}
427-
return
506+
return false
428507
}
429508

430509
// Automatically cancel if throttling or already processing
431510
if (view.isProcessingClick || shouldThrottle(view.player)) {
432-
event.isCancelled = true
433-
return
511+
return true
434512
}
435513

436514
// Only allow one click to be processed at the same time
@@ -464,8 +542,9 @@ public class InterfacesListeners private constructor(private val plugin: Plugin)
464542

465543
// Update the cancellation state of the event
466544
if (completedClickHandler.cancelled) {
467-
event.isCancelled = true
545+
return true
468546
}
547+
return false
469548
}
470549

471550
/** Converts a bukkit [action] to a [ClickType]. */

interfaces/src/main/kotlin/com/noxcrew/interfaces/interfaces/InterfaceProperties.kt

+6
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ public open class InterfaceProperties<P : Pane> {
3333
/** A post-processor applied to all items placed in the inventory. */
3434
public var itemPostProcessor: ((ItemStack) -> Unit)? = {}
3535

36+
/** Whether interaction should only cancel the item effects and not the world effects. */
37+
public var onlyCancelItemInteraction: Boolean = false
38+
39+
/** Whether to prioritise block interactions over item interactions when right-clicking. */
40+
public var prioritiseBlockInteractions: Boolean = false
41+
3642
/** Whether clicking on empty slots should be cancelled. */
3743
public var preventClickingEmptySlots: Boolean = true
3844

0 commit comments

Comments
 (0)