Skip to content

Commit

Permalink
fix: remove & move right click item on click inventory
Browse files Browse the repository at this point in the history
  • Loading branch information
Distractic committed Dec 21, 2023
1 parent 728ae87 commit 3259de4
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 84 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
package com.github.rushyverse.api.extension.event

import com.github.shynixn.mccoroutine.bukkit.callSuspendingEvent
import kotlinx.coroutines.joinAll
import org.bukkit.Bukkit
import org.bukkit.block.BlockFace
import org.bukkit.entity.Damageable
import org.bukkit.entity.Player
import org.bukkit.event.block.Action
import org.bukkit.event.entity.EntityDamageEvent
import org.bukkit.event.player.PlayerInteractEvent
import org.bukkit.inventory.ItemStack
import org.bukkit.plugin.Plugin

/**
* Future life of the damaged entity.
Expand All @@ -15,3 +24,31 @@ public fun EntityDamageEvent.finalDamagedHealth(): Double? {
null
}
}

/**
* Create a [PlayerInteractEvent] to simulate a right click with an item for a player and call it.
* @param plugin Plugin to call the event.
* @param player Player who clicked.
* @param item Item that was clicked.
*/
public suspend fun callRightClickOnItemEvent(plugin: Plugin, player: Player, item: ItemStack) {
val rightClickWithItemEvent = createRightClickEventWithItem(player, item)
Bukkit.getPluginManager().callSuspendingEvent(rightClickWithItemEvent, plugin).joinAll()
}

/**
* Create a PlayerInteractEvent to simulate a right click with an item.
* @param player Player who clicked.
* @param item Item that was clicked.
* @return The new event.
*/
private fun createRightClickEventWithItem(
player: Player,
item: ItemStack
) = PlayerInteractEvent(
player,
Action.RIGHT_CLICK_AIR,
item,
null,
BlockFace.NORTH // random value not null
)
51 changes: 3 additions & 48 deletions src/main/kotlin/com/github/rushyverse/api/gui/GUIListener.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,14 @@ import com.github.rushyverse.api.Plugin
import com.github.rushyverse.api.extension.event.cancel
import com.github.rushyverse.api.koin.inject
import com.github.rushyverse.api.player.ClientManager
import com.github.shynixn.mccoroutine.bukkit.callSuspendingEvent
import kotlinx.coroutines.joinAll
import org.bukkit.Material
import org.bukkit.Server
import org.bukkit.block.BlockFace
import org.bukkit.entity.HumanEntity
import org.bukkit.entity.Player
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.event.block.Action
import org.bukkit.event.inventory.InventoryClickEvent
import org.bukkit.event.inventory.InventoryCloseEvent
import org.bukkit.event.player.PlayerInteractEvent
import org.bukkit.inventory.Inventory
import org.bukkit.inventory.ItemStack
import org.bukkit.inventory.PlayerInventory

/**
* Listener for GUI events.
Expand All @@ -29,8 +21,6 @@ public class GUIListener(private val plugin: Plugin) : Listener {

private val clients: ClientManager by inject(plugin.id)

private val server: Server by inject()

/**
* Called when a player clicks on an item in an inventory.
* If the click is detected in a GUI, the event is cancelled and the GUI is notified.
Expand All @@ -48,11 +38,7 @@ public class GUIListener(private val plugin: Plugin) : Listener {
val clickedInventory = event.clickedInventory ?: return

val player = event.whoClicked
if (clickedInventory is PlayerInventory) {
handleClickOnPlayerInventory(player as Player, item, event)
} else {
handleClickOnGUI(player, clickedInventory, item, event)
}
handleClickOnGUI(player, clickedInventory, item, event)
}

/**
Expand All @@ -79,46 +65,15 @@ public class GUIListener(private val plugin: Plugin) : Listener {
gui.onClick(client, clickedInventory, item, event)
}

/**
* Called when a player clicks on an item in his inventory.
* To trigger the action linked to the clicked item,
* we produce a PlayerInteractEvent to simulate a right click with the item.
* @param player Player who clicked.
* @param item Item that was clicked.
*/
private suspend fun handleClickOnPlayerInventory(player: Player, item: ItemStack, event: InventoryClickEvent) {
event.cancel()

val rightClickWithItemEvent = createRightClickWithItemEvent(player, item)
server.pluginManager.callSuspendingEvent(rightClickWithItemEvent, plugin).joinAll()
}

/**
* Create a PlayerInteractEvent to simulate a right click with an item.
* @param player Player who clicked.
* @param item Item that was clicked.
* @return The new event.
*/
private fun createRightClickWithItemEvent(
player: Player,
item: ItemStack
) = PlayerInteractEvent(
player,
Action.RIGHT_CLICK_AIR,
item,
null,
BlockFace.NORTH // random value not null
)

