feat:範囲設定・可視化
This commit is contained in:
parent
767f974228
commit
3c6b644607
2
Lands
2
Lands
|
|
@ -1 +1 @@
|
|||
Subproject commit 0f432b0e89614bc07bd710423284da5e0228e25a
|
||||
Subproject commit d256db9f518bb2a6444a36f71b69afee783fd077
|
||||
|
|
@ -75,7 +75,7 @@ class LandSectorPlugin : JavaPlugin() {
|
|||
server.pluginManager.registerEvents(net.hareworks.hcu.landsector.listener.SelectionListener(this, selService, service), this)
|
||||
|
||||
// Schedule visualization task
|
||||
net.hareworks.hcu.landsector.task.SelectionVisualizerTask(this, selService).runTaskTimer(this, 0L, 4L)
|
||||
net.hareworks.hcu.landsector.task.SelectionVisualizerTask(this, selService, service).runTaskTimer(this, 0L, 4L)
|
||||
} else {
|
||||
logger.severe("SelectionService not initialized!")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -303,13 +303,21 @@ class SectorListener(
|
|||
} else {
|
||||
content.append(Component.text("Parts:", NamedTextColor.BLACK))
|
||||
ranges.forEach { range ->
|
||||
content.append(Component.text("\n- [${range.id}] ${range.type}", NamedTextColor.DARK_GRAY))
|
||||
content.append(Component.text(" "))
|
||||
content.append(Component.text("\n"))
|
||||
content.append(
|
||||
Component.text("[x] ", NamedTextColor.RED)
|
||||
.clickEvent(ClickEvent.runCommand("/landsector range delete $sectorId ${range.id}"))
|
||||
.hoverEvent(net.kyori.adventure.text.event.HoverEvent.showText(Component.text("Click to delete range")))
|
||||
)
|
||||
|
||||
val info = if (range.type == net.hareworks.hcu.landsector.model.SelectionMode.CUBOID) {
|
||||
"(${range.x1},${range.y1},${range.z1})~(${range.x2},${range.y2},${range.z2})"
|
||||
} else {
|
||||
// Cylinder: x1,y1,z1 is Center. x2=R, y2=Bottom, z2=Top
|
||||
val h = 1 + range.y2 + range.z2
|
||||
"Cyl @(${range.x1},${range.y1},${range.z1}) R:${range.x2} H:$h"
|
||||
}
|
||||
content.append(Component.text(info, NamedTextColor.DARK_GRAY))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,43 +37,38 @@ class SelectionListener(
|
|||
val player = event.player
|
||||
val selection = selectionService.getSelection(player.uniqueId)
|
||||
|
||||
// Left Click: Switch Mode
|
||||
// Left Click: Cancel only
|
||||
if (event.action == Action.LEFT_CLICK_AIR || event.action == Action.LEFT_CLICK_BLOCK) {
|
||||
event.isCancelled = true // Prevent breaking block
|
||||
|
||||
selection.mode = if (selection.mode == SelectionMode.CUBOID) SelectionMode.CYLINDER else SelectionMode.CUBOID
|
||||
player.sendMessage(Component.text("Mode switched to: ${selection.mode}", NamedTextColor.YELLOW))
|
||||
|
||||
// Reset selection on mode switch
|
||||
selection.point1 = null
|
||||
selection.point2 = null
|
||||
selection.p1Sneaking = false
|
||||
event.isCancelled = true
|
||||
return
|
||||
}
|
||||
|
||||
// Right Click: Set Points
|
||||
if (event.action == Action.RIGHT_CLICK_BLOCK) {
|
||||
event.isCancelled = true // Prevent placing
|
||||
if (event.action == Action.RIGHT_CLICK_BLOCK || event.action == Action.RIGHT_CLICK_AIR) {
|
||||
event.isCancelled = true // Prevent placing or item usage
|
||||
|
||||
val clickedBlock = event.clickedBlock ?: return
|
||||
val loc = clickedBlock.location
|
||||
val targetBlock = event.clickedBlock ?: player.rayTraceBlocks(100.0)?.hitBlock
|
||||
|
||||
if (targetBlock == null) return
|
||||
|
||||
val loc = targetBlock.location
|
||||
|
||||
if (selection.point1 == null) {
|
||||
// Set Point 1
|
||||
selection.point1 = loc
|
||||
selection.p1Sneaking = player.isSneaking
|
||||
selection.point2 = null // Clear p2 just in case
|
||||
// selection.isCenterMode is persistent, so we don't set it here based on sneaking
|
||||
selection.point2 = null
|
||||
|
||||
player.sendMessage(Component.text("Position 1 set at ${loc.blockX}, ${loc.blockY}, ${loc.blockZ} (Mode: ${selection.mode}, Sneaking: ${selection.p1Sneaking})", NamedTextColor.GREEN))
|
||||
player.sendMessage(Component.text("Position 1 set at ${loc.blockX}, ${loc.blockY}, ${loc.blockZ} (Mode: ${selection.mode}, CenterMode: ${selection.isCenterMode})", NamedTextColor.GREEN))
|
||||
player.playSound(player.location, org.bukkit.Sound.UI_BUTTON_CLICK, 1.0f, 2.0f)
|
||||
} else {
|
||||
// Set Point 2
|
||||
// If P2 is already set, reset to P1 new? standard standard is cyclic.
|
||||
if (selection.point2 != null) {
|
||||
// Resetting, treat as P1
|
||||
selection.point1 = loc
|
||||
selection.p1Sneaking = player.isSneaking
|
||||
selection.point2 = null
|
||||
player.sendMessage(Component.text("Selection reset. Position 1 set at ${loc.blockX}, ${loc.blockY}, ${loc.blockZ} (Mode: ${selection.mode}, Sneaking: ${selection.p1Sneaking})", NamedTextColor.GREEN))
|
||||
player.sendMessage(Component.text("Selection reset. Position 1 set at ${loc.blockX}, ${loc.blockY}, ${loc.blockZ} (Mode: ${selection.mode}, CenterMode: ${selection.isCenterMode})", NamedTextColor.GREEN))
|
||||
player.playSound(player.location, org.bukkit.Sound.BLOCK_NOTE_BLOCK_BASS, 1.0f, 0.5f)
|
||||
} else {
|
||||
selection.point2 = loc
|
||||
// Check for sector ID
|
||||
|
|
@ -90,10 +85,11 @@ class SelectionListener(
|
|||
selection.mode,
|
||||
p1.blockX, p1.blockY, p1.blockZ,
|
||||
p2.blockX, p2.blockY, p2.blockZ,
|
||||
selection.p1Sneaking
|
||||
selection.isCenterMode
|
||||
)
|
||||
|
||||
player.sendMessage(Component.text("Range added to Sector #$sId.", NamedTextColor.GREEN))
|
||||
player.playSound(player.location, org.bukkit.Sound.ENTITY_PLAYER_LEVELUP, 1.0f, 2.0f)
|
||||
|
||||
// Clear selection
|
||||
selection.point1 = null
|
||||
|
|
@ -104,6 +100,8 @@ class SelectionListener(
|
|||
// Show Cost / Area
|
||||
player.sendMessage(Component.text(selectionService.getAreaDetails(player.uniqueId), NamedTextColor.AQUA))
|
||||
player.sendMessage(Component.text("Cost: ${selectionService.getCost(player.uniqueId)}", NamedTextColor.GOLD))
|
||||
|
||||
player.playSound(player.location, org.bukkit.Sound.UI_BUTTON_CLICK, 1.0f, 1.0f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -116,7 +114,25 @@ class SelectionListener(
|
|||
val key = NamespacedKey(plugin, "component")
|
||||
if (item.itemMeta?.persistentDataContainer?.get(key, PersistentDataType.STRING) == "land_sector_tool") {
|
||||
event.isCancelled = true
|
||||
event.player.sendMessage(Component.text("You cannot drop this tool.", NamedTextColor.RED))
|
||||
|
||||
val player = event.player
|
||||
val selection = selectionService.getSelection(player.uniqueId)
|
||||
|
||||
if (player.isSneaking) {
|
||||
// Toggle Center/Corner Mode
|
||||
selection.isCenterMode = !selection.isCenterMode
|
||||
player.sendMessage(Component.text("Selection Modifier: ${if (selection.isCenterMode) "Center/Base Mode" else "Corner/Symmetric Mode"}", NamedTextColor.GOLD))
|
||||
player.playSound(player.location, org.bukkit.Sound.BLOCK_COMPARATOR_CLICK, 1.0f, 1.0f)
|
||||
} else {
|
||||
// Toggle Shape Mode
|
||||
selection.mode = if (selection.mode == SelectionMode.CUBOID) SelectionMode.CYLINDER else SelectionMode.CUBOID
|
||||
player.sendMessage(Component.text("Shape: ${selection.mode}", NamedTextColor.YELLOW))
|
||||
player.playSound(player.location, org.bukkit.Sound.ITEM_BOOK_PAGE_TURN, 1.0f, 1.0f)
|
||||
|
||||
// Reset selection on mode switch
|
||||
selection.point1 = null
|
||||
selection.point2 = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,5 +11,5 @@ data class SelectionData(
|
|||
var mode: SelectionMode = SelectionMode.CUBOID,
|
||||
var point1: Location? = null,
|
||||
var point2: Location? = null,
|
||||
var p1Sneaking: Boolean = false
|
||||
var isCenterMode: Boolean = false // Replaces old p1Sneaking logic with explicit toggle
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@ package net.hareworks.hcu.landsector.service
|
|||
|
||||
import net.hareworks.hcu.landsector.database.SectorDraftsTable
|
||||
import net.hareworks.hcu.landsector.database.SectorsTable
|
||||
// SectorRangesTable removed
|
||||
import net.hareworks.hcu.landsector.model.Sector
|
||||
import net.hareworks.hcu.landsector.model.SectorRange
|
||||
import net.hareworks.hcu.landsector.model.SelectionMode
|
||||
import net.hareworks.hcu.lands.model.LandData
|
||||
import net.hareworks.hcu.lands.model.Shape
|
||||
import org.jetbrains.exposed.v1.core.eq
|
||||
import org.jetbrains.exposed.v1.jdbc.Database
|
||||
import org.jetbrains.exposed.v1.jdbc.SchemaUtils
|
||||
|
|
@ -385,7 +386,7 @@ class SectorService(private val database: Database, private val landService: net
|
|||
sectorId,
|
||||
SelectionMode.CYLINDER,
|
||||
shape.x, shape.y, shape.z,
|
||||
shape.x, shape.y, shape.z,
|
||||
shape.radius.toInt(), shape.bottomHeight, shape.topHeight,
|
||||
false
|
||||
)
|
||||
}
|
||||
|
|
@ -395,6 +396,17 @@ class SectorService(private val database: Database, private val landService: net
|
|||
}
|
||||
}
|
||||
|
||||
fun getSectorShapes(sectorId: Int): List<net.hareworks.hcu.lands.model.Shape> {
|
||||
val sector = getSector(sectorId) ?: return emptyList()
|
||||
|
||||
val land = if (sector.landId != null) {
|
||||
landService.getLand(sector.landId)
|
||||
} else {
|
||||
draftLands[sectorId]
|
||||
} ?: return emptyList()
|
||||
|
||||
return land.data.parts
|
||||
}
|
||||
fun deleteRange(sectorId: Int, rangeIndex: Int): Boolean {
|
||||
val sector = getSector(sectorId) ?: return false
|
||||
if (sector.landId != null) return false // Cannot edit confirmed
|
||||
|
|
@ -470,3 +482,5 @@ class SectorService(private val database: Database, private val landService: net
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -33,13 +33,13 @@ class SelectionService {
|
|||
val height: Int
|
||||
val length: Int
|
||||
|
||||
if (!data.p1Sneaking) {
|
||||
if (!data.isCenterMode) {
|
||||
// Normal: P1 to P2
|
||||
width = abs(p1.blockX - p2.blockX) + 1
|
||||
height = abs(p1.blockY - p2.blockY) + 1
|
||||
length = abs(p1.blockZ - p2.blockZ) + 1
|
||||
} else {
|
||||
// Sneak: P1 is center
|
||||
// Center Mode: P1 is center
|
||||
val dx = abs(p1.blockX - p2.blockX)
|
||||
val dy = abs(p1.blockY - p2.blockY)
|
||||
val dz = abs(p1.blockZ - p2.blockZ)
|
||||
|
|
@ -58,12 +58,12 @@ class SelectionService {
|
|||
val baseRadius = ceil(dist - 0.5)
|
||||
val radius = baseRadius + 0.5
|
||||
|
||||
val totalHeight = if (!data.p1Sneaking) {
|
||||
val totalHeight = if (!data.isCenterMode) {
|
||||
// Normal: P1 center, symmetric height
|
||||
val h = abs(p1.blockY - p2.blockY)
|
||||
h * 2 + 1
|
||||
} else {
|
||||
// Sneak: P1 base, P2 top
|
||||
// Center Mode: P1 base, P2 top (or vice versa, height is diff)
|
||||
abs(p1.blockY - p2.blockY) + 1
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package net.hareworks.hcu.landsector.task
|
|||
|
||||
import net.hareworks.hcu.landsector.model.SelectionMode
|
||||
import net.hareworks.hcu.landsector.service.SelectionService
|
||||
import net.hareworks.hcu.landsector.service.SectorService
|
||||
import net.hareworks.hcu.visualizer.GeometryVisualizer
|
||||
import net.kyori.adventure.text.Component
|
||||
import net.kyori.adventure.text.format.NamedTextColor
|
||||
|
|
@ -18,7 +19,8 @@ import kotlin.math.abs
|
|||
|
||||
class SelectionVisualizerTask(
|
||||
private val plugin: JavaPlugin,
|
||||
private val selectionService: SelectionService
|
||||
private val selectionService: SelectionService,
|
||||
private val sectorService: SectorService
|
||||
) : BukkitRunnable() {
|
||||
|
||||
override fun run() {
|
||||
|
|
@ -28,41 +30,107 @@ class SelectionVisualizerTask(
|
|||
}
|
||||
|
||||
private fun visualize(player: Player) {
|
||||
// Check if holding tool
|
||||
// Check if tool is in inventory
|
||||
val key = NamespacedKey(plugin, "component")
|
||||
val hasTool = player.inventory.contents.any { item ->
|
||||
item != null && item.type == Material.FLINT &&
|
||||
item.itemMeta?.persistentDataContainer?.get(key, PersistentDataType.STRING) == "land_sector_tool"
|
||||
val sectorKey = NamespacedKey(plugin, "sector_id")
|
||||
|
||||
// Find tool in inventory to get sector ID
|
||||
var sectorId: Int? = null
|
||||
var hasTool = false
|
||||
|
||||
// 1. Check held items first (Priority: Held ID > Inventory ID)
|
||||
val heldItems = listOf(player.inventory.itemInMainHand, player.inventory.itemInOffHand)
|
||||
for (item in heldItems) {
|
||||
if (item.type == Material.FLINT) {
|
||||
val meta = item.itemMeta
|
||||
if (meta != null && meta.persistentDataContainer.get(key, PersistentDataType.STRING) == "land_sector_tool") {
|
||||
hasTool = true
|
||||
if (meta.persistentDataContainer.has(sectorKey, PersistentDataType.INTEGER)) {
|
||||
sectorId = meta.persistentDataContainer.get(sectorKey, PersistentDataType.INTEGER)
|
||||
break // Found specific sector tool in hand, prioritize this.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. If no specific sector ID found in hand, scan inventory for any linked tool
|
||||
if (sectorId == null) {
|
||||
for (item in player.inventory.contents) {
|
||||
if (item != null && item.type == Material.FLINT) {
|
||||
val meta = item.itemMeta ?: continue
|
||||
if (meta.persistentDataContainer.get(key, PersistentDataType.STRING) == "land_sector_tool") {
|
||||
hasTool = true
|
||||
if (meta.persistentDataContainer.has(sectorKey, PersistentDataType.INTEGER)) {
|
||||
sectorId = meta.persistentDataContainer.get(sectorKey, PersistentDataType.INTEGER)
|
||||
break // Found linked tool in inventory.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasTool) return
|
||||
|
||||
// 1. Visualize Existing Ranges (Green)
|
||||
if (sectorId != null) {
|
||||
// World Check
|
||||
val sector = sectorService.getSector(sectorId)
|
||||
if (sector != null && sector.world == player.world.name) {
|
||||
val shapes = sectorService.getSectorShapes(sectorId)
|
||||
val color = Color.fromRGB(0, 255, 0) // Green for saved
|
||||
|
||||
for (shape in shapes) {
|
||||
when (shape) {
|
||||
is net.hareworks.hcu.lands.model.Shape.Cuboid -> {
|
||||
val minX = minOf(shape.x1, shape.x2).toDouble()
|
||||
val minY = minOf(shape.y1, shape.y2).toDouble()
|
||||
val minZ = minOf(shape.z1, shape.z2).toDouble()
|
||||
val maxX = maxOf(shape.x1, shape.x2).toDouble() + 1.0
|
||||
val maxY = maxOf(shape.y1, shape.y2).toDouble() + 1.0
|
||||
val maxZ = maxOf(shape.z1, shape.z2).toDouble() + 1.0
|
||||
|
||||
GeometryVisualizer.drawCuboid(
|
||||
player,
|
||||
minX, minY, minZ,
|
||||
maxX, maxY, maxZ,
|
||||
color
|
||||
)
|
||||
}
|
||||
is net.hareworks.hcu.lands.model.Shape.Cylinder -> {
|
||||
val minY = (shape.y - shape.bottomHeight).toDouble()
|
||||
val maxY = (shape.y + shape.topHeight + 1).toDouble()
|
||||
|
||||
GeometryVisualizer.drawCylinder(
|
||||
player,
|
||||
shape.x + 0.5, shape.y.toDouble(), shape.z + 0.5,
|
||||
shape.radius.toInt(),
|
||||
minY,
|
||||
maxY,
|
||||
color
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Visualize Current Selection (Blue)
|
||||
val selection = selectionService.getSelection(player.uniqueId)
|
||||
val p1 = selection.point1 ?: return
|
||||
|
||||
// Determine P2: Either set in selection, or dynamic based on cursor if not set (or we show review for both?)
|
||||
// If p2 is set, visualize that.
|
||||
// If p2 is NOT set, visualize dynamic preview using target block.
|
||||
|
||||
var p2 = selection.point2
|
||||
var isDynamic = false
|
||||
|
||||
if (p2 == null) {
|
||||
val target = player.getTargetBlockExact(30)
|
||||
if (target != null) {
|
||||
p2 = target.location
|
||||
isDynamic = true
|
||||
}
|
||||
}
|
||||
|
||||
if (p2 == null) return // No p2 and no target
|
||||
|
||||
// If different world, ignore
|
||||
if (p2 == null) return
|
||||
if (p1.world != p2.world) return
|
||||
|
||||
// ... (Existing Draw Logic for Selection) ...
|
||||
// ... (Existing Draw Logic for Selection) ...
|
||||
if (selection.mode == SelectionMode.CUBOID) {
|
||||
// Cuboid visualization
|
||||
var minX: Double
|
||||
var minY: Double
|
||||
var minZ: Double
|
||||
|
|
@ -70,8 +138,8 @@ class SelectionVisualizerTask(
|
|||
var maxY: Double
|
||||
var maxZ: Double
|
||||
|
||||
if (!selection.p1Sneaking) {
|
||||
// Normal: corner to corner
|
||||
if (!selection.isCenterMode) {
|
||||
// Normal: Corner to Corner (Diagonal)
|
||||
minX = minOf(p1.blockX, p2.blockX).toDouble()
|
||||
minY = minOf(p1.blockY, p2.blockY).toDouble()
|
||||
minZ = minOf(p1.blockZ, p2.blockZ).toDouble()
|
||||
|
|
@ -79,7 +147,7 @@ class SelectionVisualizerTask(
|
|||
maxY = maxOf(p1.blockY, p2.blockY).toDouble() + 1.0
|
||||
maxZ = maxOf(p1.blockZ, p2.blockZ).toDouble() + 1.0
|
||||
} else {
|
||||
// Sneak: P1 center, P2 defines radius
|
||||
// Center Mode: P1 is Center
|
||||
val dx = abs(p1.blockX - p2.blockX)
|
||||
val dy = abs(p1.blockY - p2.blockY)
|
||||
val dz = abs(p1.blockZ - p2.blockZ)
|
||||
|
|
@ -95,9 +163,8 @@ class SelectionVisualizerTask(
|
|||
GeometryVisualizer.drawCuboid(player, minX, minY, minZ, maxX, maxY, maxZ)
|
||||
|
||||
} else {
|
||||
// Cylinder visualization
|
||||
val centerX: Double
|
||||
val centerY: Double
|
||||
val centerY: Double // This variable is actually unused in the logic below if we override args, but kept for clarity if needed
|
||||
val centerZ: Double
|
||||
val radius: Double
|
||||
val minY: Double
|
||||
|
|
@ -108,88 +175,55 @@ class SelectionVisualizerTask(
|
|||
val dist = Math.sqrt((dx * dx + dz * dz).toDouble())
|
||||
radius = kotlin.math.ceil(dist - 0.5)
|
||||
|
||||
if (!selection.p1Sneaking) {
|
||||
// Normal: P1 center, symmetric height based on P2.y diff
|
||||
if (!selection.isCenterMode) {
|
||||
// Normal: P1 is Center, Symmetric Height
|
||||
centerX = p1.blockX + 0.5
|
||||
centerY = p1.blockY + 0.5 // Logic center
|
||||
centerY = p1.blockY + 0.5
|
||||
centerZ = p1.blockZ + 0.5
|
||||
|
||||
val hDiff = abs(p1.blockY - p2.blockY)
|
||||
// Bottom and Top
|
||||
// From center block center, go down hDiff blocks (and include center block?)
|
||||
// If P1.y = 10, P2.y = 12. Diff 2.
|
||||
// Blocks: 8, 9, 10, 11, 12. Range [8, 12]. Height 5. (2*2 + 1)
|
||||
|
||||
// Block coords:
|
||||
val baseBlockY = p1.blockY - hDiff
|
||||
val topBlockY = p1.blockY + hDiff
|
||||
|
||||
minY = baseBlockY.toDouble()
|
||||
maxY = topBlockY.toDouble() + 1.0
|
||||
} else {
|
||||
// Sneak: P1 base center. P2 top center.
|
||||
// Center/Base Mode: P1 is Base, P2 defines Height/Radius
|
||||
centerX = p1.blockX + 0.5
|
||||
centerZ = p1.blockZ + 0.5
|
||||
// Only height from p2
|
||||
val y1 = p1.blockY
|
||||
val y2 = p2.blockY
|
||||
|
||||
val baseBlockY = minOf(y1, y2)
|
||||
val topBlockY = maxOf(y1, y2)
|
||||
|
||||
minY = baseBlockY.toDouble()
|
||||
maxY = topBlockY.toDouble() + 1.0
|
||||
}
|
||||
|
||||
// Calculate blocks for cylinder outline
|
||||
// Draw Cylinder
|
||||
GeometryVisualizer.drawCylinder(player, centerX, p1.blockY.toDouble(), centerZ, radius.toInt(), minY, maxY)
|
||||
|
||||
// Draw Outlines (Surface)
|
||||
val cX = kotlin.math.floor(centerX).toInt()
|
||||
val cZ = kotlin.math.floor(centerZ).toInt()
|
||||
val blocks = mutableSetOf<Pair<Int, Int>>()
|
||||
// Add 0.5 to radius to encompass the full block width of the boundary blocks
|
||||
val actualRadius = radius + 0.5
|
||||
val radiusSq = actualRadius * actualRadius
|
||||
val rInt = actualRadius.toInt() + 1
|
||||
|
||||
for (dx in -rInt..rInt) {
|
||||
for (dz in -rInt..rInt) {
|
||||
// Check if block center is within the radius
|
||||
if (dx * dx + dz * dz <= radiusSq) {
|
||||
blocks.add(Pair(cX + dx, cZ + dz))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Draw wireframe circle (smooth) layered with block outline
|
||||
// Wireframe uses the expanded radius to match the block outline visuals
|
||||
GeometryVisualizer.drawCylinder(player, centerX, p1.blockY.toDouble(), centerZ, radius.toInt(), minY, maxY)
|
||||
|
||||
val color = Color.fromRGB(100, 200, 255)
|
||||
// Draw bottom surface outline
|
||||
GeometryVisualizer.drawBlockSurfaceOutline(
|
||||
player,
|
||||
minY,
|
||||
blocks,
|
||||
{ _, _, _ -> false },
|
||||
color,
|
||||
minY
|
||||
)
|
||||
|
||||
// Draw top surface outline
|
||||
GeometryVisualizer.drawBlockSurfaceOutline(
|
||||
player,
|
||||
maxY,
|
||||
blocks,
|
||||
{ _, _, _ -> false },
|
||||
color,
|
||||
maxY
|
||||
)
|
||||
|
||||
// Vertical edges for the outline?
|
||||
// The current generic visualizer doesn't have a specific "drawVerticalConnectors" for this map-based approach easily accessible
|
||||
// or we'd have to iterate edges.
|
||||
// For now, surface outlines should be sufficient as requested.
|
||||
}
|
||||
|
||||
GeometryVisualizer.drawBlockSurfaceOutline(player, minY, blocks, { _, _, _ -> false }, color, minY)
|
||||
GeometryVisualizer.drawBlockSurfaceOutline(player, maxY, blocks, { _, _, _ -> false }, color, maxY)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Private helper extensions if needed for Shape
|
||||
private fun net.hareworks.hcu.lands.model.Shape.worldName(): String { return "" } // Dummy, not used because we can't easily check
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ dependencies {
|
|||
compileOnly("com.michael-bull.kotlin-result:kotlin-result:2.1.0")
|
||||
compileOnly("net.hareworks:kommand-lib:1.1")
|
||||
compileOnly("net.hareworks:permits-lib")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.0")
|
||||
compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.0")
|
||||
|
||||
// Visualizer Lib (Bundled & Relocated)
|
||||
implementation("net.hareworks.hcu:visualizer-lib:1.0")
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ class LandSectorPlugin : JavaPlugin() {
|
|||
server.pluginManager.registerEvents(net.hareworks.hcu.landsector.listener.SelectionListener(this, selService, service), this)
|
||||
|
||||
// Schedule visualization task
|
||||
net.hareworks.hcu.landsector.task.SelectionVisualizerTask(this, selService).runTaskTimer(this, 0L, 4L)
|
||||
net.hareworks.hcu.landsector.task.SelectionVisualizerTask(this, selService, service).runTaskTimer(this, 0L, 4L)
|
||||
} else {
|
||||
logger.severe("SelectionService not initialized!")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -303,13 +303,21 @@ class SectorListener(
|
|||
} else {
|
||||
content.append(Component.text("Parts:", NamedTextColor.BLACK))
|
||||
ranges.forEach { range ->
|
||||
content.append(Component.text("\n- [${range.id}] ${range.type}", NamedTextColor.DARK_GRAY))
|
||||
content.append(Component.text(" "))
|
||||
content.append(Component.text("\n"))
|
||||
content.append(
|
||||
Component.text("[x] ", NamedTextColor.RED)
|
||||
.clickEvent(ClickEvent.runCommand("/landsector range delete $sectorId ${range.id}"))
|
||||
.hoverEvent(net.kyori.adventure.text.event.HoverEvent.showText(Component.text("Click to delete range")))
|
||||
)
|
||||
|
||||
val info = if (range.type == net.hareworks.hcu.landsector.model.SelectionMode.CUBOID) {
|
||||
"(${range.x1},${range.y1},${range.z1})~(${range.x2},${range.y2},${range.z2})"
|
||||
} else {
|
||||
// Cylinder: x1,y1,z1 is Center. x2=R, y2=Bottom, z2=Top
|
||||
val h = 1 + range.y2 + range.z2
|
||||
"Cyl @(${range.x1},${range.y1},${range.z1}) R:${range.x2} H:$h"
|
||||
}
|
||||
content.append(Component.text(info, NamedTextColor.DARK_GRAY))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,43 +37,38 @@ class SelectionListener(
|
|||
val player = event.player
|
||||
val selection = selectionService.getSelection(player.uniqueId)
|
||||
|
||||
// Left Click: Switch Mode
|
||||
// Left Click: Cancel only
|
||||
if (event.action == Action.LEFT_CLICK_AIR || event.action == Action.LEFT_CLICK_BLOCK) {
|
||||
event.isCancelled = true // Prevent breaking block
|
||||
|
||||
selection.mode = if (selection.mode == SelectionMode.CUBOID) SelectionMode.CYLINDER else SelectionMode.CUBOID
|
||||
player.sendMessage(Component.text("Mode switched to: ${selection.mode}", NamedTextColor.YELLOW))
|
||||
|
||||
// Reset selection on mode switch
|
||||
selection.point1 = null
|
||||
selection.point2 = null
|
||||
selection.p1Sneaking = false
|
||||
event.isCancelled = true
|
||||
return
|
||||
}
|
||||
|
||||
// Right Click: Set Points
|
||||
if (event.action == Action.RIGHT_CLICK_BLOCK) {
|
||||
event.isCancelled = true // Prevent placing
|
||||
if (event.action == Action.RIGHT_CLICK_BLOCK || event.action == Action.RIGHT_CLICK_AIR) {
|
||||
event.isCancelled = true // Prevent placing or item usage
|
||||
|
||||
val clickedBlock = event.clickedBlock ?: return
|
||||
val loc = clickedBlock.location
|
||||
val targetBlock = event.clickedBlock ?: player.rayTraceBlocks(100.0)?.hitBlock
|
||||
|
||||
if (targetBlock == null) return
|
||||
|
||||
val loc = targetBlock.location
|
||||
|
||||
if (selection.point1 == null) {
|
||||
// Set Point 1
|
||||
selection.point1 = loc
|
||||
selection.p1Sneaking = player.isSneaking
|
||||
selection.point2 = null // Clear p2 just in case
|
||||
// selection.isCenterMode is persistent, so we don't set it here based on sneaking
|
||||
selection.point2 = null
|
||||
|
||||
player.sendMessage(Component.text("Position 1 set at ${loc.blockX}, ${loc.blockY}, ${loc.blockZ} (Mode: ${selection.mode}, Sneaking: ${selection.p1Sneaking})", NamedTextColor.GREEN))
|
||||
player.sendMessage(Component.text("Position 1 set at ${loc.blockX}, ${loc.blockY}, ${loc.blockZ} (Mode: ${selection.mode}, CenterMode: ${selection.isCenterMode})", NamedTextColor.GREEN))
|
||||
player.playSound(player.location, org.bukkit.Sound.UI_BUTTON_CLICK, 1.0f, 2.0f)
|
||||
} else {
|
||||
// Set Point 2
|
||||
// If P2 is already set, reset to P1 new? standard standard is cyclic.
|
||||
if (selection.point2 != null) {
|
||||
// Resetting, treat as P1
|
||||
selection.point1 = loc
|
||||
selection.p1Sneaking = player.isSneaking
|
||||
selection.point2 = null
|
||||
player.sendMessage(Component.text("Selection reset. Position 1 set at ${loc.blockX}, ${loc.blockY}, ${loc.blockZ} (Mode: ${selection.mode}, Sneaking: ${selection.p1Sneaking})", NamedTextColor.GREEN))
|
||||
player.sendMessage(Component.text("Selection reset. Position 1 set at ${loc.blockX}, ${loc.blockY}, ${loc.blockZ} (Mode: ${selection.mode}, CenterMode: ${selection.isCenterMode})", NamedTextColor.GREEN))
|
||||
player.playSound(player.location, org.bukkit.Sound.BLOCK_NOTE_BLOCK_BASS, 1.0f, 0.5f)
|
||||
} else {
|
||||
selection.point2 = loc
|
||||
// Check for sector ID
|
||||
|
|
@ -90,10 +85,11 @@ class SelectionListener(
|
|||
selection.mode,
|
||||
p1.blockX, p1.blockY, p1.blockZ,
|
||||
p2.blockX, p2.blockY, p2.blockZ,
|
||||
selection.p1Sneaking
|
||||
selection.isCenterMode
|
||||
)
|
||||
|
||||
player.sendMessage(Component.text("Range added to Sector #$sId.", NamedTextColor.GREEN))
|
||||
player.playSound(player.location, org.bukkit.Sound.ENTITY_PLAYER_LEVELUP, 1.0f, 2.0f)
|
||||
|
||||
// Clear selection
|
||||
selection.point1 = null
|
||||
|
|
@ -104,6 +100,8 @@ class SelectionListener(
|
|||
// Show Cost / Area
|
||||
player.sendMessage(Component.text(selectionService.getAreaDetails(player.uniqueId), NamedTextColor.AQUA))
|
||||
player.sendMessage(Component.text("Cost: ${selectionService.getCost(player.uniqueId)}", NamedTextColor.GOLD))
|
||||
|
||||
player.playSound(player.location, org.bukkit.Sound.UI_BUTTON_CLICK, 1.0f, 1.0f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -116,7 +114,25 @@ class SelectionListener(
|
|||
val key = NamespacedKey(plugin, "component")
|
||||
if (item.itemMeta?.persistentDataContainer?.get(key, PersistentDataType.STRING) == "land_sector_tool") {
|
||||
event.isCancelled = true
|
||||
event.player.sendMessage(Component.text("You cannot drop this tool.", NamedTextColor.RED))
|
||||
|
||||
val player = event.player
|
||||
val selection = selectionService.getSelection(player.uniqueId)
|
||||
|
||||
if (player.isSneaking) {
|
||||
// Toggle Center/Corner Mode
|
||||
selection.isCenterMode = !selection.isCenterMode
|
||||
player.sendMessage(Component.text("Selection Modifier: ${if (selection.isCenterMode) "Center/Base Mode" else "Corner/Symmetric Mode"}", NamedTextColor.GOLD))
|
||||
player.playSound(player.location, org.bukkit.Sound.BLOCK_COMPARATOR_CLICK, 1.0f, 1.0f)
|
||||
} else {
|
||||
// Toggle Shape Mode
|
||||
selection.mode = if (selection.mode == SelectionMode.CUBOID) SelectionMode.CYLINDER else SelectionMode.CUBOID
|
||||
player.sendMessage(Component.text("Shape: ${selection.mode}", NamedTextColor.YELLOW))
|
||||
player.playSound(player.location, org.bukkit.Sound.ITEM_BOOK_PAGE_TURN, 1.0f, 1.0f)
|
||||
|
||||
// Reset selection on mode switch
|
||||
selection.point1 = null
|
||||
selection.point2 = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,5 +11,5 @@ data class SelectionData(
|
|||
var mode: SelectionMode = SelectionMode.CUBOID,
|
||||
var point1: Location? = null,
|
||||
var point2: Location? = null,
|
||||
var p1Sneaking: Boolean = false
|
||||
var isCenterMode: Boolean = false // Replaces old p1Sneaking logic with explicit toggle
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@ package net.hareworks.hcu.landsector.service
|
|||
|
||||
import net.hareworks.hcu.landsector.database.SectorDraftsTable
|
||||
import net.hareworks.hcu.landsector.database.SectorsTable
|
||||
// SectorRangesTable removed
|
||||
import net.hareworks.hcu.landsector.model.Sector
|
||||
import net.hareworks.hcu.landsector.model.SectorRange
|
||||
import net.hareworks.hcu.landsector.model.SelectionMode
|
||||
import net.hareworks.hcu.lands.model.LandData
|
||||
import net.hareworks.hcu.lands.model.Shape
|
||||
import org.jetbrains.exposed.v1.core.eq
|
||||
import org.jetbrains.exposed.v1.jdbc.Database
|
||||
import org.jetbrains.exposed.v1.jdbc.SchemaUtils
|
||||
|
|
@ -385,7 +386,7 @@ class SectorService(private val database: Database, private val landService: net
|
|||
sectorId,
|
||||
SelectionMode.CYLINDER,
|
||||
shape.x, shape.y, shape.z,
|
||||
shape.x, shape.y, shape.z,
|
||||
shape.radius.toInt(), shape.bottomHeight, shape.topHeight,
|
||||
false
|
||||
)
|
||||
}
|
||||
|
|
@ -395,6 +396,17 @@ class SectorService(private val database: Database, private val landService: net
|
|||
}
|
||||
}
|
||||
|
||||
fun getSectorShapes(sectorId: Int): List<net.hareworks.hcu.lands.model.Shape> {
|
||||
val sector = getSector(sectorId) ?: return emptyList()
|
||||
|
||||
val land = if (sector.landId != null) {
|
||||
landService.getLand(sector.landId)
|
||||
} else {
|
||||
draftLands[sectorId]
|
||||
} ?: return emptyList()
|
||||
|
||||
return land.data.parts
|
||||
}
|
||||
fun deleteRange(sectorId: Int, rangeIndex: Int): Boolean {
|
||||
val sector = getSector(sectorId) ?: return false
|
||||
if (sector.landId != null) return false // Cannot edit confirmed
|
||||
|
|
@ -470,3 +482,5 @@ class SectorService(private val database: Database, private val landService: net
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -33,13 +33,13 @@ class SelectionService {
|
|||
val height: Int
|
||||
val length: Int
|
||||
|
||||
if (!data.p1Sneaking) {
|
||||
if (!data.isCenterMode) {
|
||||
// Normal: P1 to P2
|
||||
width = abs(p1.blockX - p2.blockX) + 1
|
||||
height = abs(p1.blockY - p2.blockY) + 1
|
||||
length = abs(p1.blockZ - p2.blockZ) + 1
|
||||
} else {
|
||||
// Sneak: P1 is center
|
||||
// Center Mode: P1 is center
|
||||
val dx = abs(p1.blockX - p2.blockX)
|
||||
val dy = abs(p1.blockY - p2.blockY)
|
||||
val dz = abs(p1.blockZ - p2.blockZ)
|
||||
|
|
@ -58,12 +58,12 @@ class SelectionService {
|
|||
val baseRadius = ceil(dist - 0.5)
|
||||
val radius = baseRadius + 0.5
|
||||
|
||||
val totalHeight = if (!data.p1Sneaking) {
|
||||
val totalHeight = if (!data.isCenterMode) {
|
||||
// Normal: P1 center, symmetric height
|
||||
val h = abs(p1.blockY - p2.blockY)
|
||||
h * 2 + 1
|
||||
} else {
|
||||
// Sneak: P1 base, P2 top
|
||||
// Center Mode: P1 base, P2 top (or vice versa, height is diff)
|
||||
abs(p1.blockY - p2.blockY) + 1
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package net.hareworks.hcu.landsector.task
|
|||
|
||||
import net.hareworks.hcu.landsector.model.SelectionMode
|
||||
import net.hareworks.hcu.landsector.service.SelectionService
|
||||
import net.hareworks.hcu.landsector.service.SectorService
|
||||
import net.hareworks.hcu.visualizer.GeometryVisualizer
|
||||
import net.kyori.adventure.text.Component
|
||||
import net.kyori.adventure.text.format.NamedTextColor
|
||||
|
|
@ -18,7 +19,8 @@ import kotlin.math.abs
|
|||
|
||||
class SelectionVisualizerTask(
|
||||
private val plugin: JavaPlugin,
|
||||
private val selectionService: SelectionService
|
||||
private val selectionService: SelectionService,
|
||||
private val sectorService: SectorService
|
||||
) : BukkitRunnable() {
|
||||
|
||||
override fun run() {
|
||||
|
|
@ -28,41 +30,107 @@ class SelectionVisualizerTask(
|
|||
}
|
||||
|
||||
private fun visualize(player: Player) {
|
||||
// Check if holding tool
|
||||
// Check if tool is in inventory
|
||||
val key = NamespacedKey(plugin, "component")
|
||||
val hasTool = player.inventory.contents.any { item ->
|
||||
item != null && item.type == Material.FLINT &&
|
||||
item.itemMeta?.persistentDataContainer?.get(key, PersistentDataType.STRING) == "land_sector_tool"
|
||||
val sectorKey = NamespacedKey(plugin, "sector_id")
|
||||
|
||||
// Find tool in inventory to get sector ID
|
||||
var sectorId: Int? = null
|
||||
var hasTool = false
|
||||
|
||||
// 1. Check held items first (Priority: Held ID > Inventory ID)
|
||||
val heldItems = listOf(player.inventory.itemInMainHand, player.inventory.itemInOffHand)
|
||||
for (item in heldItems) {
|
||||
if (item.type == Material.FLINT) {
|
||||
val meta = item.itemMeta
|
||||
if (meta != null && meta.persistentDataContainer.get(key, PersistentDataType.STRING) == "land_sector_tool") {
|
||||
hasTool = true
|
||||
if (meta.persistentDataContainer.has(sectorKey, PersistentDataType.INTEGER)) {
|
||||
sectorId = meta.persistentDataContainer.get(sectorKey, PersistentDataType.INTEGER)
|
||||
break // Found specific sector tool in hand, prioritize this.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. If no specific sector ID found in hand, scan inventory for any linked tool
|
||||
if (sectorId == null) {
|
||||
for (item in player.inventory.contents) {
|
||||
if (item != null && item.type == Material.FLINT) {
|
||||
val meta = item.itemMeta ?: continue
|
||||
if (meta.persistentDataContainer.get(key, PersistentDataType.STRING) == "land_sector_tool") {
|
||||
hasTool = true
|
||||
if (meta.persistentDataContainer.has(sectorKey, PersistentDataType.INTEGER)) {
|
||||
sectorId = meta.persistentDataContainer.get(sectorKey, PersistentDataType.INTEGER)
|
||||
break // Found linked tool in inventory.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasTool) return
|
||||
|
||||
// 1. Visualize Existing Ranges (Green)
|
||||
if (sectorId != null) {
|
||||
// World Check
|
||||
val sector = sectorService.getSector(sectorId)
|
||||
if (sector != null && sector.world == player.world.name) {
|
||||
val shapes = sectorService.getSectorShapes(sectorId)
|
||||
val color = Color.fromRGB(0, 255, 0) // Green for saved
|
||||
|
||||
for (shape in shapes) {
|
||||
when (shape) {
|
||||
is net.hareworks.hcu.lands.model.Shape.Cuboid -> {
|
||||
val minX = minOf(shape.x1, shape.x2).toDouble()
|
||||
val minY = minOf(shape.y1, shape.y2).toDouble()
|
||||
val minZ = minOf(shape.z1, shape.z2).toDouble()
|
||||
val maxX = maxOf(shape.x1, shape.x2).toDouble() + 1.0
|
||||
val maxY = maxOf(shape.y1, shape.y2).toDouble() + 1.0
|
||||
val maxZ = maxOf(shape.z1, shape.z2).toDouble() + 1.0
|
||||
|
||||
GeometryVisualizer.drawCuboid(
|
||||
player,
|
||||
minX, minY, minZ,
|
||||
maxX, maxY, maxZ,
|
||||
color
|
||||
)
|
||||
}
|
||||
is net.hareworks.hcu.lands.model.Shape.Cylinder -> {
|
||||
val minY = (shape.y - shape.bottomHeight).toDouble()
|
||||
val maxY = (shape.y + shape.topHeight + 1).toDouble()
|
||||
|
||||
GeometryVisualizer.drawCylinder(
|
||||
player,
|
||||
shape.x + 0.5, shape.y.toDouble(), shape.z + 0.5,
|
||||
shape.radius.toInt(),
|
||||
minY,
|
||||
maxY,
|
||||
color
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Visualize Current Selection (Blue)
|
||||
val selection = selectionService.getSelection(player.uniqueId)
|
||||
val p1 = selection.point1 ?: return
|
||||
|
||||
// Determine P2: Either set in selection, or dynamic based on cursor if not set (or we show review for both?)
|
||||
// If p2 is set, visualize that.
|
||||
// If p2 is NOT set, visualize dynamic preview using target block.
|
||||
|
||||
var p2 = selection.point2
|
||||
var isDynamic = false
|
||||
|
||||
if (p2 == null) {
|
||||
val target = player.getTargetBlockExact(30)
|
||||
if (target != null) {
|
||||
p2 = target.location
|
||||
isDynamic = true
|
||||
}
|
||||
}
|
||||
|
||||
if (p2 == null) return // No p2 and no target
|
||||
|
||||
// If different world, ignore
|
||||
if (p2 == null) return
|
||||
if (p1.world != p2.world) return
|
||||
|
||||
// ... (Existing Draw Logic for Selection) ...
|
||||
// ... (Existing Draw Logic for Selection) ...
|
||||
if (selection.mode == SelectionMode.CUBOID) {
|
||||
// Cuboid visualization
|
||||
var minX: Double
|
||||
var minY: Double
|
||||
var minZ: Double
|
||||
|
|
@ -70,8 +138,8 @@ class SelectionVisualizerTask(
|
|||
var maxY: Double
|
||||
var maxZ: Double
|
||||
|
||||
if (!selection.p1Sneaking) {
|
||||
// Normal: corner to corner
|
||||
if (!selection.isCenterMode) {
|
||||
// Normal: Corner to Corner (Diagonal)
|
||||
minX = minOf(p1.blockX, p2.blockX).toDouble()
|
||||
minY = minOf(p1.blockY, p2.blockY).toDouble()
|
||||
minZ = minOf(p1.blockZ, p2.blockZ).toDouble()
|
||||
|
|
@ -79,7 +147,7 @@ class SelectionVisualizerTask(
|
|||
maxY = maxOf(p1.blockY, p2.blockY).toDouble() + 1.0
|
||||
maxZ = maxOf(p1.blockZ, p2.blockZ).toDouble() + 1.0
|
||||
} else {
|
||||
// Sneak: P1 center, P2 defines radius
|
||||
// Center Mode: P1 is Center
|
||||
val dx = abs(p1.blockX - p2.blockX)
|
||||
val dy = abs(p1.blockY - p2.blockY)
|
||||
val dz = abs(p1.blockZ - p2.blockZ)
|
||||
|
|
@ -95,9 +163,8 @@ class SelectionVisualizerTask(
|
|||
GeometryVisualizer.drawCuboid(player, minX, minY, minZ, maxX, maxY, maxZ)
|
||||
|
||||
} else {
|
||||
// Cylinder visualization
|
||||
val centerX: Double
|
||||
val centerY: Double
|
||||
val centerY: Double // This variable is actually unused in the logic below if we override args, but kept for clarity if needed
|
||||
val centerZ: Double
|
||||
val radius: Double
|
||||
val minY: Double
|
||||
|
|
@ -108,88 +175,55 @@ class SelectionVisualizerTask(
|
|||
val dist = Math.sqrt((dx * dx + dz * dz).toDouble())
|
||||
radius = kotlin.math.ceil(dist - 0.5)
|
||||
|
||||
if (!selection.p1Sneaking) {
|
||||
// Normal: P1 center, symmetric height based on P2.y diff
|
||||
if (!selection.isCenterMode) {
|
||||
// Normal: P1 is Center, Symmetric Height
|
||||
centerX = p1.blockX + 0.5
|
||||
centerY = p1.blockY + 0.5 // Logic center
|
||||
centerY = p1.blockY + 0.5
|
||||
centerZ = p1.blockZ + 0.5
|
||||
|
||||
val hDiff = abs(p1.blockY - p2.blockY)
|
||||
// Bottom and Top
|
||||
// From center block center, go down hDiff blocks (and include center block?)
|
||||
// If P1.y = 10, P2.y = 12. Diff 2.
|
||||
// Blocks: 8, 9, 10, 11, 12. Range [8, 12]. Height 5. (2*2 + 1)
|
||||
|
||||
// Block coords:
|
||||
val baseBlockY = p1.blockY - hDiff
|
||||
val topBlockY = p1.blockY + hDiff
|
||||
|
||||
minY = baseBlockY.toDouble()
|
||||
maxY = topBlockY.toDouble() + 1.0
|
||||
} else {
|
||||
// Sneak: P1 base center. P2 top center.
|
||||
// Center/Base Mode: P1 is Base, P2 defines Height/Radius
|
||||
centerX = p1.blockX + 0.5
|
||||
centerZ = p1.blockZ + 0.5
|
||||
// Only height from p2
|
||||
val y1 = p1.blockY
|
||||
val y2 = p2.blockY
|
||||
|
||||
val baseBlockY = minOf(y1, y2)
|
||||
val topBlockY = maxOf(y1, y2)
|
||||
|
||||
minY = baseBlockY.toDouble()
|
||||
maxY = topBlockY.toDouble() + 1.0
|
||||
}
|
||||
|
||||
// Calculate blocks for cylinder outline
|
||||
// Draw Cylinder
|
||||
GeometryVisualizer.drawCylinder(player, centerX, p1.blockY.toDouble(), centerZ, radius.toInt(), minY, maxY)
|
||||
|
||||
// Draw Outlines (Surface)
|
||||
val cX = kotlin.math.floor(centerX).toInt()
|
||||
val cZ = kotlin.math.floor(centerZ).toInt()
|
||||
val blocks = mutableSetOf<Pair<Int, Int>>()
|
||||
// Add 0.5 to radius to encompass the full block width of the boundary blocks
|
||||
val actualRadius = radius + 0.5
|
||||
val radiusSq = actualRadius * actualRadius
|
||||
val rInt = actualRadius.toInt() + 1
|
||||
|
||||
for (dx in -rInt..rInt) {
|
||||
for (dz in -rInt..rInt) {
|
||||
// Check if block center is within the radius
|
||||
if (dx * dx + dz * dz <= radiusSq) {
|
||||
blocks.add(Pair(cX + dx, cZ + dz))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Draw wireframe circle (smooth) layered with block outline
|
||||
// Wireframe uses the expanded radius to match the block outline visuals
|
||||
GeometryVisualizer.drawCylinder(player, centerX, p1.blockY.toDouble(), centerZ, radius.toInt(), minY, maxY)
|
||||
|
||||
val color = Color.fromRGB(100, 200, 255)
|
||||
// Draw bottom surface outline
|
||||
GeometryVisualizer.drawBlockSurfaceOutline(
|
||||
player,
|
||||
minY,
|
||||
blocks,
|
||||
{ _, _, _ -> false },
|
||||
color,
|
||||
minY
|
||||
)
|
||||
|
||||
// Draw top surface outline
|
||||
GeometryVisualizer.drawBlockSurfaceOutline(
|
||||
player,
|
||||
maxY,
|
||||
blocks,
|
||||
{ _, _, _ -> false },
|
||||
color,
|
||||
maxY
|
||||
)
|
||||
|
||||
// Vertical edges for the outline?
|
||||
// The current generic visualizer doesn't have a specific "drawVerticalConnectors" for this map-based approach easily accessible
|
||||
// or we'd have to iterate edges.
|
||||
// For now, surface outlines should be sufficient as requested.
|
||||
}
|
||||
|
||||
GeometryVisualizer.drawBlockSurfaceOutline(player, minY, blocks, { _, _, _ -> false }, color, minY)
|
||||
GeometryVisualizer.drawBlockSurfaceOutline(player, maxY, blocks, { _, _, _ -> false }, color, maxY)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Private helper extensions if needed for Shape
|
||||
private fun net.hareworks.hcu.lands.model.Shape.worldName(): String { return "" } // Dummy, not used because we can't easily check
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user