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)
|
server.pluginManager.registerEvents(net.hareworks.hcu.landsector.listener.SelectionListener(this, selService, service), this)
|
||||||
|
|
||||||
// Schedule visualization task
|
// 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 {
|
} else {
|
||||||
logger.severe("SelectionService not initialized!")
|
logger.severe("SelectionService not initialized!")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -303,13 +303,21 @@ class SectorListener(
|
||||||
} else {
|
} else {
|
||||||
content.append(Component.text("Parts:", NamedTextColor.BLACK))
|
content.append(Component.text("Parts:", NamedTextColor.BLACK))
|
||||||
ranges.forEach { range ->
|
ranges.forEach { range ->
|
||||||
content.append(Component.text("\n- [${range.id}] ${range.type}", NamedTextColor.DARK_GRAY))
|
content.append(Component.text("\n"))
|
||||||
content.append(Component.text(" "))
|
|
||||||
content.append(
|
content.append(
|
||||||
Component.text("[x] ", NamedTextColor.RED)
|
Component.text("[x] ", NamedTextColor.RED)
|
||||||
.clickEvent(ClickEvent.runCommand("/landsector range delete $sectorId ${range.id}"))
|
.clickEvent(ClickEvent.runCommand("/landsector range delete $sectorId ${range.id}"))
|
||||||
.hoverEvent(net.kyori.adventure.text.event.HoverEvent.showText(Component.text("Click to delete range")))
|
.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 player = event.player
|
||||||
val selection = selectionService.getSelection(player.uniqueId)
|
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) {
|
if (event.action == Action.LEFT_CLICK_AIR || event.action == Action.LEFT_CLICK_BLOCK) {
|
||||||
event.isCancelled = true // Prevent breaking block
|
event.isCancelled = true
|
||||||
|
|
||||||
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
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Right Click: Set Points
|
// Right Click: Set Points
|
||||||
if (event.action == Action.RIGHT_CLICK_BLOCK) {
|
if (event.action == Action.RIGHT_CLICK_BLOCK || event.action == Action.RIGHT_CLICK_AIR) {
|
||||||
event.isCancelled = true // Prevent placing
|
event.isCancelled = true // Prevent placing or item usage
|
||||||
|
|
||||||
val clickedBlock = event.clickedBlock ?: return
|
val targetBlock = event.clickedBlock ?: player.rayTraceBlocks(100.0)?.hitBlock
|
||||||
val loc = clickedBlock.location
|
|
||||||
|
if (targetBlock == null) return
|
||||||
|
|
||||||
|
val loc = targetBlock.location
|
||||||
|
|
||||||
if (selection.point1 == null) {
|
if (selection.point1 == null) {
|
||||||
// Set Point 1
|
// Set Point 1
|
||||||
selection.point1 = loc
|
selection.point1 = loc
|
||||||
selection.p1Sneaking = player.isSneaking
|
// selection.isCenterMode is persistent, so we don't set it here based on sneaking
|
||||||
selection.point2 = null // Clear p2 just in case
|
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 {
|
} else {
|
||||||
// Set Point 2
|
// Set Point 2
|
||||||
// If P2 is already set, reset to P1 new? standard standard is cyclic.
|
|
||||||
if (selection.point2 != null) {
|
if (selection.point2 != null) {
|
||||||
// Resetting, treat as P1
|
// Resetting, treat as P1
|
||||||
selection.point1 = loc
|
selection.point1 = loc
|
||||||
selection.p1Sneaking = player.isSneaking
|
|
||||||
selection.point2 = null
|
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 {
|
} else {
|
||||||
selection.point2 = loc
|
selection.point2 = loc
|
||||||
// Check for sector ID
|
// Check for sector ID
|
||||||
|
|
@ -90,10 +85,11 @@ class SelectionListener(
|
||||||
selection.mode,
|
selection.mode,
|
||||||
p1.blockX, p1.blockY, p1.blockZ,
|
p1.blockX, p1.blockY, p1.blockZ,
|
||||||
p2.blockX, p2.blockY, p2.blockZ,
|
p2.blockX, p2.blockY, p2.blockZ,
|
||||||
selection.p1Sneaking
|
selection.isCenterMode
|
||||||
)
|
)
|
||||||
|
|
||||||
player.sendMessage(Component.text("Range added to Sector #$sId.", NamedTextColor.GREEN))
|
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
|
// Clear selection
|
||||||
selection.point1 = null
|
selection.point1 = null
|
||||||
|
|
@ -104,6 +100,8 @@ class SelectionListener(
|
||||||
// Show Cost / Area
|
// Show Cost / Area
|
||||||
player.sendMessage(Component.text(selectionService.getAreaDetails(player.uniqueId), NamedTextColor.AQUA))
|
player.sendMessage(Component.text(selectionService.getAreaDetails(player.uniqueId), NamedTextColor.AQUA))
|
||||||
player.sendMessage(Component.text("Cost: ${selectionService.getCost(player.uniqueId)}", NamedTextColor.GOLD))
|
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")
|
val key = NamespacedKey(plugin, "component")
|
||||||
if (item.itemMeta?.persistentDataContainer?.get(key, PersistentDataType.STRING) == "land_sector_tool") {
|
if (item.itemMeta?.persistentDataContainer?.get(key, PersistentDataType.STRING) == "land_sector_tool") {
|
||||||
event.isCancelled = true
|
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 mode: SelectionMode = SelectionMode.CUBOID,
|
||||||
var point1: Location? = null,
|
var point1: Location? = null,
|
||||||
var point2: 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.SectorDraftsTable
|
||||||
import net.hareworks.hcu.landsector.database.SectorsTable
|
import net.hareworks.hcu.landsector.database.SectorsTable
|
||||||
// SectorRangesTable removed
|
|
||||||
import net.hareworks.hcu.landsector.model.Sector
|
import net.hareworks.hcu.landsector.model.Sector
|
||||||
import net.hareworks.hcu.landsector.model.SectorRange
|
import net.hareworks.hcu.landsector.model.SectorRange
|
||||||
import net.hareworks.hcu.landsector.model.SelectionMode
|
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.core.eq
|
||||||
import org.jetbrains.exposed.v1.jdbc.Database
|
import org.jetbrains.exposed.v1.jdbc.Database
|
||||||
import org.jetbrains.exposed.v1.jdbc.SchemaUtils
|
import org.jetbrains.exposed.v1.jdbc.SchemaUtils
|
||||||
|
|
@ -385,7 +386,7 @@ class SectorService(private val database: Database, private val landService: net
|
||||||
sectorId,
|
sectorId,
|
||||||
SelectionMode.CYLINDER,
|
SelectionMode.CYLINDER,
|
||||||
shape.x, shape.y, shape.z,
|
shape.x, shape.y, shape.z,
|
||||||
shape.x, shape.y, shape.z,
|
shape.radius.toInt(), shape.bottomHeight, shape.topHeight,
|
||||||
false
|
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 {
|
fun deleteRange(sectorId: Int, rangeIndex: Int): Boolean {
|
||||||
val sector = getSector(sectorId) ?: return false
|
val sector = getSector(sectorId) ?: return false
|
||||||
if (sector.landId != null) return false // Cannot edit confirmed
|
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 height: Int
|
||||||
val length: Int
|
val length: Int
|
||||||
|
|
||||||
if (!data.p1Sneaking) {
|
if (!data.isCenterMode) {
|
||||||
// Normal: P1 to P2
|
// Normal: P1 to P2
|
||||||
width = abs(p1.blockX - p2.blockX) + 1
|
width = abs(p1.blockX - p2.blockX) + 1
|
||||||
height = abs(p1.blockY - p2.blockY) + 1
|
height = abs(p1.blockY - p2.blockY) + 1
|
||||||
length = abs(p1.blockZ - p2.blockZ) + 1
|
length = abs(p1.blockZ - p2.blockZ) + 1
|
||||||
} else {
|
} else {
|
||||||
// Sneak: P1 is center
|
// Center Mode: P1 is center
|
||||||
val dx = abs(p1.blockX - p2.blockX)
|
val dx = abs(p1.blockX - p2.blockX)
|
||||||
val dy = abs(p1.blockY - p2.blockY)
|
val dy = abs(p1.blockY - p2.blockY)
|
||||||
val dz = abs(p1.blockZ - p2.blockZ)
|
val dz = abs(p1.blockZ - p2.blockZ)
|
||||||
|
|
@ -58,12 +58,12 @@ class SelectionService {
|
||||||
val baseRadius = ceil(dist - 0.5)
|
val baseRadius = ceil(dist - 0.5)
|
||||||
val radius = baseRadius + 0.5
|
val radius = baseRadius + 0.5
|
||||||
|
|
||||||
val totalHeight = if (!data.p1Sneaking) {
|
val totalHeight = if (!data.isCenterMode) {
|
||||||
// Normal: P1 center, symmetric height
|
// Normal: P1 center, symmetric height
|
||||||
val h = abs(p1.blockY - p2.blockY)
|
val h = abs(p1.blockY - p2.blockY)
|
||||||
h * 2 + 1
|
h * 2 + 1
|
||||||
} else {
|
} else {
|
||||||
// Sneak: P1 base, P2 top
|
// Center Mode: P1 base, P2 top (or vice versa, height is diff)
|
||||||
abs(p1.blockY - p2.blockY) + 1
|
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.model.SelectionMode
|
||||||
import net.hareworks.hcu.landsector.service.SelectionService
|
import net.hareworks.hcu.landsector.service.SelectionService
|
||||||
|
import net.hareworks.hcu.landsector.service.SectorService
|
||||||
import net.hareworks.hcu.visualizer.GeometryVisualizer
|
import net.hareworks.hcu.visualizer.GeometryVisualizer
|
||||||
import net.kyori.adventure.text.Component
|
import net.kyori.adventure.text.Component
|
||||||
import net.kyori.adventure.text.format.NamedTextColor
|
import net.kyori.adventure.text.format.NamedTextColor
|
||||||
|
|
@ -18,7 +19,8 @@ import kotlin.math.abs
|
||||||
|
|
||||||
class SelectionVisualizerTask(
|
class SelectionVisualizerTask(
|
||||||
private val plugin: JavaPlugin,
|
private val plugin: JavaPlugin,
|
||||||
private val selectionService: SelectionService
|
private val selectionService: SelectionService,
|
||||||
|
private val sectorService: SectorService
|
||||||
) : BukkitRunnable() {
|
) : BukkitRunnable() {
|
||||||
|
|
||||||
override fun run() {
|
override fun run() {
|
||||||
|
|
@ -28,41 +30,107 @@ class SelectionVisualizerTask(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun visualize(player: Player) {
|
private fun visualize(player: Player) {
|
||||||
// Check if holding tool
|
|
||||||
// Check if tool is in inventory
|
|
||||||
val key = NamespacedKey(plugin, "component")
|
val key = NamespacedKey(plugin, "component")
|
||||||
val hasTool = player.inventory.contents.any { item ->
|
val sectorKey = NamespacedKey(plugin, "sector_id")
|
||||||
item != null && item.type == Material.FLINT &&
|
|
||||||
item.itemMeta?.persistentDataContainer?.get(key, PersistentDataType.STRING) == "land_sector_tool"
|
// 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
|
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 selection = selectionService.getSelection(player.uniqueId)
|
||||||
val p1 = selection.point1 ?: return
|
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 p2 = selection.point2
|
||||||
var isDynamic = false
|
|
||||||
|
|
||||||
if (p2 == null) {
|
if (p2 == null) {
|
||||||
val target = player.getTargetBlockExact(30)
|
val target = player.getTargetBlockExact(30)
|
||||||
if (target != null) {
|
if (target != null) {
|
||||||
p2 = target.location
|
p2 = target.location
|
||||||
isDynamic = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p2 == null) return // No p2 and no target
|
if (p2 == null) return
|
||||||
|
|
||||||
// If different world, ignore
|
|
||||||
if (p1.world != p2.world) return
|
if (p1.world != p2.world) return
|
||||||
|
|
||||||
|
// ... (Existing Draw Logic for Selection) ...
|
||||||
|
// ... (Existing Draw Logic for Selection) ...
|
||||||
if (selection.mode == SelectionMode.CUBOID) {
|
if (selection.mode == SelectionMode.CUBOID) {
|
||||||
// Cuboid visualization
|
|
||||||
var minX: Double
|
var minX: Double
|
||||||
var minY: Double
|
var minY: Double
|
||||||
var minZ: Double
|
var minZ: Double
|
||||||
|
|
@ -70,8 +138,8 @@ class SelectionVisualizerTask(
|
||||||
var maxY: Double
|
var maxY: Double
|
||||||
var maxZ: Double
|
var maxZ: Double
|
||||||
|
|
||||||
if (!selection.p1Sneaking) {
|
if (!selection.isCenterMode) {
|
||||||
// Normal: corner to corner
|
// Normal: Corner to Corner (Diagonal)
|
||||||
minX = minOf(p1.blockX, p2.blockX).toDouble()
|
minX = minOf(p1.blockX, p2.blockX).toDouble()
|
||||||
minY = minOf(p1.blockY, p2.blockY).toDouble()
|
minY = minOf(p1.blockY, p2.blockY).toDouble()
|
||||||
minZ = minOf(p1.blockZ, p2.blockZ).toDouble()
|
minZ = minOf(p1.blockZ, p2.blockZ).toDouble()
|
||||||
|
|
@ -79,7 +147,7 @@ class SelectionVisualizerTask(
|
||||||
maxY = maxOf(p1.blockY, p2.blockY).toDouble() + 1.0
|
maxY = maxOf(p1.blockY, p2.blockY).toDouble() + 1.0
|
||||||
maxZ = maxOf(p1.blockZ, p2.blockZ).toDouble() + 1.0
|
maxZ = maxOf(p1.blockZ, p2.blockZ).toDouble() + 1.0
|
||||||
} else {
|
} else {
|
||||||
// Sneak: P1 center, P2 defines radius
|
// Center Mode: P1 is Center
|
||||||
val dx = abs(p1.blockX - p2.blockX)
|
val dx = abs(p1.blockX - p2.blockX)
|
||||||
val dy = abs(p1.blockY - p2.blockY)
|
val dy = abs(p1.blockY - p2.blockY)
|
||||||
val dz = abs(p1.blockZ - p2.blockZ)
|
val dz = abs(p1.blockZ - p2.blockZ)
|
||||||
|
|
@ -95,9 +163,8 @@ class SelectionVisualizerTask(
|
||||||
GeometryVisualizer.drawCuboid(player, minX, minY, minZ, maxX, maxY, maxZ)
|
GeometryVisualizer.drawCuboid(player, minX, minY, minZ, maxX, maxY, maxZ)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Cylinder visualization
|
|
||||||
val centerX: Double
|
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 centerZ: Double
|
||||||
val radius: Double
|
val radius: Double
|
||||||
val minY: Double
|
val minY: Double
|
||||||
|
|
@ -108,88 +175,55 @@ class SelectionVisualizerTask(
|
||||||
val dist = Math.sqrt((dx * dx + dz * dz).toDouble())
|
val dist = Math.sqrt((dx * dx + dz * dz).toDouble())
|
||||||
radius = kotlin.math.ceil(dist - 0.5)
|
radius = kotlin.math.ceil(dist - 0.5)
|
||||||
|
|
||||||
if (!selection.p1Sneaking) {
|
if (!selection.isCenterMode) {
|
||||||
// Normal: P1 center, symmetric height based on P2.y diff
|
// Normal: P1 is Center, Symmetric Height
|
||||||
centerX = p1.blockX + 0.5
|
centerX = p1.blockX + 0.5
|
||||||
centerY = p1.blockY + 0.5 // Logic center
|
centerY = p1.blockY + 0.5
|
||||||
centerZ = p1.blockZ + 0.5
|
centerZ = p1.blockZ + 0.5
|
||||||
|
|
||||||
val hDiff = abs(p1.blockY - p2.blockY)
|
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 baseBlockY = p1.blockY - hDiff
|
||||||
val topBlockY = p1.blockY + hDiff
|
val topBlockY = p1.blockY + hDiff
|
||||||
|
|
||||||
minY = baseBlockY.toDouble()
|
minY = baseBlockY.toDouble()
|
||||||
maxY = topBlockY.toDouble() + 1.0
|
maxY = topBlockY.toDouble() + 1.0
|
||||||
} else {
|
} else {
|
||||||
// Sneak: P1 base center. P2 top center.
|
// Center/Base Mode: P1 is Base, P2 defines Height/Radius
|
||||||
centerX = p1.blockX + 0.5
|
centerX = p1.blockX + 0.5
|
||||||
centerZ = p1.blockZ + 0.5
|
centerZ = p1.blockZ + 0.5
|
||||||
// Only height from p2
|
|
||||||
val y1 = p1.blockY
|
val y1 = p1.blockY
|
||||||
val y2 = p2.blockY
|
val y2 = p2.blockY
|
||||||
|
|
||||||
val baseBlockY = minOf(y1, y2)
|
val baseBlockY = minOf(y1, y2)
|
||||||
val topBlockY = maxOf(y1, y2)
|
val topBlockY = maxOf(y1, y2)
|
||||||
|
|
||||||
minY = baseBlockY.toDouble()
|
minY = baseBlockY.toDouble()
|
||||||
maxY = topBlockY.toDouble() + 1.0
|
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 cX = kotlin.math.floor(centerX).toInt()
|
||||||
val cZ = kotlin.math.floor(centerZ).toInt()
|
val cZ = kotlin.math.floor(centerZ).toInt()
|
||||||
val blocks = mutableSetOf<Pair<Int, Int>>()
|
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 actualRadius = radius + 0.5
|
||||||
val radiusSq = actualRadius * actualRadius
|
val radiusSq = actualRadius * actualRadius
|
||||||
val rInt = actualRadius.toInt() + 1
|
val rInt = actualRadius.toInt() + 1
|
||||||
|
|
||||||
for (dx in -rInt..rInt) {
|
for (dx in -rInt..rInt) {
|
||||||
for (dz in -rInt..rInt) {
|
for (dz in -rInt..rInt) {
|
||||||
// Check if block center is within the radius
|
|
||||||
if (dx * dx + dz * dz <= radiusSq) {
|
if (dx * dx + dz * dz <= radiusSq) {
|
||||||
blocks.add(Pair(cX + dx, cZ + dz))
|
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)
|
val color = Color.fromRGB(100, 200, 255)
|
||||||
// Draw bottom surface outline
|
GeometryVisualizer.drawBlockSurfaceOutline(player, minY, blocks, { _, _, _ -> false }, color, minY)
|
||||||
GeometryVisualizer.drawBlockSurfaceOutline(
|
GeometryVisualizer.drawBlockSurfaceOutline(player, maxY, blocks, { _, _, _ -> false }, color, maxY)
|
||||||
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.
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
// 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("com.michael-bull.kotlin-result:kotlin-result:2.1.0")
|
||||||
compileOnly("net.hareworks:kommand-lib:1.1")
|
compileOnly("net.hareworks:kommand-lib:1.1")
|
||||||
compileOnly("net.hareworks:permits-lib")
|
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)
|
// Visualizer Lib (Bundled & Relocated)
|
||||||
implementation("net.hareworks.hcu:visualizer-lib:1.0")
|
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)
|
server.pluginManager.registerEvents(net.hareworks.hcu.landsector.listener.SelectionListener(this, selService, service), this)
|
||||||
|
|
||||||
// Schedule visualization task
|
// 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 {
|
} else {
|
||||||
logger.severe("SelectionService not initialized!")
|
logger.severe("SelectionService not initialized!")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -303,13 +303,21 @@ class SectorListener(
|
||||||
} else {
|
} else {
|
||||||
content.append(Component.text("Parts:", NamedTextColor.BLACK))
|
content.append(Component.text("Parts:", NamedTextColor.BLACK))
|
||||||
ranges.forEach { range ->
|
ranges.forEach { range ->
|
||||||
content.append(Component.text("\n- [${range.id}] ${range.type}", NamedTextColor.DARK_GRAY))
|
content.append(Component.text("\n"))
|
||||||
content.append(Component.text(" "))
|
|
||||||
content.append(
|
content.append(
|
||||||
Component.text("[x] ", NamedTextColor.RED)
|
Component.text("[x] ", NamedTextColor.RED)
|
||||||
.clickEvent(ClickEvent.runCommand("/landsector range delete $sectorId ${range.id}"))
|
.clickEvent(ClickEvent.runCommand("/landsector range delete $sectorId ${range.id}"))
|
||||||
.hoverEvent(net.kyori.adventure.text.event.HoverEvent.showText(Component.text("Click to delete range")))
|
.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 player = event.player
|
||||||
val selection = selectionService.getSelection(player.uniqueId)
|
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) {
|
if (event.action == Action.LEFT_CLICK_AIR || event.action == Action.LEFT_CLICK_BLOCK) {
|
||||||
event.isCancelled = true // Prevent breaking block
|
event.isCancelled = true
|
||||||
|
|
||||||
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
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Right Click: Set Points
|
// Right Click: Set Points
|
||||||
if (event.action == Action.RIGHT_CLICK_BLOCK) {
|
if (event.action == Action.RIGHT_CLICK_BLOCK || event.action == Action.RIGHT_CLICK_AIR) {
|
||||||
event.isCancelled = true // Prevent placing
|
event.isCancelled = true // Prevent placing or item usage
|
||||||
|
|
||||||
val clickedBlock = event.clickedBlock ?: return
|
val targetBlock = event.clickedBlock ?: player.rayTraceBlocks(100.0)?.hitBlock
|
||||||
val loc = clickedBlock.location
|
|
||||||
|
if (targetBlock == null) return
|
||||||
|
|
||||||
|
val loc = targetBlock.location
|
||||||
|
|
||||||
if (selection.point1 == null) {
|
if (selection.point1 == null) {
|
||||||
// Set Point 1
|
// Set Point 1
|
||||||
selection.point1 = loc
|
selection.point1 = loc
|
||||||
selection.p1Sneaking = player.isSneaking
|
// selection.isCenterMode is persistent, so we don't set it here based on sneaking
|
||||||
selection.point2 = null // Clear p2 just in case
|
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 {
|
} else {
|
||||||
// Set Point 2
|
// Set Point 2
|
||||||
// If P2 is already set, reset to P1 new? standard standard is cyclic.
|
|
||||||
if (selection.point2 != null) {
|
if (selection.point2 != null) {
|
||||||
// Resetting, treat as P1
|
// Resetting, treat as P1
|
||||||
selection.point1 = loc
|
selection.point1 = loc
|
||||||
selection.p1Sneaking = player.isSneaking
|
|
||||||
selection.point2 = null
|
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 {
|
} else {
|
||||||
selection.point2 = loc
|
selection.point2 = loc
|
||||||
// Check for sector ID
|
// Check for sector ID
|
||||||
|
|
@ -90,10 +85,11 @@ class SelectionListener(
|
||||||
selection.mode,
|
selection.mode,
|
||||||
p1.blockX, p1.blockY, p1.blockZ,
|
p1.blockX, p1.blockY, p1.blockZ,
|
||||||
p2.blockX, p2.blockY, p2.blockZ,
|
p2.blockX, p2.blockY, p2.blockZ,
|
||||||
selection.p1Sneaking
|
selection.isCenterMode
|
||||||
)
|
)
|
||||||
|
|
||||||
player.sendMessage(Component.text("Range added to Sector #$sId.", NamedTextColor.GREEN))
|
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
|
// Clear selection
|
||||||
selection.point1 = null
|
selection.point1 = null
|
||||||
|
|
@ -104,6 +100,8 @@ class SelectionListener(
|
||||||
// Show Cost / Area
|
// Show Cost / Area
|
||||||
player.sendMessage(Component.text(selectionService.getAreaDetails(player.uniqueId), NamedTextColor.AQUA))
|
player.sendMessage(Component.text(selectionService.getAreaDetails(player.uniqueId), NamedTextColor.AQUA))
|
||||||
player.sendMessage(Component.text("Cost: ${selectionService.getCost(player.uniqueId)}", NamedTextColor.GOLD))
|
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")
|
val key = NamespacedKey(plugin, "component")
|
||||||
if (item.itemMeta?.persistentDataContainer?.get(key, PersistentDataType.STRING) == "land_sector_tool") {
|
if (item.itemMeta?.persistentDataContainer?.get(key, PersistentDataType.STRING) == "land_sector_tool") {
|
||||||
event.isCancelled = true
|
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 mode: SelectionMode = SelectionMode.CUBOID,
|
||||||
var point1: Location? = null,
|
var point1: Location? = null,
|
||||||
var point2: 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.SectorDraftsTable
|
||||||
import net.hareworks.hcu.landsector.database.SectorsTable
|
import net.hareworks.hcu.landsector.database.SectorsTable
|
||||||
// SectorRangesTable removed
|
|
||||||
import net.hareworks.hcu.landsector.model.Sector
|
import net.hareworks.hcu.landsector.model.Sector
|
||||||
import net.hareworks.hcu.landsector.model.SectorRange
|
import net.hareworks.hcu.landsector.model.SectorRange
|
||||||
import net.hareworks.hcu.landsector.model.SelectionMode
|
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.core.eq
|
||||||
import org.jetbrains.exposed.v1.jdbc.Database
|
import org.jetbrains.exposed.v1.jdbc.Database
|
||||||
import org.jetbrains.exposed.v1.jdbc.SchemaUtils
|
import org.jetbrains.exposed.v1.jdbc.SchemaUtils
|
||||||
|
|
@ -385,7 +386,7 @@ class SectorService(private val database: Database, private val landService: net
|
||||||
sectorId,
|
sectorId,
|
||||||
SelectionMode.CYLINDER,
|
SelectionMode.CYLINDER,
|
||||||
shape.x, shape.y, shape.z,
|
shape.x, shape.y, shape.z,
|
||||||
shape.x, shape.y, shape.z,
|
shape.radius.toInt(), shape.bottomHeight, shape.topHeight,
|
||||||
false
|
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 {
|
fun deleteRange(sectorId: Int, rangeIndex: Int): Boolean {
|
||||||
val sector = getSector(sectorId) ?: return false
|
val sector = getSector(sectorId) ?: return false
|
||||||
if (sector.landId != null) return false // Cannot edit confirmed
|
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 height: Int
|
||||||
val length: Int
|
val length: Int
|
||||||
|
|
||||||
if (!data.p1Sneaking) {
|
if (!data.isCenterMode) {
|
||||||
// Normal: P1 to P2
|
// Normal: P1 to P2
|
||||||
width = abs(p1.blockX - p2.blockX) + 1
|
width = abs(p1.blockX - p2.blockX) + 1
|
||||||
height = abs(p1.blockY - p2.blockY) + 1
|
height = abs(p1.blockY - p2.blockY) + 1
|
||||||
length = abs(p1.blockZ - p2.blockZ) + 1
|
length = abs(p1.blockZ - p2.blockZ) + 1
|
||||||
} else {
|
} else {
|
||||||
// Sneak: P1 is center
|
// Center Mode: P1 is center
|
||||||
val dx = abs(p1.blockX - p2.blockX)
|
val dx = abs(p1.blockX - p2.blockX)
|
||||||
val dy = abs(p1.blockY - p2.blockY)
|
val dy = abs(p1.blockY - p2.blockY)
|
||||||
val dz = abs(p1.blockZ - p2.blockZ)
|
val dz = abs(p1.blockZ - p2.blockZ)
|
||||||
|
|
@ -58,12 +58,12 @@ class SelectionService {
|
||||||
val baseRadius = ceil(dist - 0.5)
|
val baseRadius = ceil(dist - 0.5)
|
||||||
val radius = baseRadius + 0.5
|
val radius = baseRadius + 0.5
|
||||||
|
|
||||||
val totalHeight = if (!data.p1Sneaking) {
|
val totalHeight = if (!data.isCenterMode) {
|
||||||
// Normal: P1 center, symmetric height
|
// Normal: P1 center, symmetric height
|
||||||
val h = abs(p1.blockY - p2.blockY)
|
val h = abs(p1.blockY - p2.blockY)
|
||||||
h * 2 + 1
|
h * 2 + 1
|
||||||
} else {
|
} else {
|
||||||
// Sneak: P1 base, P2 top
|
// Center Mode: P1 base, P2 top (or vice versa, height is diff)
|
||||||
abs(p1.blockY - p2.blockY) + 1
|
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.model.SelectionMode
|
||||||
import net.hareworks.hcu.landsector.service.SelectionService
|
import net.hareworks.hcu.landsector.service.SelectionService
|
||||||
|
import net.hareworks.hcu.landsector.service.SectorService
|
||||||
import net.hareworks.hcu.visualizer.GeometryVisualizer
|
import net.hareworks.hcu.visualizer.GeometryVisualizer
|
||||||
import net.kyori.adventure.text.Component
|
import net.kyori.adventure.text.Component
|
||||||
import net.kyori.adventure.text.format.NamedTextColor
|
import net.kyori.adventure.text.format.NamedTextColor
|
||||||
|
|
@ -18,7 +19,8 @@ import kotlin.math.abs
|
||||||
|
|
||||||
class SelectionVisualizerTask(
|
class SelectionVisualizerTask(
|
||||||
private val plugin: JavaPlugin,
|
private val plugin: JavaPlugin,
|
||||||
private val selectionService: SelectionService
|
private val selectionService: SelectionService,
|
||||||
|
private val sectorService: SectorService
|
||||||
) : BukkitRunnable() {
|
) : BukkitRunnable() {
|
||||||
|
|
||||||
override fun run() {
|
override fun run() {
|
||||||
|
|
@ -28,41 +30,107 @@ class SelectionVisualizerTask(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun visualize(player: Player) {
|
private fun visualize(player: Player) {
|
||||||
// Check if holding tool
|
|
||||||
// Check if tool is in inventory
|
|
||||||
val key = NamespacedKey(plugin, "component")
|
val key = NamespacedKey(plugin, "component")
|
||||||
val hasTool = player.inventory.contents.any { item ->
|
val sectorKey = NamespacedKey(plugin, "sector_id")
|
||||||
item != null && item.type == Material.FLINT &&
|
|
||||||
item.itemMeta?.persistentDataContainer?.get(key, PersistentDataType.STRING) == "land_sector_tool"
|
// 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
|
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 selection = selectionService.getSelection(player.uniqueId)
|
||||||
val p1 = selection.point1 ?: return
|
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 p2 = selection.point2
|
||||||
var isDynamic = false
|
|
||||||
|
|
||||||
if (p2 == null) {
|
if (p2 == null) {
|
||||||
val target = player.getTargetBlockExact(30)
|
val target = player.getTargetBlockExact(30)
|
||||||
if (target != null) {
|
if (target != null) {
|
||||||
p2 = target.location
|
p2 = target.location
|
||||||
isDynamic = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p2 == null) return // No p2 and no target
|
if (p2 == null) return
|
||||||
|
|
||||||
// If different world, ignore
|
|
||||||
if (p1.world != p2.world) return
|
if (p1.world != p2.world) return
|
||||||
|
|
||||||
|
// ... (Existing Draw Logic for Selection) ...
|
||||||
|
// ... (Existing Draw Logic for Selection) ...
|
||||||
if (selection.mode == SelectionMode.CUBOID) {
|
if (selection.mode == SelectionMode.CUBOID) {
|
||||||
// Cuboid visualization
|
|
||||||
var minX: Double
|
var minX: Double
|
||||||
var minY: Double
|
var minY: Double
|
||||||
var minZ: Double
|
var minZ: Double
|
||||||
|
|
@ -70,8 +138,8 @@ class SelectionVisualizerTask(
|
||||||
var maxY: Double
|
var maxY: Double
|
||||||
var maxZ: Double
|
var maxZ: Double
|
||||||
|
|
||||||
if (!selection.p1Sneaking) {
|
if (!selection.isCenterMode) {
|
||||||
// Normal: corner to corner
|
// Normal: Corner to Corner (Diagonal)
|
||||||
minX = minOf(p1.blockX, p2.blockX).toDouble()
|
minX = minOf(p1.blockX, p2.blockX).toDouble()
|
||||||
minY = minOf(p1.blockY, p2.blockY).toDouble()
|
minY = minOf(p1.blockY, p2.blockY).toDouble()
|
||||||
minZ = minOf(p1.blockZ, p2.blockZ).toDouble()
|
minZ = minOf(p1.blockZ, p2.blockZ).toDouble()
|
||||||
|
|
@ -79,7 +147,7 @@ class SelectionVisualizerTask(
|
||||||
maxY = maxOf(p1.blockY, p2.blockY).toDouble() + 1.0
|
maxY = maxOf(p1.blockY, p2.blockY).toDouble() + 1.0
|
||||||
maxZ = maxOf(p1.blockZ, p2.blockZ).toDouble() + 1.0
|
maxZ = maxOf(p1.blockZ, p2.blockZ).toDouble() + 1.0
|
||||||
} else {
|
} else {
|
||||||
// Sneak: P1 center, P2 defines radius
|
// Center Mode: P1 is Center
|
||||||
val dx = abs(p1.blockX - p2.blockX)
|
val dx = abs(p1.blockX - p2.blockX)
|
||||||
val dy = abs(p1.blockY - p2.blockY)
|
val dy = abs(p1.blockY - p2.blockY)
|
||||||
val dz = abs(p1.blockZ - p2.blockZ)
|
val dz = abs(p1.blockZ - p2.blockZ)
|
||||||
|
|
@ -95,9 +163,8 @@ class SelectionVisualizerTask(
|
||||||
GeometryVisualizer.drawCuboid(player, minX, minY, minZ, maxX, maxY, maxZ)
|
GeometryVisualizer.drawCuboid(player, minX, minY, minZ, maxX, maxY, maxZ)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Cylinder visualization
|
|
||||||
val centerX: Double
|
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 centerZ: Double
|
||||||
val radius: Double
|
val radius: Double
|
||||||
val minY: Double
|
val minY: Double
|
||||||
|
|
@ -108,88 +175,55 @@ class SelectionVisualizerTask(
|
||||||
val dist = Math.sqrt((dx * dx + dz * dz).toDouble())
|
val dist = Math.sqrt((dx * dx + dz * dz).toDouble())
|
||||||
radius = kotlin.math.ceil(dist - 0.5)
|
radius = kotlin.math.ceil(dist - 0.5)
|
||||||
|
|
||||||
if (!selection.p1Sneaking) {
|
if (!selection.isCenterMode) {
|
||||||
// Normal: P1 center, symmetric height based on P2.y diff
|
// Normal: P1 is Center, Symmetric Height
|
||||||
centerX = p1.blockX + 0.5
|
centerX = p1.blockX + 0.5
|
||||||
centerY = p1.blockY + 0.5 // Logic center
|
centerY = p1.blockY + 0.5
|
||||||
centerZ = p1.blockZ + 0.5
|
centerZ = p1.blockZ + 0.5
|
||||||
|
|
||||||
val hDiff = abs(p1.blockY - p2.blockY)
|
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 baseBlockY = p1.blockY - hDiff
|
||||||
val topBlockY = p1.blockY + hDiff
|
val topBlockY = p1.blockY + hDiff
|
||||||
|
|
||||||
minY = baseBlockY.toDouble()
|
minY = baseBlockY.toDouble()
|
||||||
maxY = topBlockY.toDouble() + 1.0
|
maxY = topBlockY.toDouble() + 1.0
|
||||||
} else {
|
} else {
|
||||||
// Sneak: P1 base center. P2 top center.
|
// Center/Base Mode: P1 is Base, P2 defines Height/Radius
|
||||||
centerX = p1.blockX + 0.5
|
centerX = p1.blockX + 0.5
|
||||||
centerZ = p1.blockZ + 0.5
|
centerZ = p1.blockZ + 0.5
|
||||||
// Only height from p2
|
|
||||||
val y1 = p1.blockY
|
val y1 = p1.blockY
|
||||||
val y2 = p2.blockY
|
val y2 = p2.blockY
|
||||||
|
|
||||||
val baseBlockY = minOf(y1, y2)
|
val baseBlockY = minOf(y1, y2)
|
||||||
val topBlockY = maxOf(y1, y2)
|
val topBlockY = maxOf(y1, y2)
|
||||||
|
|
||||||
minY = baseBlockY.toDouble()
|
minY = baseBlockY.toDouble()
|
||||||
maxY = topBlockY.toDouble() + 1.0
|
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 cX = kotlin.math.floor(centerX).toInt()
|
||||||
val cZ = kotlin.math.floor(centerZ).toInt()
|
val cZ = kotlin.math.floor(centerZ).toInt()
|
||||||
val blocks = mutableSetOf<Pair<Int, Int>>()
|
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 actualRadius = radius + 0.5
|
||||||
val radiusSq = actualRadius * actualRadius
|
val radiusSq = actualRadius * actualRadius
|
||||||
val rInt = actualRadius.toInt() + 1
|
val rInt = actualRadius.toInt() + 1
|
||||||
|
|
||||||
for (dx in -rInt..rInt) {
|
for (dx in -rInt..rInt) {
|
||||||
for (dz in -rInt..rInt) {
|
for (dz in -rInt..rInt) {
|
||||||
// Check if block center is within the radius
|
|
||||||
if (dx * dx + dz * dz <= radiusSq) {
|
if (dx * dx + dz * dz <= radiusSq) {
|
||||||
blocks.add(Pair(cX + dx, cZ + dz))
|
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)
|
val color = Color.fromRGB(100, 200, 255)
|
||||||
// Draw bottom surface outline
|
GeometryVisualizer.drawBlockSurfaceOutline(player, minY, blocks, { _, _, _ -> false }, color, minY)
|
||||||
GeometryVisualizer.drawBlockSurfaceOutline(
|
GeometryVisualizer.drawBlockSurfaceOutline(player, maxY, blocks, { _, _, _ -> false }, color, maxY)
|
||||||
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.
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
// 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