Compare commits

..

No commits in common. "0ed06e789f114b13ea3f2d78d7773858ede1fd80" and "c33cd859fee78c98035d88b73a6c37614b495f3c" have entirely different histories.

7 changed files with 65 additions and 644 deletions

@ -1 +0,0 @@
Subproject commit 520a378cd5c3da6321305d07cc0b402b73292add

View File

@ -55,20 +55,10 @@ class App : JavaPlugin() {
}
this.playerIdService = pIdService
// Register public API as a Bukkit service
val landsAPI = net.hareworks.hcu.lands.api.LandsAPIImpl(service)
server.servicesManager.register(
net.hareworks.hcu.lands.api.LandsAPI::class.java,
landsAPI,
this,
org.bukkit.plugin.ServicePriority.Normal
)
// Run visualizer every 20 ticks (1 second)
server.scheduler.runTaskTimer(this, VisualizerTask(service), 20L, 10L)
logger.info("Lands plugin initialized successfully.")
logger.info("LandsAPI registered as a Bukkit service.")
}
override fun onDisable() {

View File

@ -1,104 +0,0 @@
package net.hareworks.hcu.lands.api
import net.hareworks.hcu.lands.model.Land
import net.hareworks.hcu.lands.model.LandData
import org.bukkit.Location
import org.bukkit.block.Block
/**
* Public API for the Lands plugin.
* This service can be obtained via Bukkit's ServicesManager.
*
* Example usage:
* ```kotlin
* val landsAPI = Bukkit.getServicesManager().load(LandsAPI::class.java)
* val land = landsAPI?.getLandAt(block)
* ```
*/
interface LandsAPI {
/**
* Gets the land that contains the specified block.
*
* @param block The block to check
* @return The land containing the block, or null if no land contains it
*/
fun getLandAt(block: Block): Land?
/**
* Gets the land that contains the specified location.
*
* @param location The location to check
* @return The land containing the location, or null if no land contains it
*/
fun getLandAt(location: Location): Land?
/**
* Gets the land that contains the specified coordinates.
*
* @param world The world name
* @param x The x coordinate
* @param y The y coordinate
* @param z The z coordinate
* @return The land containing the coordinates, or null if no land contains it
*/
fun getLandAt(world: String, x: Double, y: Double, z: Double): Land?
/**
* Gets a land by its name.
*
* @param name The name of the land
* @return The land with the specified name, or null if not found
*/
fun getLand(name: String): Land?
/**
* Gets all lands in the specified world.
*
* @param world The world name
* @return List of all lands in the world
*/
fun getLandsInWorld(world: String): List<Land>
/**
* Gets all lands owned by the specified actor.
*
* @param actorId The actor ID
* @return List of all lands owned by the actor
*/
fun getLandsByOwner(actorId: Int): List<Land>
/**
* Gets all lands.
*
* @return List of all lands
*/
fun getAllLands(): List<Land>
/**
* Creates a new land.
*
* @param name The name of the land
* @param actorId The owner's actor ID
* @param world The world name
* @return true if the land was created successfully, false if a land with that name already exists
*/
fun createLand(name: String, actorId: Int, world: String): Boolean
/**
* Deletes a land.
*
* @param name The name of the land to delete
* @return true if the land was deleted successfully, false if the land doesn't exist
*/
fun deleteLand(name: String): Boolean
/**
* Modifies a land's data.
*
* @param name The name of the land to modify
* @param modifier A function that modifies the land's data
* @return true if the land was modified successfully, false if the land doesn't exist
*/
fun modifyLand(name: String, modifier: (LandData) -> Unit): Boolean
}

View File

@ -1,63 +0,0 @@
package net.hareworks.hcu.lands.api
import net.hareworks.hcu.lands.model.Land
import net.hareworks.hcu.lands.model.LandData
import net.hareworks.hcu.lands.service.LandService
import org.bukkit.Location
import org.bukkit.block.Block
/**
* Implementation of the LandsAPI interface.
* This is registered as a Bukkit service and delegates to the internal LandService.
*/
class LandsAPIImpl(private val landService: LandService) : LandsAPI {
override fun getLandAt(block: Block): Land? {
return getLandAt(block.world.name, block.x + 0.5, block.y + 0.5, block.z + 0.5)
}
override fun getLandAt(location: Location): Land? {
return getLandAt(location.world.name, location.x, location.y, location.z)
}
override fun getLandAt(world: String, x: Double, y: Double, z: Double): Land? {
return landService.getAllLands()
.filter { it.world == world }
.firstOrNull { land ->
land.data.parts.any { shape ->
when (shape) {
is net.hareworks.hcu.lands.model.Shape.Cuboid -> shape.contains(x, y, z)
is net.hareworks.hcu.lands.model.Shape.Cylinder -> shape.contains(x, y, z)
}
}
}
}
override fun getLand(name: String): Land? {
return landService.getLand(name)
}
override fun getLandsInWorld(world: String): List<Land> {
return landService.getAllLands().filter { it.world == world }.toList()
}
override fun getLandsByOwner(actorId: Int): List<Land> {
return landService.getAllLands().filter { it.actorId == actorId }.toList()
}
override fun getAllLands(): List<Land> {
return landService.getAllLands().toList()
}
override fun createLand(name: String, actorId: Int, world: String): Boolean {
return landService.createLand(name, actorId, world)
}
override fun deleteLand(name: String): Boolean {
return landService.deleteLand(name)
}
override fun modifyLand(name: String, modifier: (LandData) -> Unit): Boolean {
return landService.modifyLand(name, modifier)
}
}

View File

@ -4,7 +4,6 @@ import io.papermc.paper.math.Position
import net.hareworks.hcu.lands.App
import net.hareworks.hcu.lands.model.Land
import net.hareworks.hcu.lands.model.Shape
import net.hareworks.hcu.lands.model.normalizeToBlockInt
import net.hareworks.hcu.lands.task.LandsVisualizer
import net.hareworks.kommand_lib.kommand
import net.kyori.adventure.text.Component
@ -89,14 +88,7 @@ class LandsCommand(
executes {
val p1: Position = argument("pos1")
val p2: Position = argument("pos2")
val shape = Shape.Cuboid(
normalizeToBlockInt(p1.x()),
normalizeToBlockInt(p1.y()),
normalizeToBlockInt(p1.z()),
normalizeToBlockInt(p2.x()),
normalizeToBlockInt(p2.y()),
normalizeToBlockInt(p2.z())
)
val shape = Shape.Cuboid(p1.x(), p1.y(), p1.z(), p2.x(), p2.y(), p2.z())
val name: String = argument("name")
updateLandData(sender, name) { parts ->
@ -110,99 +102,17 @@ class LandsCommand(
literal("cylinder") {
blockCoordinates("center") {
float("radius") {
literal("center") {
integer("totalHeight", min = 1) {
executes {
val c: Position = argument("center")
val r: Double = argument("radius")
val total: Int = argument("totalHeight")
val name: String = argument("name")
float("height") {
executes {
val c: Position = argument("center")
val r: Double = argument("radius")
val h: Double = argument("height")
val shape = Shape.Cylinder(c.x(), c.y(), c.z(), r, h)
val name: String = argument("name")
// Subtract 1 for center block, distribute remaining
val remaining = total - 1
val topH = (remaining + 1) / 2 // Prefer top
val bottomH = remaining - topH
val shape = Shape.Cylinder(
normalizeToBlockInt(c.x()),
normalizeToBlockInt(c.y()),
normalizeToBlockInt(c.z()),
r, bottomH, topH
)
updateLandData(sender, name) { parts ->
parts.add(shape)
sender.sendMessage(Component.text("Shape added.", NamedTextColor.GREEN))
}
}
}
}
literal("top") {
integer("topHeight", min = 1) {
literal("bottom") {
integer("bottomHeight", min = 0) {
executes {
val c: Position = argument("center")
val r: Double = argument("radius")
val topH: Int = argument("topHeight")
val bottomH: Int = argument("bottomHeight")
val name: String = argument("name")
val shape = Shape.Cylinder(
normalizeToBlockInt(c.x()),
normalizeToBlockInt(c.y()),
normalizeToBlockInt(c.z()),
r, bottomH, topH
)
updateLandData(sender, name) { parts ->
parts.add(shape)
sender.sendMessage(Component.text("Shape added.", NamedTextColor.GREEN))
}
}
}
}
executes {
val c: Position = argument("center")
val r: Double = argument("radius")
val total: Int = argument("topHeight")
val name: String = argument("name")
// total - 1 goes to top, 0 to bottom
val shape = Shape.Cylinder(
normalizeToBlockInt(c.x()),
normalizeToBlockInt(c.y()),
normalizeToBlockInt(c.z()),
r, 0, total - 1
)
updateLandData(sender, name) { parts ->
parts.add(shape)
sender.sendMessage(Component.text("Shape added.", NamedTextColor.GREEN))
}
}
}
}
literal("bottom") {
integer("bottomHeight", min = 1) {
executes {
val c: Position = argument("center")
val r: Double = argument("radius")
val total: Int = argument("bottomHeight")
val name: String = argument("name")
// total - 1 goes to bottom, 0 to top
val shape = Shape.Cylinder(
normalizeToBlockInt(c.x()),
normalizeToBlockInt(c.y()),
normalizeToBlockInt(c.z()),
r, total - 1, 0
)
updateLandData(sender, name) { parts ->
parts.add(shape)
sender.sendMessage(Component.text("Shape added.", NamedTextColor.GREEN))
}
updateLandData(sender, name) { parts ->
parts.add(shape)
sender.sendMessage(Component.text("Shape added.", NamedTextColor.GREEN))
}
}
}
@ -220,14 +130,7 @@ class LandsCommand(
val index: Int = argument("index")
val p1: Position = argument("pos1")
val p2: Position = argument("pos2")
val shape = Shape.Cuboid(
normalizeToBlockInt(p1.x()),
normalizeToBlockInt(p1.y()),
normalizeToBlockInt(p1.z()),
normalizeToBlockInt(p2.x()),
normalizeToBlockInt(p2.y()),
normalizeToBlockInt(p2.z())
)
val shape = Shape.Cuboid(p1.x(), p1.y(), p1.z(), p2.x(), p2.y(), p2.z())
val name: String = argument("name")
updateLandData(sender, name) { parts ->
@ -245,115 +148,21 @@ class LandsCommand(
literal("cylinder") {
blockCoordinates("center") {
float("radius") {
literal("center") {
integer("totalHeight", min = 1) {
executes {
val index: Int = argument("index")
val c: Position = argument("center")
val r: Double = argument("radius")
val total: Int = argument("totalHeight")
val name: String = argument("name")
float("height") {
executes {
val index: Int = argument("index")
val c: Position = argument("center")
val r: Double = argument("radius")
val h: Double = argument("height")
val shape = Shape.Cylinder(c.x(), c.y(), c.z(), r, h)
val name: String = argument("name")
val remaining = total - 1
val topH = (remaining + 1) / 2
val bottomH = remaining - topH
val shape = Shape.Cylinder(
normalizeToBlockInt(c.x()),
normalizeToBlockInt(c.y()),
normalizeToBlockInt(c.z()),
r, bottomH, topH
)
updateLandData(sender, name) { parts ->
if (index in parts.indices) {
parts[index] = shape
sender.sendMessage(Component.text("Shape modified at index $index.", NamedTextColor.GREEN))
} else {
sender.sendMessage(Component.text("Index out of bounds.", NamedTextColor.RED))
}
}
}
}
}
literal("top") {
integer("topHeight", min = 1) {
literal("bottom") {
integer("bottomHeight", min = 0) {
executes {
val index: Int = argument("index")
val c: Position = argument("center")
val r: Double = argument("radius")
val topH: Int = argument("topHeight")
val bottomH: Int = argument("bottomHeight")
val name: String = argument("name")
val shape = Shape.Cylinder(
normalizeToBlockInt(c.x()),
normalizeToBlockInt(c.y()),
normalizeToBlockInt(c.z()),
r, bottomH, topH
)
updateLandData(sender, name) { parts ->
if (index in parts.indices) {
parts[index] = shape
sender.sendMessage(Component.text("Shape modified at index $index.", NamedTextColor.GREEN))
} else {
sender.sendMessage(Component.text("Index out of bounds.", NamedTextColor.RED))
}
}
}
}
}
executes {
val index: Int = argument("index")
val c: Position = argument("center")
val r: Double = argument("radius")
val total: Int = argument("topHeight")
val name: String = argument("name")
val shape = Shape.Cylinder(
normalizeToBlockInt(c.x()),
normalizeToBlockInt(c.y()),
normalizeToBlockInt(c.z()),
r, 0, total - 1
)
updateLandData(sender, name) { parts ->
if (index in parts.indices) {
parts[index] = shape
sender.sendMessage(Component.text("Shape modified at index $index.", NamedTextColor.GREEN))
} else {
sender.sendMessage(Component.text("Index out of bounds.", NamedTextColor.RED))
}
}
}
}
}
literal("bottom") {
integer("bottomHeight", min = 1) {
executes {
val index: Int = argument("index")
val c: Position = argument("center")
val r: Double = argument("radius")
val total: Int = argument("bottomHeight")
val name: String = argument("name")
val shape = Shape.Cylinder(
normalizeToBlockInt(c.x()),
normalizeToBlockInt(c.y()),
normalizeToBlockInt(c.z()),
r, total - 1, 0
)
updateLandData(sender, name) { parts ->
if (index in parts.indices) {
parts[index] = shape
sender.sendMessage(Component.text("Shape modified at index $index.", NamedTextColor.GREEN))
} else {
sender.sendMessage(Component.text("Index out of bounds.", NamedTextColor.RED))
}
updateLandData(sender, name) { parts ->
if (index in parts.indices) {
parts[index] = shape
sender.sendMessage(Component.text("Shape modified at index $index.", NamedTextColor.GREEN))
} else {
sender.sendMessage(Component.text("Index out of bounds.", NamedTextColor.RED))
}
}
}

View File

@ -21,8 +21,8 @@ sealed class Shape {
@Serializable
@SerialName("cuboid")
data class Cuboid(
val x1: Int, val y1: Int, val z1: Int,
val x2: Int, val y2: Int, val z2: Int
val x1: Double, val y1: Double, val z1: Double,
val x2: Double, val y2: Double, val z2: Double
) : Shape() {
fun minX() = minOf(x1, x2)
fun maxX() = maxOf(x1, x2)
@ -32,116 +32,24 @@ sealed class Shape {
fun maxZ() = maxOf(z1, z2)
fun contains(x: Double, y: Double, z: Double): Boolean {
// Add 0.5 to block coordinates for center-based comparison
return x >= minX() + 0.5 && x <= maxX() + 0.5 &&
y >= minY() + 0.5 && y <= maxY() + 0.5 &&
z >= minZ() + 0.5 && z <= maxZ() + 0.5
}
/**
* Returns the two corner blocks (min and max corners) for visualization
*/
fun getCornerBlocks(): Pair<Triple<Int, Int, Int>, Triple<Int, Int, Int>> {
val minCorner = Triple(minX(), minY(), minZ())
val maxCorner = Triple(maxX(), maxY(), maxZ())
return minCorner to maxCorner
return x >= minX() && x <= maxX() &&
y >= minY() && y <= maxY() &&
z >= minZ() && z <= maxZ()
}
}
@Serializable
@SerialName("cylinder")
data class Cylinder(
val x: Int, val y: Int, val z: Int,
val x: Double, val y: Double, val z: Double,
val radius: Double,
val bottomHeight: Int, // Height extending downward from center block
val topHeight: Int // Height extending upward from center block
val height: Double // Assuming vertical cylinder
) : Shape() {
fun contains(tx: Double, ty: Double, tz: Double): Boolean {
// Add 0.5 to block coordinates for center-based comparison
val centerX = x + 0.5
val centerY = y + 0.5
val centerZ = z + 0.5
// Total height includes center block (1) + bottomHeight + topHeight
val minY = centerY - bottomHeight
val maxY = centerY + 1.0 + topHeight
if (ty < minY || ty > maxY) return false
val dx = tx - centerX
val dz = tz - centerZ
if (ty < y || ty > y + height) return false
val dx = tx - x
val dz = tz - z
return (dx * dx + dz * dz) <= (radius * radius)
}
/**
* Returns the center block for visualization
*/
fun getCenterBlock(): Triple<Int, Int, Int> {
return Triple(x, y, z)
}
/**
* Total height of the cylinder including center block
*/
fun totalHeight(): Int = 1 + bottomHeight + topHeight
/**
* Returns a set of blocks that form the outline of the blockified cylinder.
* This includes blocks at the edge of the cylinder's radius at each Y level.
*/
fun getBlockifiedOutline(): Set<Triple<Int, Int, Int>> {
val outline = mutableSetOf<Triple<Int, Int, Int>>()
val centerX = x + 0.5
val centerZ = z + 0.5
val radiusSquared = radius * radius
// Calculate the bounding box for the cylinder
val minBlockX = (x - radius - 1).toInt()
val maxBlockX = (x + radius + 1).toInt()
val minBlockZ = (z - radius - 1).toInt()
val maxBlockZ = (z + radius + 1).toInt()
// For each Y level in the cylinder
val minY = y - bottomHeight
val maxY = y + topHeight
for (blockY in minY..maxY) {
// Check each block in the XZ plane
for (blockX in minBlockX..maxBlockX) {
for (blockZ in minBlockZ..maxBlockZ) {
// Check if this block is inside the cylinder
val dx = (blockX + 0.5) - centerX
val dz = (blockZ + 0.5) - centerZ
val isInside = (dx * dx + dz * dz) <= radiusSquared
if (isInside) {
// Check if any adjacent block is outside (making this an edge block)
val hasOutsideNeighbor = listOf(
Pair(blockX - 1, blockZ),
Pair(blockX + 1, blockZ),
Pair(blockX, blockZ - 1),
Pair(blockX, blockZ + 1)
).any { (nx, nz) ->
val ndx = (nx + 0.5) - centerX
val ndz = (nz + 0.5) - centerZ
(ndx * ndx + ndz * ndz) > radiusSquared
}
if (hasOutsideNeighbor) {
outline.add(Triple(blockX, blockY, blockZ))
}
}
}
}
}
return outline
}
}
}
/**
* Normalizes a coordinate to block integer (floor)
*/
fun normalizeToBlockInt(coord: Double): Int {
return kotlin.math.floor(coord).toInt()
}

View File

@ -51,158 +51,40 @@ class VisualizerTask(private val landService: LandService) : Runnable {
}
private fun drawCuboid(player: Player, cuboid: Shape.Cuboid) {
val (minCorner, maxCorner) = cuboid.getCornerBlocks()
// Draw edges
val minX = cuboid.minX()
val maxX = cuboid.maxX()
val minY = cuboid.minY()
val maxY = cuboid.maxY()
val minZ = cuboid.minZ()
val maxZ = cuboid.maxZ()
// Draw outline of min corner block
drawBlockOutline(player, minCorner.first, minCorner.second, minCorner.third, Color.LIME)
// Draw outline of max corner block
drawBlockOutline(player, maxCorner.first, maxCorner.second, maxCorner.third, Color.YELLOW)
// Draw edges of the cuboid using outer corners
// Min corner is at block position, max corner extends to outer edge (+1.0)
val minX = cuboid.minX().toDouble()
val maxX = cuboid.maxX() + 1.0
val minY = cuboid.minY().toDouble()
val maxY = cuboid.maxY() + 1.0
val minZ = cuboid.minZ().toDouble()
val maxZ = cuboid.maxZ() + 1.0
val edgeColor = Particle.DustOptions(Color.fromRGB(100, 255, 100), 0.75f)
val step = 0.5
// Bottom face edges
drawLine(player, minX, minY, minZ, maxX, minY, minZ, edgeColor, step)
drawLine(player, maxX, minY, minZ, maxX, minY, maxZ, edgeColor, step)
drawLine(player, maxX, minY, maxZ, minX, minY, maxZ, edgeColor, step)
drawLine(player, minX, minY, maxZ, minX, minY, minZ, edgeColor, step)
// Top face edges
drawLine(player, minX, maxY, minZ, maxX, maxY, minZ, edgeColor, step)
drawLine(player, maxX, maxY, minZ, maxX, maxY, maxZ, edgeColor, step)
drawLine(player, maxX, maxY, maxZ, minX, maxY, maxZ, edgeColor, step)
drawLine(player, minX, maxY, maxZ, minX, maxY, minZ, edgeColor, step)
// Vertical edges
drawLine(player, minX, minY, minZ, minX, maxY, minZ, edgeColor, step)
drawLine(player, maxX, minY, minZ, maxX, maxY, minZ, edgeColor, step)
drawLine(player, maxX, minY, maxZ, maxX, maxY, maxZ, edgeColor, step)
drawLine(player, minX, minY, maxZ, minX, maxY, maxZ, edgeColor, step)
// Just corners for now or simple lines
// A simple way to visualize is to spawn particles at corners and periodically along edges
// Doing full wireframe every tick is heavy.
// Let's do corners.
val corners = listOf(
Triple(minX, minY, minZ), Triple(maxX, minY, minZ),
Triple(minX, maxY, minZ), Triple(maxX, maxY, minZ),
Triple(minX, minY, maxZ), Triple(maxX, minY, maxZ),
Triple(minX, maxY, maxZ), Triple(maxX, maxY, maxZ)
)
corners.forEach { (x, y, z) ->
player.spawnParticle(Particle.DUST, x, y, z, 1, 0.0, 0.0, 0.0, Particle.DustOptions(Color.LIME, 1.0f))
}
}
private fun drawCylinder(player: Player, cylinder: Shape.Cylinder) {
val center = cylinder.getCenterBlock()
// Draw outline of center block
drawBlockOutline(player, center.first, center.second, center.third, Color.AQUA)
// Draw cylinder edges (top and bottom circles + vertical lines)
// Add 0.5 to block coordinates for center on X/Z axis
val centerX = cylinder.x + 0.5
val centerZ = cylinder.z + 0.5
// Y coordinates use block boundaries (no +0.5 offset)
val bottomY = cylinder.y.toDouble() - cylinder.bottomHeight
val topY = cylinder.y.toDouble() + 1.0 + cylinder.topHeight
val segments = 32
val segments = 20
val step = (Math.PI * 2) / segments
val edgeColor = Particle.DustOptions(Color.fromRGB(100, 200, 255), 0.75f)
// Bottom circle
// Top and Bottom circles
for (i in 0 until segments) {
val angle1 = i * step
val angle2 = (i + 1) * step
val x1 = centerX + cos(angle1) * cylinder.radius
val z1 = centerZ + sin(angle1) * cylinder.radius
val x2 = centerX + cos(angle2) * cylinder.radius
val z2 = centerZ + sin(angle2) * cylinder.radius
val angle = i * step
val dx = cos(angle) * cylinder.radius
val dz = sin(angle) * cylinder.radius
drawLine(player, x1, bottomY, z1, x2, bottomY, z2, edgeColor, 0.25)
}
// Top circle
for (i in 0 until segments) {
val angle1 = i * step
val angle2 = (i + 1) * step
val x1 = centerX + cos(angle1) * cylinder.radius
val z1 = centerZ + sin(angle1) * cylinder.radius
val x2 = centerX + cos(angle2) * cylinder.radius
val z2 = centerZ + sin(angle2) * cylinder.radius
drawLine(player, x1, topY, z1, x2, topY, z2, edgeColor, 0.25)
}
// Vertical lines (8 cardinal directions)
val verticalSegments = 8
for (i in 0 until verticalSegments) {
val angle = i * (Math.PI * 2) / verticalSegments
val x = centerX + cos(angle) * cylinder.radius
val z = centerZ + sin(angle) * cylinder.radius
drawLine(player, x, bottomY, z, x, topY, z, edgeColor, 0.5)
}
// Draw blockified outline
val blockifiedOutline = cylinder.getBlockifiedOutline()
val blockColor = Particle.DustOptions(Color.fromRGB(150, 220, 255), 0.5f)
for ((bx, by, bz) in blockifiedOutline) {
// Draw a subtle outline for each block in the blockified cylinder
drawBlockOutline(player, bx, by, bz, Color.fromRGB(150, 220, 255))
}
}
/**
* Draws an outline of a block using particles
*/
private fun drawBlockOutline(player: Player, x: Int, y: Int, z: Int, color: Color) {
val dustOptions = Particle.DustOptions(color, 1.0f)
val step = 0.25 // Particle spacing along edges
// Bottom face edges
drawLine(player, x.toDouble(), y.toDouble(), z.toDouble(), x + 1.0, y.toDouble(), z.toDouble(), dustOptions, step)
drawLine(player, x + 1.0, y.toDouble(), z.toDouble(), x + 1.0, y.toDouble(), z + 1.0, dustOptions, step)
drawLine(player, x + 1.0, y.toDouble(), z + 1.0, x.toDouble(), y.toDouble(), z + 1.0, dustOptions, step)
drawLine(player, x.toDouble(), y.toDouble(), z + 1.0, x.toDouble(), y.toDouble(), z.toDouble(), dustOptions, step)
// Top face edges
drawLine(player, x.toDouble(), y + 1.0, z.toDouble(), x + 1.0, y + 1.0, z.toDouble(), dustOptions, step)
drawLine(player, x + 1.0, y + 1.0, z.toDouble(), x + 1.0, y + 1.0, z + 1.0, dustOptions, step)
drawLine(player, x + 1.0, y + 1.0, z + 1.0, x.toDouble(), y + 1.0, z + 1.0, dustOptions, step)
drawLine(player, x.toDouble(), y + 1.0, z + 1.0, x.toDouble(), y + 1.0, z.toDouble(), dustOptions, step)
// Vertical edges
drawLine(player, x.toDouble(), y.toDouble(), z.toDouble(), x.toDouble(), y + 1.0, z.toDouble(), dustOptions, step)
drawLine(player, x + 1.0, y.toDouble(), z.toDouble(), x + 1.0, y + 1.0, z.toDouble(), dustOptions, step)
drawLine(player, x + 1.0, y.toDouble(), z + 1.0, x + 1.0, y + 1.0, z + 1.0, dustOptions, step)
drawLine(player, x.toDouble(), y.toDouble(), z + 1.0, x.toDouble(), y + 1.0, z + 1.0, dustOptions, step)
}
/**
* Draws a line of particles between two points
*/
private fun drawLine(
player: Player,
x1: Double, y1: Double, z1: Double,
x2: Double, y2: Double, z2: Double,
dustOptions: Particle.DustOptions,
step: Double
) {
val dx = x2 - x1
val dy = y2 - y1
val dz = z2 - z1
val distance = kotlin.math.sqrt(dx * dx + dy * dy + dz * dz)
val steps = (distance / step).toInt()
if (steps == 0) return
for (i in 0..steps) {
val t = i.toDouble() / steps
val x = x1 + dx * t
val y = y1 + dy * t
val z = z1 + dz * t
player.spawnParticle(Particle.DUST, x, y, z, 1, 0.0, 0.0, 0.0, dustOptions)
player.spawnParticle(Particle.DUST, cylinder.x + dx, cylinder.y, cylinder.z + dz, 1, 0.0, 0.0, 0.0, Particle.DustOptions(Color.AQUA, 1.0f))
player.spawnParticle(Particle.DUST, cylinder.x + dx, cylinder.y + cylinder.height, cylinder.z + dz, 1, 0.0, 0.0, 0.0, Particle.DustOptions(Color.AQUA, 1.0f))
}
}
}