GhostDisplays/main/kotlin/net/hareworks/ghostdisplays/internal/DefaultDisplayService.kt
2025-12-06 04:40:18 +09:00

102 lines
3.8 KiB
Kotlin

package net.hareworks.ghostdisplays.internal
import java.util.concurrent.CompletableFuture
import java.util.concurrent.CopyOnWriteArraySet
import net.hareworks.ghostdisplays.api.DisplayController
import net.hareworks.ghostdisplays.api.DisplayService
import net.hareworks.ghostdisplays.api.InteractionOptions
import net.hareworks.ghostdisplays.internal.controller.BaseDisplayController
import net.hareworks.ghostdisplays.internal.controller.DisplayRegistry
import org.bukkit.Bukkit
import org.bukkit.Location
import org.bukkit.entity.BlockDisplay
import org.bukkit.entity.Display
import org.bukkit.entity.Entity
import org.bukkit.entity.Interaction
import org.bukkit.entity.ItemDisplay
import org.bukkit.entity.TextDisplay
import org.bukkit.inventory.ItemStack
import org.bukkit.plugin.java.JavaPlugin
internal class DefaultDisplayService(
private val plugin: JavaPlugin,
private val registry: DisplayRegistry
) : DisplayService {
private val controllers = CopyOnWriteArraySet<BaseDisplayController<out Display>>()
override fun createTextDisplay(
location: Location,
interaction: InteractionOptions,
builder: TextDisplay.() -> Unit
): DisplayController<TextDisplay> = spawnDisplay(location, TextDisplay::class.java, interaction) {
builder(it)
}
override fun createBlockDisplay(
location: Location,
interaction: InteractionOptions,
builder: BlockDisplay.() -> Unit
): DisplayController<BlockDisplay> = spawnDisplay(location, BlockDisplay::class.java, interaction) {
builder(it)
}
override fun createItemDisplay(
location: Location,
itemStack: ItemStack,
interaction: InteractionOptions,
builder: ItemDisplay.() -> Unit
): DisplayController<ItemDisplay> = spawnDisplay(location, ItemDisplay::class.java, interaction) {
it.itemStack = itemStack.clone()
builder(it)
}
override fun destroyAll() {
controllers.forEach { it.destroy() }
controllers.clear()
}
private fun <T : Display> spawnDisplay(
location: Location,
type: Class<T>,
interactionOptions: InteractionOptions,
afterSpawn: (T) -> Unit
): DisplayController<T> {
val entity = callSync {
val world = location.world ?: throw IllegalArgumentException("Location must have a world")
world.spawn(location, type) { spawned ->
spawned.setPersistent(false)
spawned.setVisibleByDefault(false)
}
}
callSync { afterSpawn(entity) }
val interaction = if (interactionOptions.enabled) spawnInteraction(location, interactionOptions) else null
val controller = BaseDisplayController(plugin, entity, interaction, registry)
controllers += controller
registry.register(controller)
return controller
}
private fun spawnInteraction(location: Location, options: InteractionOptions): Interaction =
callSync {
val world = location.world ?: throw IllegalArgumentException("Location must have a world")
world.spawn(location, Interaction::class.java) { interaction ->
interaction.setPersistent(false)
interaction.setInvisible(true)
interaction.setVisibleByDefault(false)
interaction.setResponsive(options.responsive)
interaction.setInteractionWidth(options.effectiveWidth().toFloat())
interaction.setInteractionHeight(options.effectiveHeight().toFloat())
}
}
private fun <T> callSync(action: () -> T): T {
return if (Bukkit.isPrimaryThread()) {
action()
} else {
val future = CompletableFuture<T>()
Bukkit.getScheduler().runTask(plugin) { future.complete(action()) }
future.join()
}
}
}