Files
KSpigot/src/main/kotlin/net/axay/kspigot/gui/GUI.kt
2022-03-29 16:31:19 +02:00

244 lines
7.5 KiB
Kotlin

@file:Suppress("MemberVisibilityCanBePrivate")
package net.axay.kspigot.gui
import net.axay.kspigot.event.listen
import net.axay.kspigot.extensions.bukkit.closeForViewers
import net.axay.kspigot.main.PluginInstance
import net.kyori.adventure.text.Component
import org.bukkit.entity.Player
import org.bukkit.event.inventory.InventoryCloseEvent
import org.bukkit.event.player.PlayerQuitEvent
import org.bukkit.inventory.Inventory
import org.bukkit.inventory.ItemStack
class GUIData<T : ForInventory>(
val guiType: GUIType<T>,
val title: Component,
internal val pages: Map<Int, GUIPage<T>>,
val defaultPage: Int,
val transitionTo: InventoryChangeEffect?,
val transitionFrom: InventoryChangeEffect?,
internal val generalOnClick: ((GUIClickEvent<T>) -> Unit)?,
internal val onClose: ((GUICloseEvent<T>) -> Unit)?
)
abstract class GUI<T : ForInventory>(
val data: GUIData<T>,
) {
/**
* Returns the instance beloning to the given player.
* If not existing, a new instance will be created.
*/
abstract fun getInstance(player: Player): GUIInstance<T>
/**
* Returns all active instances of this GUI.
*/
abstract fun getAllInstances(): Collection<GUIInstance<T>>
/**
* Closes this GUI for all viewers and unregisters
* all instances.
*/
abstract fun closeGUI()
protected fun unregisterAndClose() {
getAllInstances().forEach {
it.bukkitInventory.closeForViewers()
it.unregister()
}
}
}
class GUIShared<T : ForInventory>(
guiData: GUIData<T>,
) : GUI<T>(guiData) {
private var _singleInstance: GUIInstance<T>? = null
val singleInstance
get() = _singleInstance ?: GUIInstance(this, null).apply {
_singleInstance = this
register()
}
override fun getInstance(player: Player) = singleInstance
override fun getAllInstances() = _singleInstance?.let { listOf(it) } ?: emptyList()
override fun closeGUI() {
unregisterAndClose()
_singleInstance = null
}
}
class GUIIndividual<T : ForInventory>(
guiData: GUIData<T>,
resetOnClose: Boolean,
resetOnQuit: Boolean,
) : GUI<T>(guiData) {
private val playerInstances = HashMap<Player, GUIInstance<T>>()
override fun getInstance(player: Player) =
playerInstances[player] ?: createInstance(player)
override fun getAllInstances() = playerInstances.values
private fun createInstance(player: Player) =
GUIInstance(this, player).apply {
playerInstances[player] = this
register()
}
fun deleteInstance(player: Player) = playerInstances.remove(player)?.unregister()
override fun closeGUI() {
unregisterAndClose()
playerInstances.clear()
}
init {
if (resetOnClose || data.onClose != null) {
listen<InventoryCloseEvent> {
if (data.onClose != null && playerInstances[it.player]?.bukkitInventory == it.inventory) {
data.onClose.invoke(GUICloseEvent(it, playerInstances[it.player]!!, it.player as Player))
}
if (resetOnClose) {
deleteInstance(it.player as? Player ?: return@listen)
}
}
}
if (resetOnQuit) {
listen<PlayerQuitEvent> {
deleteInstance(it.player)
}
}
}
}
class GUIInstance<T : ForInventory>(
val gui: GUI<T>,
holder: Player?,
) {
internal val bukkitInventory = gui.data.guiType.createBukkitInv(holder, gui.data.title)
private val currentElements = HashSet<GUIElement<*>>()
internal var isInMove: Boolean = false
var currentPageInt: Int = gui.data.defaultPage; private set
val currentPage
get() = getPage(currentPageInt)
?: throw IllegalStateException("The currentPageInt has no associated page!")
init {
loadPageUnsafe(gui.data.defaultPage)
}
internal fun loadPageUnsafe(page: Int, offsetHorizontally: Int = 0, offsetVertically: Int = 0) {
gui.data.pages[page]?.let { loadPageUnsafe(it, offsetHorizontally, offsetVertically) }
}
internal fun loadPageUnsafe(page: GUIPage<*>, offsetHorizontally: Int = 0, offsetVertically: Int = 0) {
val ifOffset = offsetHorizontally != 0 || offsetVertically != 0
if (!ifOffset) {
// unregister this inv from all elements on the previous page
currentElements.forEach { it.stopUsing(this) }
currentElements.clear()
// register this inv for all new elements
HashSet(page.slots.values).forEach {
if (it is GUIElement) {
currentElements += it
it.startUsing(this)
}
}
currentPageInt = page.number
}
loadContent(page.slots, offsetHorizontally, offsetVertically)
}
internal fun loadContent(
content: Map<Int, GUISlot<*>>,
offsetHorizontally: Int = 0,
offsetVertically: Int = 0,
) {
val ifOffset = offsetHorizontally != 0 || offsetVertically != 0
val dimensions = gui.data.guiType.dimensions
// clear the space which will be redefined
if (ifOffset) {
dimensions.invSlots.forEach {
val slotToClear = dimensions.invSlotsWithRealSlots[it.add(offsetHorizontally, offsetVertically)]
if (slotToClear != null) bukkitInventory.clear(slotToClear)
}
} else bukkitInventory.clear()
// render the given content
content.forEach {
val slot = it.value
if (slot is GUIElement) {
if (ifOffset) {
val invSlot = InventorySlot.fromRealSlot(it.key, dimensions)
if (invSlot != null) {
val offsetSlot = invSlot.add(offsetHorizontally, offsetVertically).realSlotIn(dimensions)
if (offsetSlot != null) bukkitInventory.setItem(offsetSlot, slot.getItemStack(offsetSlot))
}
} else bukkitInventory.setItem(it.key, slot.getItemStack(it.key))
}
}
}
/**
* Registers this GUI.
* (KSpigot will listen for actions in the inventory.)
*/
@Suppress("UNCHECKED_CAST")
fun register() = PluginInstance.guiHolder.register(this as GUIInstance<ForInventory>)
/**
* Stops KSpigot from listening to actions in this
* GUI anymore.
*/
fun unregister() {
@Suppress("UNCHECKED_CAST")
PluginInstance.guiHolder.unregister(this as GUIInstance<ForInventory>)
// unregister this inv from all elements
currentElements.forEach { it.stopUsing(this) }
currentElements.clear()
}
/**
* @return True, if the [inventory] belongs to this GUI.
*/
fun isThisInv(inventory: Inventory) = inventory == bukkitInventory
/**
* Loads the specified page in order to display it in the GUI.
*/
fun loadPage(page: GUIPage<T>) = loadPageUnsafe(page)
/**
* Temporarily sets the given item at the given slots.
*/
operator fun set(slot: InventorySlotCompound<T>, value: ItemStack) {
slot.realSlotsWithInvType(gui.data.guiType).forEach {
bukkitInventory.setItem(it, value)
}
}
/**
* Searches for a page associated to the given [page] index.
*/
fun getPage(page: Int?) = gui.data.pages[page]
/**
* Reloads the current page.
*/
fun reloadCurrentPage() {
if (!isInMove)
loadPage(currentPage)
}
}