200 lines
6.6 KiB
Kotlin
200 lines
6.6 KiB
Kotlin
package net.hareworks.ghostdisplays.internal.controller
|
|
|
|
import java.util.HashSet
|
|
import java.util.UUID
|
|
import java.util.concurrent.CompletableFuture
|
|
import java.util.concurrent.ConcurrentHashMap
|
|
import java.util.concurrent.CopyOnWriteArrayList
|
|
import java.util.concurrent.atomic.AtomicBoolean
|
|
import net.hareworks.ghostdisplays.api.DisplayController
|
|
import net.hareworks.ghostdisplays.api.audience.AudiencePredicate
|
|
import net.hareworks.ghostdisplays.api.click.ClickPriority
|
|
import net.hareworks.ghostdisplays.api.click.ClickSurface
|
|
import net.hareworks.ghostdisplays.api.click.DisplayClickContext
|
|
import net.hareworks.ghostdisplays.api.click.DisplayClickHandler
|
|
import net.hareworks.ghostdisplays.api.click.HandlerRegistration
|
|
import org.bukkit.Bukkit
|
|
import org.bukkit.entity.Display
|
|
import org.bukkit.entity.Entity
|
|
import org.bukkit.entity.Interaction
|
|
import org.bukkit.entity.Player
|
|
import org.bukkit.event.player.PlayerInteractEntityEvent
|
|
import org.bukkit.plugin.java.JavaPlugin
|
|
|
|
internal class BaseDisplayController<T : Display>(
|
|
private val plugin: JavaPlugin,
|
|
override val display: T,
|
|
override val interaction: Interaction?,
|
|
private val registry: DisplayRegistry
|
|
) : DisplayController<T> {
|
|
private val destroyed = AtomicBoolean(false)
|
|
private val viewerCounts = ConcurrentHashMap<UUID, Int>()
|
|
private val handlers = CopyOnWriteArrayList<HandlerEntry>()
|
|
private val audiences = CopyOnWriteArrayList<AudienceBindingImpl>()
|
|
|
|
override fun show(player: Player) {
|
|
runSync {
|
|
val uuid = player.uniqueId
|
|
val newCount = viewerCounts.compute(uuid) { _, count -> (count ?: 0) + 1 } ?: 1
|
|
if (newCount == 1) {
|
|
player.showEntity(plugin, display)
|
|
interaction?.let { player.showEntity(plugin, it) }
|
|
}
|
|
}
|
|
}
|
|
|
|
override fun hide(player: Player) {
|
|
runSync {
|
|
val uuid = player.uniqueId
|
|
val current = viewerCounts[uuid] ?: return@runSync
|
|
if (current <= 1) {
|
|
viewerCounts.remove(uuid)
|
|
player.hideEntity(plugin, display)
|
|
interaction?.let { player.hideEntity(plugin, it) }
|
|
} else {
|
|
viewerCounts[uuid] = current - 1
|
|
}
|
|
}
|
|
}
|
|
|
|
override fun isViewing(playerId: UUID): Boolean = viewerCounts.containsKey(playerId)
|
|
|
|
override fun viewerIds(): Set<UUID> = HashSet(viewerCounts.keys)
|
|
|
|
override fun applyEntityUpdate(mutator: (T) -> Unit) {
|
|
callSync {
|
|
mutator(display)
|
|
display.updateDisplay(false)
|
|
}
|
|
}
|
|
|
|
override fun destroy() {
|
|
if (!destroyed.compareAndSet(false, true)) return
|
|
registry.unregister(this)
|
|
val viewers = viewerIds().mapNotNull { Bukkit.getPlayer(it) }
|
|
runSync {
|
|
viewers.forEach {
|
|
it.hideEntity(plugin, display)
|
|
interaction?.let { interactionEntity -> it.hideEntity(plugin, interactionEntity) }
|
|
}
|
|
display.remove()
|
|
interaction?.remove()
|
|
viewerCounts.clear()
|
|
}
|
|
handlers.clear()
|
|
audiences.forEach { it.clear() }
|
|
audiences.clear()
|
|
}
|
|
|
|
override fun onClick(priority: ClickPriority, handler: DisplayClickHandler): HandlerRegistration {
|
|
val entry = HandlerEntry(priority, handler)
|
|
handlers += entry
|
|
return HandlerRegistration {
|
|
handlers.remove(entry)
|
|
}
|
|
}
|
|
|
|
override fun addAudience(predicate: AudiencePredicate): HandlerRegistration {
|
|
val binding = AudienceBindingImpl(predicate)
|
|
audiences += binding
|
|
refreshAudienceInternal(binding = binding)
|
|
return HandlerRegistration { binding.unregister() }
|
|
}
|
|
|
|
private fun refreshAudienceInternal(target: Player? = null, binding: AudienceBindingImpl? = null) {
|
|
runSync {
|
|
val players = target?.let { listOf(it) } ?: Bukkit.getOnlinePlayers()
|
|
val targets = binding?.let { listOf(it) } ?: audiences
|
|
if (players.isEmpty() || targets.isEmpty()) return@runSync
|
|
players.forEach { player ->
|
|
targets.forEach { it.evaluate(player) }
|
|
}
|
|
}
|
|
}
|
|
|
|
override fun refreshAudience(target: Player?) {
|
|
refreshAudienceInternal(target, null)
|
|
}
|
|
|
|
internal fun handleClick(event: PlayerInteractEntityEvent, clicked: Entity, surface: ClickSurface) {
|
|
val sortedHandlers = handlers.sortedBy { it.priority.ordinal }
|
|
if (sortedHandlers.isEmpty()) return
|
|
val context = DisplayClickContext(
|
|
player = event.player,
|
|
hand = event.hand,
|
|
clickedEntity = clicked,
|
|
surface = surface,
|
|
controller = this,
|
|
event = event
|
|
)
|
|
sortedHandlers.forEach { entry ->
|
|
entry.handler.handle(context)
|
|
if (event.isCancelled) return
|
|
}
|
|
}
|
|
|
|
internal fun handlePlayerQuit(player: Player) {
|
|
val uuid = player.uniqueId
|
|
viewerCounts.remove(uuid)
|
|
audiences.forEach { it.forget(uuid) }
|
|
}
|
|
|
|
private fun runSync(action: () -> Unit) {
|
|
if (Bukkit.isPrimaryThread()) {
|
|
action()
|
|
} else {
|
|
Bukkit.getScheduler().runTask(plugin, action)
|
|
}
|
|
}
|
|
|
|
private fun <R> callSync(action: () -> R): R {
|
|
return if (Bukkit.isPrimaryThread()) {
|
|
action()
|
|
} else {
|
|
val future = CompletableFuture<R>()
|
|
Bukkit.getScheduler().runTask(plugin) { future.complete(action()) }
|
|
future.join()
|
|
}
|
|
}
|
|
|
|
private inner class AudienceBindingImpl(
|
|
private val predicate: AudiencePredicate
|
|
) {
|
|
private val activeViewers = ConcurrentHashMap.newKeySet<UUID>()
|
|
|
|
fun evaluate(player: Player) {
|
|
val uuid = player.uniqueId
|
|
val matches = runCatching { predicate.test(player) }.getOrDefault(false)
|
|
if (matches) {
|
|
if (activeViewers.add(uuid)) {
|
|
show(player)
|
|
}
|
|
} else {
|
|
if (activeViewers.remove(uuid)) {
|
|
hide(player)
|
|
}
|
|
}
|
|
}
|
|
|
|
fun forget(playerId: UUID) {
|
|
activeViewers.remove(playerId)
|
|
}
|
|
|
|
fun unregister() {
|
|
audiences.remove(this)
|
|
val targets = activeViewers.toList()
|
|
targets.mapNotNull { Bukkit.getPlayer(it) }.forEach { hide(it) }
|
|
activeViewers.clear()
|
|
}
|
|
|
|
fun clear() {
|
|
activeViewers.clear()
|
|
}
|
|
}
|
|
|
|
private data class HandlerEntry(
|
|
val priority: ClickPriority,
|
|
val handler: DisplayClickHandler
|
|
)
|
|
}
|