@@ -10,6 +10,7 @@ import com.noxcrew.interfaces.grid.GridPoint
10
10
import com.noxcrew.interfaces.pane.PlayerPane
11
11
import com.noxcrew.interfaces.utilities.runSync
12
12
import com.noxcrew.interfaces.view.AbstractInterfaceView
13
+ import com.noxcrew.interfaces.view.ChestInterfaceView
13
14
import com.noxcrew.interfaces.view.InterfaceView
14
15
import com.noxcrew.interfaces.view.PlayerInterfaceView
15
16
import io.papermc.paper.event.player.AsyncChatEvent
@@ -24,13 +25,16 @@ import org.bukkit.event.EventHandler
24
25
import org.bukkit.event.EventPriority
25
26
import org.bukkit.event.Listener
26
27
import org.bukkit.event.block.Action
28
+ import org.bukkit.event.entity.PlayerDeathEvent
27
29
import org.bukkit.event.inventory.ClickType
28
30
import org.bukkit.event.inventory.InventoryClickEvent
29
31
import org.bukkit.event.inventory.InventoryCloseEvent
30
32
import org.bukkit.event.inventory.InventoryCloseEvent.Reason
31
33
import org.bukkit.event.inventory.InventoryOpenEvent
34
+ import org.bukkit.event.player.PlayerDropItemEvent
32
35
import org.bukkit.event.player.PlayerInteractEvent
33
36
import org.bukkit.event.player.PlayerQuitEvent
37
+ import org.bukkit.event.player.PlayerRespawnEvent
34
38
import org.bukkit.inventory.EquipmentSlot
35
39
import org.bukkit.inventory.InventoryHolder
36
40
import org.bukkit.plugin.Plugin
@@ -64,14 +68,6 @@ public class InterfacesListeners private constructor(private val plugin: Plugin)
64
68
Reason .PLUGIN
65
69
)
66
70
67
- /* * All valid interaction types. */
68
- private val VALID_INTERACT = EnumSet .of(
69
- Action .LEFT_CLICK_AIR ,
70
- Action .LEFT_CLICK_BLOCK ,
71
- Action .RIGHT_CLICK_AIR ,
72
- Action .RIGHT_CLICK_BLOCK
73
- )
74
-
75
71
/* * The possible valid slot range inside the player inventory. */
76
72
private val PLAYER_INVENTORY_RANGE = 0 .. 40
77
73
@@ -136,15 +132,15 @@ public class InterfacesListeners private constructor(private val plugin: Plugin)
136
132
@EventHandler
137
133
public fun onClose (event : InventoryCloseEvent ) {
138
134
val holder = event.inventory.holder
139
- val view = convertHolderToInterfaceView( holder) ? : return
135
+ val view = holder as ? AbstractInterfaceView < * , * > ? : return
140
136
val reason = event.reason
141
137
142
138
SCOPE .launch {
143
139
// Mark the current view as closed properly
144
140
view.markClosed(reason)
145
141
146
142
// Try to open back up a previous interface
147
- if (reason in REOPEN_REASONS ) {
143
+ if (reason in REOPEN_REASONS && ! event.player.isDead ) {
148
144
getOpenInterface(event.player.uniqueId)?.open()
149
145
}
150
146
}
@@ -166,19 +162,83 @@ public class InterfacesListeners private constructor(private val plugin: Plugin)
166
162
167
163
@EventHandler(priority = EventPriority .LOW )
168
164
public fun onInteract (event : PlayerInteractEvent ) {
169
- if (event.action !in VALID_INTERACT ) return
170
- if (event.hand != EquipmentSlot .HAND ) return
165
+ if (event.action == Action .PHYSICAL ) return
171
166
if (event.useItemInHand() == Event .Result .DENY ) return
172
167
173
168
val player = event.player
174
169
val view = getOpenInterface(player.uniqueId) ? : return
175
- val slot = player.inventory.heldItemSlot
176
- val clickedPoint = GridPoint .at(3 , slot)
170
+
171
+ val clickedPoint = if (event.hand == EquipmentSlot .HAND ) {
172
+ GridPoint .at(3 , player.inventory.heldItemSlot)
173
+ } else {
174
+ PlayerPane .OFF_HAND_SLOT
175
+ }
177
176
val click = convertAction(event.action, player.isSneaking)
178
177
178
+ // Check if the action is prevented if this slot is not freely
179
+ // movable
180
+ if (! canFreelyMove(view, clickedPoint) &&
181
+ event.action in view.backing.properties.preventedInteractions
182
+ ) {
183
+ event.isCancelled = true
184
+ return
185
+ }
186
+
179
187
handleClick(view, clickedPoint, click, event, - 1 )
180
188
}
181
189
190
+ @EventHandler(priority = EventPriority .LOW , ignoreCancelled = true )
191
+ public fun onDropItem (event : PlayerDropItemEvent ) {
192
+ val player = event.player
193
+ val view = getOpenInterface(player.uniqueId) ? : return
194
+ val slot = player.inventory.heldItemSlot
195
+ val droppedSlot = GridPoint .at(3 , slot)
196
+
197
+ // Don't allow dropping items that cannot be freely edited
198
+ if (! canFreelyMove(view, droppedSlot)) {
199
+ event.isCancelled = true
200
+ }
201
+ }
202
+
203
+ @EventHandler(priority = EventPriority .HIGH , ignoreCancelled = true )
204
+ public fun onDeath (event : PlayerDeathEvent ) {
205
+ // Determine the holder of the top inventory being shown (can be open player inventory)
206
+ val view = convertHolderToInterfaceView(event.player.openInventory.topInventory.holder) ? : return
207
+
208
+ // Ignore chest inventories!
209
+ if (view is ChestInterfaceView ) return
210
+
211
+ // Tally up all items that the player cannot modify and remove them from the drops
212
+ for (index in PLAYER_INVENTORY_RANGE ) {
213
+ val stack = event.player.inventory.getItem(index) ? : continue
214
+ val x = index / 9
215
+ val adjustedX = PlayerPane .PANE_ORDERING .indexOf(x)
216
+ val point = GridPoint (adjustedX, index % 9 )
217
+ if (! canFreelyMove(view, point)) {
218
+ var removed = false
219
+
220
+ // Remove the first item in drops that is similar, drops will be a list
221
+ // of exactly what was in the inventory, without merging any stacks. So
222
+ // we do not need to do anything fancy to match the amounts.
223
+ event.drops.removeIf {
224
+ if (! removed && it.isSimilar(stack)) {
225
+ removed = true
226
+ return @removeIf true
227
+ } else {
228
+ return @removeIf false
229
+ }
230
+ }
231
+ }
232
+ }
233
+ }
234
+
235
+ @EventHandler
236
+ public fun onRespawn (event : PlayerRespawnEvent ) {
237
+ SCOPE .launch {
238
+ getOpenInterface(event.player.uniqueId)?.open()
239
+ }
240
+ }
241
+
182
242
@EventHandler(priority = EventPriority .LOWEST )
183
243
public fun onChat (event : AsyncChatEvent ) {
184
244
val player = event.player
@@ -232,6 +292,12 @@ public class InterfacesListeners private constructor(private val plugin: Plugin)
232
292
return null
233
293
}
234
294
295
+ /* * Returns whether [clickedPoint] in [view] can be freely moved. */
296
+ private fun canFreelyMove (
297
+ view : AbstractInterfaceView <* , * >,
298
+ clickedPoint : GridPoint ,
299
+ ): Boolean = view.pane.getRaw(clickedPoint)?.clickHandler == null && ! view.backing.properties.preventClickingEmptySlots
300
+
235
301
/* * Handles a [view] being clicked at [clickedPoint] through some [event]. */
236
302
private fun handleClick (
237
303
view : AbstractInterfaceView <* , * >,
@@ -241,7 +307,15 @@ public class InterfacesListeners private constructor(private val plugin: Plugin)
241
307
slot : Int
242
308
) {
243
309
// Determine the type of click, if nothing was clicked we allow it
244
- val clickHandler = view.pane.getRaw(clickedPoint)?.clickHandler ? : return
310
+ val clickHandler = view.pane.getRaw(clickedPoint)?.clickHandler
311
+
312
+ // Optionally cancel clicking on other slots
313
+ if (clickHandler == null ) {
314
+ if (view.backing.properties.preventClickingEmptySlots) {
315
+ event.isCancelled = true
316
+ }
317
+ return
318
+ }
245
319
246
320
// Automatically cancel if throttling or already processing
247
321
if (view.isProcessingClick || shouldThrottle(view.player)) {
0 commit comments