/**
* Called when a player closes an inventory.
* If the inventory is a GUI, the GUI is notified that it is closed for this player.
* @param event Event of the close.
*/
@EventHandler
public suspend fun onInventoryClose(event: InventoryCloseEvent) {
val client = clients.getClientOrNull(event.player)
val gui = client?.gui() ?: return
val client = clients.getClientOrNull(event.player) ?: return
val gui = client.gui() ?: return
// We don't close the inventory because it is closing due to event.
// That avoids an infinite loop of events and consequently a stack overflow.
gui.closeClient(client, false)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.github.rushyverse.api.extension.event

import be.seeseemelk.mockbukkit.MockBukkit
import be.seeseemelk.mockbukkit.ServerMock
import com.github.rushyverse.api.extension.ItemStack
import com.github.shynixn.mccoroutine.bukkit.callSuspendingEvent
import io.kotest.matchers.shouldBe
import io.mockk.every
import io.mockk.mockkStatic
import io.mockk.slot
import io.mockk.unmockkAll
import io.mockk.verify
import kotlin.test.AfterTest
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.test.runTest
import org.bukkit.Material
import org.bukkit.event.block.Action
import org.bukkit.event.player.PlayerInteractEvent

class EventExtTest {

private lateinit var serverMock: ServerMock

@BeforeTest
fun onBefore() {
serverMock = MockBukkit.mock()
mockkStatic("com.github.shynixn.mccoroutine.bukkit.MCCoroutineKt")
}

@AfterTest
fun onAfter() {
MockBukkit.unmock()
unmockkAll()
}

@Test
fun `should trigger right click`() = runTest {
val plugin = MockBukkit.createMockPlugin()
val player = serverMock.addPlayer()
val item = ItemStack { type = Material.DIRT }
val slot = slot<PlayerInteractEvent>()

lateinit var jobs: List<Job>
val pluginManager = serverMock.pluginManager
every { pluginManager.callSuspendingEvent(capture(slot), plugin) } answers {
List(5) { async { delay(1.seconds) } }.apply { jobs = this }
}

callRightClickOnItemEvent(plugin, player, item)

verify(exactly = 1) { pluginManager.callSuspendingEvent(any(), plugin) }
jobs.forEach { it.isCompleted shouldBe true }

val event = slot.captured
event.player shouldBe player
event.action shouldBe Action.RIGHT_CLICK_AIR
event.item shouldBe item
event.clickedBlock shouldBe null
}

}
36 changes: 0 additions & 36 deletions src/test/kotlin/com/github/rushyverse/api/gui/GUIListenerTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,23 @@ import com.github.rushyverse.api.player.Client
import com.github.rushyverse.api.player.ClientManager
import com.github.rushyverse.api.player.ClientManagerImpl
import com.github.shynixn.mccoroutine.bukkit.callSuspendingEvent
import io.kotest.matchers.shouldBe
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.slot
import io.mockk.unmockkAll
import io.mockk.verify
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.test.AfterTest
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.test.runTest
import org.bukkit.Material
import org.bukkit.entity.Player
import org.bukkit.event.block.Action
import org.bukkit.event.inventory.InventoryClickEvent
import org.bukkit.event.inventory.InventoryCloseEvent
import org.bukkit.event.player.PlayerInteractEvent
import org.bukkit.inventory.Inventory
import org.bukkit.inventory.ItemStack
import org.junit.jupiter.api.Nested
Expand Down Expand Up @@ -116,35 +109,6 @@ class GUIListenerTest : AbstractKoinTest() {
verify(exactly = 0) { pluginManager.callSuspendingEvent(any(), plugin) }
}

@Test
fun `should trigger right click event if player select his own inventory`() = runTest {
val (player, client) = registerPlayer()
val gui = registerGUI {
coEvery { contains(client) } returns false
}

val pluginManager = server.pluginManager

val slot = slot<PlayerInteractEvent>()
val jobs = List(5) {
async { delay(1.seconds) }
}
every { pluginManager.callSuspendingEvent(capture(slot), plugin) } returns jobs

val item = ItemStack { type = Material.DIRT }

callEvent(false, player, item, player.inventory)
coVerify(exactly = 0) { gui.onClick(any(), any(), any(), any()) }
verify(exactly = 1) { pluginManager.callSuspendingEvent(any(), plugin) }
jobs.forEach { it.isCompleted shouldBe true }

val event = slot.captured
event.player shouldBe player
event.action shouldBe Action.RIGHT_CLICK_AIR
event.item shouldBe item
event.clickedBlock shouldBe null
}

@Test
fun `should call GUI onClick if client has opened one`() = runTest {
val (player, client) = registerPlayer()
Expand Down

0 comments on commit 3259de4

Please sign in to comment.