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>() override fun createTextDisplay( location: Location, interaction: InteractionOptions, builder: TextDisplay.() -> Unit ): DisplayController = spawnDisplay(location, TextDisplay::class.java, interaction) { builder(it) } override fun createBlockDisplay( location: Location, interaction: InteractionOptions, builder: BlockDisplay.() -> Unit ): DisplayController = spawnDisplay(location, BlockDisplay::class.java, interaction) { builder(it) } override fun createItemDisplay( location: Location, itemStack: ItemStack, interaction: InteractionOptions, builder: ItemDisplay.() -> Unit ): DisplayController = spawnDisplay(location, ItemDisplay::class.java, interaction) { it.itemStack = itemStack.clone() builder(it) } override fun destroyAll() { controllers.forEach { it.destroy() } controllers.clear() } private fun spawnDisplay( location: Location, type: Class, interactionOptions: InteractionOptions, afterSpawn: (T) -> Unit ): DisplayController { 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 callSync(action: () -> T): T { return if (Bukkit.isPrimaryThread()) { action() } else { val future = CompletableFuture() Bukkit.getScheduler().runTask(plugin) { future.complete(action()) } future.join() } } }