diff --git a/build.gradle.kts b/build.gradle.kts index 4ffc67d..5a4cdd1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,12 +18,18 @@ repositories { maven("https://repo.dmulloy2.net/repository/public/") { name = "dmulloy2-repo" } + maven("https://maven.enginehub.org/repo/") { + name = "enginehub" + } } dependencies { compileOnly("io.papermc.paper:paper-api:1.21.3-R0.1-SNAPSHOT") compileOnly("com.comphenix.protocol:ProtocolLib:5.3.0") + compileOnly("com.fastasyncworldedit:FastAsyncWorldEdit-Bukkit") +// implementation(files("libs/ProtocolLib.jar")) implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") + implementation(platform("com.intellectualsites.bom:bom-newest:1.51")) // Ref: https://github.com/IntellectualSites/bom // implementation("de.nilsdruyen.gradle-ftp-upload-plugin:de.nilsdruyen.gradle-ftp-upload-plugin.gradle.plugin:0.5.0") } diff --git a/src/main/kotlin/com/pobnellion/aoe/Aoe.kt b/src/main/kotlin/com/pobnellion/aoe/Aoe.kt index 7214109..816cd98 100644 --- a/src/main/kotlin/com/pobnellion/aoe/Aoe.kt +++ b/src/main/kotlin/com/pobnellion/aoe/Aoe.kt @@ -5,12 +5,20 @@ import com.pobnellion.aoe.ui.PlaceHint import com.pobnellion.aoe.ui.PlaceItem import org.bukkit.Bukkit import org.bukkit.plugin.java.JavaPlugin +import java.io.File class Aoe : JavaPlugin() { + companion object { + private var instance: Aoe? = null + + fun getSchematicsFolder(): File = File(instance!!.dataFolder, "schematics") + } override fun onEnable() { + instance = this + // Events - Bukkit.getPluginManager().registerEvents(PlaceHint(), this) + Bukkit.getPluginManager().registerEvents(PlaceHint, this) Bukkit.getPluginManager().registerEvents(PlaceItem(), this) // Commands diff --git a/src/main/kotlin/com/pobnellion/aoe/building/Blacksmith.kt b/src/main/kotlin/com/pobnellion/aoe/building/Blacksmith.kt index 34a9833..241d704 100644 --- a/src/main/kotlin/com/pobnellion/aoe/building/Blacksmith.kt +++ b/src/main/kotlin/com/pobnellion/aoe/building/Blacksmith.kt @@ -8,7 +8,7 @@ import org.bukkit.entity.Player class Blacksmith: Building { override fun showPlaceHint(player: Player) { - PlaceHint.add(player, 4.0f, 3.0f, 4.0f, ::place, PlaceHintValidators::allReplaceable) + PlaceHint.add(player, "house2", ::place, PlaceHintValidators::allReplaceable) } fun place(location: Location, sizeX: Float, sizeY: Float, sizeZ: Float) { diff --git a/src/main/kotlin/com/pobnellion/aoe/building/House.kt b/src/main/kotlin/com/pobnellion/aoe/building/House.kt index ef017a5..493a1a6 100644 --- a/src/main/kotlin/com/pobnellion/aoe/building/House.kt +++ b/src/main/kotlin/com/pobnellion/aoe/building/House.kt @@ -8,7 +8,7 @@ import org.bukkit.entity.Player class House: Building { override fun showPlaceHint(player: Player) { - PlaceHint.add(player, 3.0f, 3.0f, 3.0f, ::place, PlaceHintValidators::allAir) + PlaceHint.add(player, "house1", ::place, PlaceHintValidators::allReplaceable) } fun place(location: Location, sizeX: Float, sizeY: Float, sizeZ: Float) { diff --git a/src/main/kotlin/com/pobnellion/aoe/ui/PlaceHint.kt b/src/main/kotlin/com/pobnellion/aoe/ui/PlaceHint.kt index cf0c3eb..39fbc84 100644 --- a/src/main/kotlin/com/pobnellion/aoe/ui/PlaceHint.kt +++ b/src/main/kotlin/com/pobnellion/aoe/ui/PlaceHint.kt @@ -5,7 +5,10 @@ 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 com.pobnellion.aoe.Aoe +import com.sk89q.worldedit.bukkit.BukkitAdapter +import com.sk89q.worldedit.extent.clipboard.Clipboard +import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormats import org.bukkit.Bukkit import org.bukkit.Location import org.bukkit.Material @@ -16,18 +19,17 @@ 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.io.File +import java.io.FileInputStream import java.util.* import kotlin.math.floor -import kotlin.math.min -class PlaceHint : Listener { - - companion object { +class PlaceHint { + companion object : Listener { private const val MAX_TARGET_DISTANCE = 64 - private val playerHints = HashMap(); + private val playerHints = HashMap(); fun add( player: Player, @@ -36,12 +38,25 @@ class PlaceHint : Listener { 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) + + playerHints[player.uniqueId] = PlaceHint(player, sizeX, sizeY, sizeZ, onConfirm, validate) + val block = player.getTargetBlockExact(MAX_TARGET_DISTANCE); + playerHints[player.uniqueId]!!.moveTo(block?.location) + } + + fun add( + player: Player, + schematicName: String, + onConfirm: (Location, Float, Float, Float) -> Unit, + validate: (Location, Float, Float, Float) -> Boolean, + ) { + remove(player) + createOutlineTeam(player) + + playerHints[player.uniqueId] = PlaceHint(player, schematicName, onConfirm, validate) val block = player.getTargetBlockExact(MAX_TARGET_DISTANCE); playerHints[player.uniqueId]!!.moveTo(block?.location) } @@ -63,107 +78,129 @@ class PlaceHint : Listener { 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)) + packet.optionalTeamParameters.write(0, Optional.of(teamParamsTemplate)) 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 val teamParamsTemplate: WrappedTeamParameters = WrappedTeamParameters.newBuilder() + .displayName(WrappedChatComponent.fromText("")) + .collisionRule("never") + .nametagVisibility("never") + .options(0) + .color(EnumWrappers.ChatFormatting.RED) + .prefix(WrappedChatComponent.fromText("")) + .suffix(WrappedChatComponent.fromText("")) + .build() } - @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 + private val player: Player + private val sizeX: Float + private val sizeY: Float + private val sizeZ: Float + val onConfirm: (Location, Float, Float, Float) -> Unit + val validate: (Location, Float, Float, Float) -> Boolean + private val schematic: Clipboard? + private var marker: Pair = Pair((Math.random() * Int.MAX_VALUE).toInt(), UUID.randomUUID()) + private var base: Pair = Pair((Math.random() * Int.MAX_VALUE).toInt(), UUID.randomUUID()) + private val displayIds: MutableList> = mutableListOf() var currentLocation: Location? = null var isValid = false + constructor(player: Player, sizeX: Float, sizeY: Float, sizeZ: Float, + onConfirm: (Location, Float, Float, Float) -> Unit, + validate: (Location, Float, Float, Float) -> Boolean) { + this.player = player + this.sizeX = sizeX + this.sizeY = sizeY + this.sizeZ = sizeZ + this.onConfirm = onConfirm + this.validate = validate + this.schematic = null + } + + constructor(player: Player, schematicName: String, + onConfirm: (Location, Float, Float, Float) -> Unit, + validate: (Location, Float, Float, Float) -> Boolean) { + this.player = player + this.onConfirm = onConfirm + this.validate = validate + + val file = File(Aoe.getSchematicsFolder(), + if (schematicName.endsWith(".schem")) schematicName else "$schematicName.schem") + + val format = ClipboardFormats.findByFile(file) + val reader = format?.getReader(FileInputStream(file)) + val clipboard = reader?.read() + + this.schematic = clipboard + this.sizeX = clipboard!!.dimensions.x().toFloat() + this.sizeY = clipboard.dimensions.y().toFloat() + this.sizeZ = clipboard.dimensions.z().toFloat() + } + 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 + val spawnMarkerPacket = PacketContainer(PacketType.Play.Server.SPAWN_ENTITY) + spawnMarkerPacket.integers.write(0, marker.first) + spawnMarkerPacket.uuiDs.write(0, marker.second) + spawnMarkerPacket.entityTypeModifier.write(0, EntityType.BLOCK_DISPLAY) + spawnMarkerPacket.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) + base = spawnBlockDisplay(initialLocation, Material.GRAY_STAINED_GLASS, scale = Vector3f(sizeX + 0.05f, 0.2f, sizeZ + 0.05f)) - // 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 + schematic?.iterator()?.forEach { + val material = BukkitAdapter.adapt(schematic.getBlock(it)).material - 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()) + val x = (it.x() - schematic.minimumPoint.x()).toFloat() + val y = (it.y() - schematic.minimumPoint.y()).toFloat() + val z = (it.z() - schematic.minimumPoint.z()).toFloat() - 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)) + if (!material.isAir && !material.isEmpty) + displayIds.add(spawnBlockDisplay(initialLocation, material, offset = Vector3f(x, y, z), options = 0b01000000)) + } 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())) + outlineTeamPacket.getSpecificModifier(Collection::class.java).write(0, displayIds.map { it.second.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()) + val ids = displayIds.map { it.first }.toMutableList() + ids.add(base.first) + val passengerPacket = PacketContainer(PacketType.Play.Server.MOUNT) + passengerPacket.integers.write(0, marker.first) + passengerPacket.integerArrays.write(0, ids.toIntArray()) + + ProtocolLibrary.getProtocolManager().sendServerPacket(player, spawnMarkerPacket) ProtocolLibrary.getProtocolManager().sendServerPacket(player, outlineTeamPacket) - ProtocolLibrary.getProtocolManager().sendServerPacket(player, ridingPacket) -// ProtocolLibrary.getProtocolManager().sendServerPacket(player, outlineInvisiblePacket) + ProtocolLibrary.getProtocolManager().sendServerPacket(player, passengerPacket) isValid = validate(initialLocation, sizeX, sizeY, sizeZ) updateColour() @@ -173,7 +210,10 @@ private class HintMarker( private fun hide() { val packet = PacketContainer(PacketType.Play.Server.ENTITY_DESTROY) - packet.intLists.write(0, IntList.of(markerId, outlineId)) + val ids = displayIds.map { it.first }.toMutableList() + ids.add(marker.first) + ids.add(base.first) + packet.intLists.write(0, ids) ProtocolLibrary.getProtocolManager().sendServerPacket(player, packet) @@ -194,28 +234,17 @@ private class HintMarker( 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 basePacket = PacketContainer(PacketType.Play.Server.ENTITY_TELEPORT) + basePacket.integers.write(0, marker.first) - val outlinePacket = PacketContainer(PacketType.Play.Server.ENTITY_TELEPORT) - outlinePacket.integers.write(0, outlineId) - - outlinePacket.doubles + basePacket.doubles .write(0, newLocation.x) .write(1, newLocation.y) .write(2, newLocation.z) - outlinePacket.booleans.write(0, true) - ProtocolLibrary.getProtocolManager().sendServerPacket(player, outlinePacket) + basePacket.booleans.write(0, true) + + ProtocolLibrary.getProtocolManager().sendServerPacket(player, basePacket) currentLocation = newLocation @@ -230,7 +259,16 @@ private class HintMarker( // region helper functions - private fun spawnBlockDisplay(id: Int, uuid: UUID, location: Location, material: Material, scale: Vector3f, options: Byte) { + private fun spawnBlockDisplay( + location: Location, + material: Material, + scale: Vector3f = Vector3f(1f, 1f, 1f), + offset: Vector3f = Vector3f(0f, 0f, 0f), + options: Byte = 0b00000000 + ): Pair { + val id = (Math.random() * Int.MAX_VALUE).toInt() + val uuid = UUID.randomUUID() + val spawnPacket = PacketContainer(PacketType.Play.Server.SPAWN_ENTITY) spawnPacket.integers.write(0, id) spawnPacket.uuiDs.write(0, uuid) @@ -250,6 +288,7 @@ private class HintMarker( val watcher = WrappedDataWatcher() watcher.setObject(0, Registry.get(Byte::class.javaObjectType), options) + watcher.setObject(11, Registry.get(Vector3f::class.javaObjectType), offset) watcher.setObject(12, Registry.get(Vector3f::class.javaObjectType), scale) watcher.setObject(23, Registry.getBlockDataSerializer(false), blockData) @@ -258,20 +297,22 @@ private class HintMarker( ProtocolLibrary.getProtocolManager().sendServerPacket(player, spawnPacket) ProtocolLibrary.getProtocolManager().sendServerPacket(player, dataPacket) + + return Pair(id, uuid) } private fun updateColour() { - // marker + // base val dataPacket = PacketContainer(PacketType.Play.Server.ENTITY_METADATA) - dataPacket.integers.write(0,markerId); + dataPacket.integers.write(0, base.first); - val material = if (isValid) materialValid else materialInvalid + val material = if (isValid) Material.LIME_STAINED_GLASS else Material.RED_STAINED_GLASS val blockData = BukkitConverters .getWrappedBlockDataConverter() .getGeneric(WrappedBlockData.createData(material)) val metadata = listOf( - WrappedDataValue(23, WrappedDataWatcher.Registry.getBlockDataSerializer(false), blockData) + WrappedDataValue(23, Registry.getBlockDataSerializer(false), blockData) ) dataPacket.dataValueCollectionModifier.write(0, metadata) @@ -281,7 +322,7 @@ private class HintMarker( val outlineTeamPacket = PacketContainer(PacketType.Play.Server.SCOREBOARD_TEAM) outlineTeamPacket.strings.write(0, "placeValid") outlineTeamPacket.integers.write(0, 2) - val params = WrappedTeamParameters.newBuilder(Utils.teamParams) + val params = WrappedTeamParameters.newBuilder(teamParamsTemplate) .color(if (isValid) EnumWrappers.ChatFormatting.GREEN else EnumWrappers.ChatFormatting.RED) .build() @@ -290,18 +331,4 @@ private class HintMarker( } // 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() - } } \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index c21d421..28fd838 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -4,6 +4,7 @@ main: com.pobnellion.aoe.Aoe api-version: '1.21' depend: - ProtocolLib + - WorldEdit commands: debug: description: Debug command