Files
AOP/src/main/kotlin/com/pobnellion/aoe/ui/PlaceHint.kt
2024-12-30 13:13:42 +13:00

307 lines
12 KiB
Kotlin

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<UUID, HintMarker>();
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()
}
}