Initial commit

This commit is contained in:
2024-12-30 00:58:28 +13:00
commit aac3d2d4cb
18 changed files with 1048 additions and 0 deletions

View File

@ -0,0 +1,23 @@
package com.pobnellion.aoe
import com.pobnellion.aoe.command.TestCommand
import com.pobnellion.aoe.ui.PlaceHint
import com.pobnellion.aoe.ui.PlaceItem
import org.bukkit.Bukkit
import org.bukkit.plugin.java.JavaPlugin
class Aoe : JavaPlugin() {
override fun onEnable() {
// Events
Bukkit.getPluginManager().registerEvents(PlaceHint(), this)
Bukkit.getPluginManager().registerEvents(PlaceItem(), this)
// Commands
this.getCommand("debug")!!.setExecutor(TestCommand())
}
override fun onDisable() {
// Plugin shutdown logic
}
}

View File

@ -0,0 +1,23 @@
package com.pobnellion.aoe.building
import com.pobnellion.aoe.ui.PlaceHint
import com.pobnellion.aoe.ui.PlaceHintValidators
import org.bukkit.Location
import org.bukkit.Material
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)
}
fun place(location: Location, sizeX: Float, sizeY: Float, sizeZ: Float) {
for (x in 0..<sizeX.toInt())
for (y in 0..<sizeY.toInt())
for (z in 0..<sizeZ.toInt())
location.world.getBlockAt(
location.blockX + x,
location.blockY + y,
location.blockZ + z ).type = Material.SMOOTH_STONE
}
}

View File

@ -0,0 +1,8 @@
package com.pobnellion.aoe.building
import org.bukkit.Location
import org.bukkit.entity.Player
interface Building {
fun showPlaceHint(player: Player)
}

View File

@ -0,0 +1,23 @@
package com.pobnellion.aoe.building
import com.pobnellion.aoe.ui.PlaceHint
import com.pobnellion.aoe.ui.PlaceHintValidators
import org.bukkit.Location
import org.bukkit.Material
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)
}
fun place(location: Location, sizeX: Float, sizeY: Float, sizeZ: Float) {
for (x in 0..<sizeX.toInt())
for (y in 0..<sizeY.toInt())
for (z in 0..<sizeZ.toInt())
location.world.getBlockAt(
location.blockX + x,
location.blockY + y,
location.blockZ + z ).type = Material.OAK_LOG
}
}

View File

@ -0,0 +1,20 @@
package com.pobnellion.aoe.command
import com.pobnellion.aoe.ui.PlaceHint
import org.bukkit.Material
import org.bukkit.command.Command
import org.bukkit.command.CommandExecutor
import org.bukkit.command.CommandSender
import org.bukkit.entity.Player
class TestCommand : CommandExecutor {
override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>?): Boolean {
if (sender !is Player) {
sender.sendMessage("Command can only be executed by players")
return true
}
val player: Player = sender
return true
}
}

View File

@ -0,0 +1,7 @@
package com.pobnellion.aoe.team
import com.pobnellion.aoe.building.Building
class Team {
public val Buildings: List<Building> = listOf()
}

View File

@ -0,0 +1,302 @@
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 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, 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()
}
}

View File

@ -0,0 +1,38 @@
package com.pobnellion.aoe.ui
import org.bukkit.Location
class PlaceHintValidators {
companion object {
fun alwaysTrue(location: Location, sizeX: Float, sizeY: Float, sizeZ: Float): Boolean {
return true
}
fun allAir(location: Location, sizeX: Float, sizeY: Float, sizeZ: Float): Boolean {
for (x in 0..<sizeX.toInt())
for (y in 0..<sizeY.toInt())
for (z in 0..<sizeZ.toInt())
if (!location.world.getBlockAt(
location.blockX + x,
location.blockY + y,
location.blockZ + z ).isEmpty)
return false
return true
}
fun allReplaceable(location: Location, sizeX: Float, sizeY: Float, sizeZ: Float): Boolean {
for (x in 0..<sizeX.toInt())
for (y in 0..<sizeY.toInt())
for (z in 0..<sizeZ.toInt())
if (!location.world.getBlockAt(
location.blockX + x,
location.blockY + y,
location.blockZ + z ).isReplaceable)
return false
return true
}
}
}

View File

@ -0,0 +1,67 @@
package com.pobnellion.aoe.ui
import com.pobnellion.aoe.building.Blacksmith
import com.pobnellion.aoe.building.House
import net.kyori.adventure.text.Component
import org.bukkit.Bukkit
import org.bukkit.Material
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.inventory.InventoryClickEvent
import org.bukkit.event.inventory.InventoryCloseEvent
import org.bukkit.event.inventory.InventoryDragEvent
import org.bukkit.event.player.PlayerInteractEvent
import org.bukkit.inventory.ItemStack
class PlaceItem : Listener {
private val buildingInventory = Bukkit.createInventory(null, 9, Component.text("Buildings"))
init {
buildingInventory.addItem(createGuiItem(Material.OAK_LOG, "House", "Just a house bro"))
buildingInventory.addItem(createGuiItem(Material.ANVIL, "Blacksmith", "smith ."))
}
@EventHandler
fun onRightClickItem(event: PlayerInteractEvent) {
if (event.material != Material.BRICKS || (event.action != Action.RIGHT_CLICK_AIR && event.action != Action.RIGHT_CLICK_BLOCK))
return
event.isCancelled = true
event.player.openInventory(buildingInventory)
}
@EventHandler
fun onInventoryClick(event: InventoryClickEvent) {
if (event.inventory != buildingInventory)
return
event.isCancelled = true
var validClick = true
when (event.currentItem?.type) {
Material.OAK_LOG -> House().showPlaceHint(event.whoClicked as Player)
Material.ANVIL -> Blacksmith().showPlaceHint(event.whoClicked as Player)
else -> validClick = false
}
if (validClick)
event.whoClicked.closeInventory(InventoryCloseEvent.Reason.PLUGIN)
}
@EventHandler
fun onInventoryClick(event: InventoryDragEvent) {
if (event.inventory == buildingInventory)
event.isCancelled = true
}
private fun createGuiItem(material: Material, name: String, lore: String): ItemStack {
val item = ItemStack(material, 1)
item.itemMeta.displayName(Component.text(name))
item.itemMeta.lore(mutableListOf(Component.text(lore)))
return item
}
}