package com.pobnellion.aoe.ui; import com.comphenix.protocol.PacketType import com.comphenix.protocol.ProtocolLibrary import com.comphenix.protocol.events.PacketContainer import com.comphenix.protocol.wrappers.* import com.comphenix.protocol.wrappers.WrappedDataWatcher.Registry import it.unimi.dsi.fastutil.ints.IntList import org.bukkit.Bukkit import org.bukkit.Location import org.bukkit.Material import org.bukkit.entity.EntityType 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.player.PlayerInteractEvent import org.bukkit.event.player.PlayerMoveEvent import org.bukkit.potion.PotionEffectType import org.joml.Math import org.joml.Vector3f import java.util.* import kotlin.math.floor import kotlin.math.min class PlaceHint : Listener { companion object { private const val MAX_TARGET_DISTANCE = 64 private val playerHints = HashMap(); fun add( player: Player, sizeX: Float, sizeY: Float, sizeZ: Float, onConfirm: (Location, Float, Float, Float) -> Unit, validate: (Location, Float, Float, Float) -> Boolean, materialValid: Material = Material.LIME_STAINED_GLASS, materialInvalid: Material = Material.RED_STAINED_GLASS, ) { remove(player) createOutlineTeam(player) playerHints[player.uniqueId] = HintMarker(player, sizeX, sizeY, sizeZ, onConfirm, validate, materialValid, materialInvalid) val block = player.getTargetBlockExact(MAX_TARGET_DISTANCE); playerHints[player.uniqueId]!!.moveTo(block?.location) } fun remove(player: Player) { if (!playerHints.containsKey(player.uniqueId)) return playerHints[player.uniqueId]!!.moveTo(null) playerHints.remove(player.uniqueId) } fun clear() { playerHints.values.forEach(fun (hint) {hint.moveTo(null)}) playerHints.clear() } private fun createOutlineTeam(player: Player) { val packet = PacketContainer(PacketType.Play.Server.SCOREBOARD_TEAM) packet.strings.write(0, "placeValid") packet.integers.write(0, 0) packet.optionalTeamParameters.write(0, Optional.of(Utils.teamParams)) ProtocolLibrary.getProtocolManager().sendServerPacket(player, packet) } } @EventHandler fun onPlayerLook(event: PlayerMoveEvent) { if (!playerHints.containsKey(event.player.uniqueId)) return val block = event.player.getTargetBlockExact(MAX_TARGET_DISTANCE); playerHints[event.player.uniqueId]!!.moveTo(block?.location) } @EventHandler fun onLeftClick(event: PlayerInteractEvent) { if (!playerHints.containsKey(event.player.uniqueId) || (event.action != Action.LEFT_CLICK_BLOCK && event.action != Action.LEFT_CLICK_AIR)) return if (!playerHints[event.player.uniqueId]!!.isValid) return val marker = playerHints[event.player.uniqueId]!! marker.onConfirm(marker.currentLocation!!, marker.sizeX, marker.sizeY, marker.sizeZ) remove(event.player) } } private class HintMarker( val player: Player, val sizeX: Float, val sizeY: Float, val sizeZ: Float, val onConfirm: (Location, Float, Float, Float) -> Unit, val validate: (Location, Float, Float, Float) -> Boolean, val materialValid: Material, val materialInvalid: Material, ) { private val markerUuid = UUID.randomUUID() private val outlineUuid = UUID.randomUUID() private val markerId = (Math.random() * Int.MAX_VALUE).toInt() private val outlineId = markerId + 1 var currentLocation: Location? = null var isValid = false private fun show(initialLocation: Location) { initialLocation.add(-floor(sizeX / 2.0), 1.0, -floor(sizeZ / 2.0)) spawnBlockDisplay(markerId, markerUuid, initialLocation, Material.GRAY_STAINED_GLASS, Vector3f(sizeX, 0.2f, sizeZ), 0b01000000) // spawnBlockDisplay(outlineId, outlineUuid, initialLocation, Material.STONE, Vector3f(sizeX, sizeY, sizeZ), 0b00100000) val spawnSlimePacket = PacketContainer(PacketType.Play.Server.SPAWN_ENTITY) spawnSlimePacket.integers.write(0, outlineId) spawnSlimePacket.uuiDs.write(0, outlineUuid) spawnSlimePacket.entityTypeModifier.write(0, EntityType.SLIME) spawnSlimePacket.doubles .write(0, initialLocation.x) .write(1, initialLocation.y) .write(2, initialLocation.z) val dataPacket = PacketContainer(PacketType.Play.Server.ENTITY_METADATA) dataPacket.integers.write(0, outlineId) // TODO: 1 - display riding slime for nicer movement // TODO: 2 - slime riding display for nicer movement // TODO: 3 - resource pack for outline with really low opacity val watcher = WrappedDataWatcher() watcher.setObject(0, Registry.get(Byte::class.javaObjectType), 0b01100000.toByte()) // watcher.setObject(5, Registry.get(Boolean::class.javaObjectType), true) watcher.setObject(16, Registry.get(Int::class.javaObjectType), min(sizeX, min(sizeY, sizeZ)).toInt()) dataPacket.dataValueCollectionModifier.write(0, watcher.watchableObjects .map { WrappedDataValue(it.watcherObject.index, it.watcherObject.serializer, it.rawValue) }) ProtocolLibrary.getProtocolManager().sendServerPacket(player, spawnSlimePacket) ProtocolLibrary.getProtocolManager().sendServerPacket(player, dataPacket) val ridingPacket = PacketContainer(PacketType.Play.Server.MOUNT) ridingPacket.integers.write(0, outlineId) ridingPacket.integerArrays.write(0, intArrayOf(markerId)) val outlineTeamPacket = PacketContainer(PacketType.Play.Server.SCOREBOARD_TEAM) outlineTeamPacket.strings.write(0, "placeValid") outlineTeamPacket.integers.write(0, 3) outlineTeamPacket.getSpecificModifier(Collection::class.java).write(0, listOf(outlineUuid.toString())) // val outlineInvisiblePacket = PacketContainer(PacketType.Play.Server.ENTITY_EFFECT) // outlineInvisiblePacket.integers // .write(0, outlineId) // .write(1, 1) // .write(2, -1) // // outlineInvisiblePacket.effectTypes.write(0, PotionEffectType.INVISIBILITY) // outlineInvisiblePacket.bytes.write(0, 0b0000.toByte()) ProtocolLibrary.getProtocolManager().sendServerPacket(player, outlineTeamPacket) ProtocolLibrary.getProtocolManager().sendServerPacket(player, ridingPacket) // ProtocolLibrary.getProtocolManager().sendServerPacket(player, outlineInvisiblePacket) isValid = validate(initialLocation, sizeX, sizeY, sizeZ) updateColour() currentLocation = initialLocation; } private fun hide() { val packet = PacketContainer(PacketType.Play.Server.ENTITY_DESTROY) packet.intLists.write(0, IntList.of(markerId, outlineId)) ProtocolLibrary.getProtocolManager().sendServerPacket(player, packet) currentLocation = null } fun moveTo(newLocation: Location?) { if (newLocation == null) { if (currentLocation != null) hide() return } if (currentLocation == null) { show(newLocation) return } newLocation.add(-floor(sizeX / 2.0), 1.0, -floor(sizeZ / 2.0)) // val markerPacket = PacketContainer(PacketType.Play.Server.ENTITY_TELEPORT) // markerPacket.integers.write(0, markerId) // // markerPacket.doubles // .write(0, newLocation.x) // .write(1, newLocation.y) // .write(2, newLocation.z) // // markerPacket.booleans.write(0, true) // // ProtocolLibrary.getProtocolManager().sendServerPacket(player, markerPacket) val outlinePacket = PacketContainer(PacketType.Play.Server.ENTITY_TELEPORT) outlinePacket.integers.write(0, outlineId) outlinePacket.doubles .write(0, newLocation.x) .write(1, newLocation.y) .write(2, newLocation.z) outlinePacket.booleans.write(0, true) ProtocolLibrary.getProtocolManager().sendServerPacket(player, outlinePacket) currentLocation = newLocation // Update material val valid = validate(newLocation, sizeX, sizeY, sizeZ) if (valid != isValid) { isValid = valid updateColour() } } // region helper functions private fun spawnBlockDisplay(id: Int, uuid: UUID, location: Location, material: Material, scale: Vector3f, options: Byte) { val spawnPacket = PacketContainer(PacketType.Play.Server.SPAWN_ENTITY) spawnPacket.integers.write(0, id) spawnPacket.uuiDs.write(0, uuid) spawnPacket.entityTypeModifier.write(0, EntityType.BLOCK_DISPLAY) spawnPacket.doubles .write(0, location.x) .write(1, location.y) .write(2, location.z) val dataPacket = PacketContainer(PacketType.Play.Server.ENTITY_METADATA) dataPacket.integers.write(0, id) // woated val blockData = BukkitConverters .getWrappedBlockDataConverter() .getGeneric(WrappedBlockData.createData(material)) val watcher = WrappedDataWatcher() watcher.setObject(0, Registry.get(Byte::class.javaObjectType), options) watcher.setObject(12, Registry.get(Vector3f::class.javaObjectType), scale) watcher.setObject(23, Registry.getBlockDataSerializer(false), blockData) dataPacket.dataValueCollectionModifier.write(0, watcher.watchableObjects .map { WrappedDataValue(it.watcherObject.index, it.watcherObject.serializer, it.rawValue) }) ProtocolLibrary.getProtocolManager().sendServerPacket(player, spawnPacket) ProtocolLibrary.getProtocolManager().sendServerPacket(player, dataPacket) } private fun updateColour() { // marker val dataPacket = PacketContainer(PacketType.Play.Server.ENTITY_METADATA) dataPacket.integers.write(0,markerId); val material = if (isValid) materialValid else materialInvalid val blockData = BukkitConverters .getWrappedBlockDataConverter() .getGeneric(WrappedBlockData.createData(material)) val metadata = listOf( WrappedDataValue(23, WrappedDataWatcher.Registry.getBlockDataSerializer(false), blockData) ) dataPacket.dataValueCollectionModifier.write(0, metadata) ProtocolLibrary.getProtocolManager().sendServerPacket(player, dataPacket) // outline val outlineTeamPacket = PacketContainer(PacketType.Play.Server.SCOREBOARD_TEAM) outlineTeamPacket.strings.write(0, "placeValid") outlineTeamPacket.integers.write(0, 2) val params = WrappedTeamParameters.newBuilder(Utils.teamParams) .color(if (isValid) EnumWrappers.ChatFormatting.GREEN else EnumWrappers.ChatFormatting.RED) .build() outlineTeamPacket.optionalTeamParameters.write(0, Optional.of(params)) ProtocolLibrary.getProtocolManager().sendServerPacket(player, outlineTeamPacket) } // endregion } private class Utils { companion object { val teamParams: WrappedTeamParameters = WrappedTeamParameters.newBuilder() .displayName(WrappedChatComponent.fromText("")) .collisionRule("never") .nametagVisibility("never") .options(0) .color(EnumWrappers.ChatFormatting.RED) .prefix(WrappedChatComponent.fromText("")) .suffix(WrappedChatComponent.fromText("")) .build() } }