feat: 土地条件追加・コマンド変更
This commit is contained in:
parent
3c6b644607
commit
167daab8cd
|
|
@ -21,6 +21,8 @@ class LandSectorPlugin : JavaPlugin() {
|
|||
override fun onEnable() {
|
||||
instance = this
|
||||
|
||||
saveDefaultConfig()
|
||||
|
||||
selectionService = SelectionService()
|
||||
|
||||
// Register commands
|
||||
|
|
@ -53,7 +55,7 @@ class LandSectorPlugin : JavaPlugin() {
|
|||
return
|
||||
}
|
||||
|
||||
val service = SectorService(db, landService)
|
||||
val service = SectorService(this, db, landService)
|
||||
try {
|
||||
service.init()
|
||||
} catch (e: Exception) {
|
||||
|
|
|
|||
|
|
@ -18,51 +18,17 @@ class LandSectorCommand(private val landSectorPlugin: LandSectorPlugin) {
|
|||
fun register() {
|
||||
kommand(landSectorPlugin) {
|
||||
command("landsector") {
|
||||
literal("givetool") {
|
||||
// Admin Commands
|
||||
literal("reload") {
|
||||
condition { it.isOp }
|
||||
executes {
|
||||
val player = sender as? Player ?: return@executes
|
||||
giveTool(player, null)
|
||||
}
|
||||
integer("sectorId") {
|
||||
executes {
|
||||
val player = sender as? Player ?: return@executes
|
||||
val id = argument<Int>("sectorId")
|
||||
giveTool(player, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
literal("activate") {
|
||||
integer("sectorId") {
|
||||
executes {
|
||||
val id = argument<Int>("sectorId")
|
||||
val service = landSectorPlugin.sectorService ?: return@executes
|
||||
|
||||
if (service.activateSector(id)) {
|
||||
sender.sendMessage(Component.text("Sector #$id activated and land secured!", NamedTextColor.GREEN))
|
||||
} else {
|
||||
sender.sendMessage(Component.text("Failed to activate sector #$id (Already active or empty parts?).", NamedTextColor.RED))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
literal("cancel") {
|
||||
integer("sectorId") {
|
||||
executes {
|
||||
val id = argument<Int>("sectorId")
|
||||
sender.sendMessage(Component.text("Cancellation for Sector #$id pending implementation.", NamedTextColor.YELLOW))
|
||||
// TODO: Cancel logic
|
||||
val player = sender as? Player
|
||||
if (player != null) {
|
||||
landSectorPlugin.selectionService?.clearSelection(player.uniqueId)
|
||||
sender.sendMessage(Component.text("Selection cleared.", NamedTextColor.RED))
|
||||
}
|
||||
}
|
||||
landSectorPlugin.reloadConfig()
|
||||
sender.sendMessage(Component.text("Configuration reloaded.", NamedTextColor.GREEN))
|
||||
}
|
||||
}
|
||||
|
||||
literal("give") {
|
||||
condition { it.isOp }
|
||||
executes {
|
||||
val player = sender as? Player ?: return@executes
|
||||
|
||||
|
|
@ -82,6 +48,7 @@ class LandSectorCommand(private val landSectorPlugin: LandSectorPlugin) {
|
|||
}
|
||||
|
||||
literal("list") {
|
||||
condition { it.isOp }
|
||||
executes {
|
||||
val player = sender as? Player ?: return@executes
|
||||
val service = landSectorPlugin.sectorService
|
||||
|
|
@ -123,46 +90,8 @@ class LandSectorCommand(private val landSectorPlugin: LandSectorPlugin) {
|
|||
}
|
||||
}
|
||||
|
||||
literal("transfer") {
|
||||
integer("sectorId") {
|
||||
integer("targetActorId") {
|
||||
executes {
|
||||
val sectorId = argument<Int>("sectorId")
|
||||
val targetActorId = argument<Int>("targetActorId")
|
||||
val player = sender as? Player ?: return@executes
|
||||
val service = landSectorPlugin.sectorService ?: return@executes
|
||||
|
||||
if (service.transferSector(sectorId, targetActorId)) {
|
||||
sender.sendMessage(Component.text("Transfer successful!", NamedTextColor.GREEN))
|
||||
} else {
|
||||
sender.sendMessage(Component.text("Transfer failed.", NamedTextColor.RED))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
literal("range") {
|
||||
literal("delete") {
|
||||
integer("sectorId") {
|
||||
integer("rangeIndex") {
|
||||
executes {
|
||||
val sectorId = argument<Int>("sectorId")
|
||||
val rangeIndex = argument<Int>("rangeIndex")
|
||||
val service = landSectorPlugin.sectorService ?: return@executes
|
||||
|
||||
if (service.deleteRange(sectorId, rangeIndex)) {
|
||||
sender.sendMessage(Component.text("Range #$rangeIndex in Sector #$sectorId deleted.", NamedTextColor.GREEN))
|
||||
} else {
|
||||
sender.sendMessage(Component.text("Failed to delete range. (Invalid index or sector already confirmed?)", NamedTextColor.RED))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
literal("delete") {
|
||||
condition { it.isOp }
|
||||
integer("id") {
|
||||
executes {
|
||||
val id = argument<Int>("id")
|
||||
|
|
@ -209,6 +138,92 @@ class LandSectorCommand(private val landSectorPlugin: LandSectorPlugin) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// User Operations
|
||||
literal("operation") {
|
||||
literal("givetool") {
|
||||
executes {
|
||||
val player = sender as? Player ?: return@executes
|
||||
giveTool(player, null)
|
||||
}
|
||||
integer("sectorId") {
|
||||
executes {
|
||||
val player = sender as? Player ?: return@executes
|
||||
val id = argument<Int>("sectorId")
|
||||
giveTool(player, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
literal("activate") {
|
||||
integer("sectorId") {
|
||||
executes {
|
||||
val id = argument<Int>("sectorId")
|
||||
val service = landSectorPlugin.sectorService ?: return@executes
|
||||
|
||||
if (service.activateSector(id)) {
|
||||
sender.sendMessage(Component.text("Sector #$id activated and land secured!", NamedTextColor.GREEN))
|
||||
} else {
|
||||
sender.sendMessage(Component.text("Failed to activate sector #$id (Already active or empty parts?).", NamedTextColor.RED))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
literal("cancel") {
|
||||
integer("sectorId") {
|
||||
executes {
|
||||
val id = argument<Int>("sectorId")
|
||||
sender.sendMessage(Component.text("Cancellation for Sector #$id pending implementation.", NamedTextColor.YELLOW))
|
||||
// TODO: Cancel logic
|
||||
val player = sender as? Player
|
||||
if (player != null) {
|
||||
landSectorPlugin.selectionService?.clearSelection(player.uniqueId)
|
||||
sender.sendMessage(Component.text("Selection cleared.", NamedTextColor.RED))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
literal("transfer") {
|
||||
integer("sectorId") {
|
||||
integer("targetActorId") {
|
||||
executes {
|
||||
val sectorId = argument<Int>("sectorId")
|
||||
val targetActorId = argument<Int>("targetActorId")
|
||||
val player = sender as? Player ?: return@executes
|
||||
val service = landSectorPlugin.sectorService ?: return@executes
|
||||
|
||||
if (service.transferSector(sectorId, targetActorId)) {
|
||||
sender.sendMessage(Component.text("Transfer successful!", NamedTextColor.GREEN))
|
||||
} else {
|
||||
sender.sendMessage(Component.text("Transfer failed.", NamedTextColor.RED))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
literal("range") {
|
||||
literal("delete") {
|
||||
integer("sectorId") {
|
||||
integer("rangeIndex") {
|
||||
executes {
|
||||
val sectorId = argument<Int>("sectorId")
|
||||
val rangeIndex = argument<Int>("rangeIndex")
|
||||
val service = landSectorPlugin.sectorService ?: return@executes
|
||||
|
||||
if (service.deleteRange(sectorId, rangeIndex)) {
|
||||
sender.sendMessage(Component.text("Range #$rangeIndex in Sector #$sectorId deleted.", NamedTextColor.GREEN))
|
||||
} else {
|
||||
sender.sendMessage(Component.text("Failed to delete range. (Invalid index or sector already confirmed?)", NamedTextColor.RED))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -270,29 +270,40 @@ class SectorListener(
|
|||
if (myRole == FactionRole.OWNER || myRole == FactionRole.EXEC) {
|
||||
content.append(
|
||||
Component.text("[Transfer to Faction]\n\n", NamedTextColor.GOLD)
|
||||
.clickEvent(ClickEvent.runCommand("/landsector transfer $sectorId $myFaction"))
|
||||
.clickEvent(ClickEvent.runCommand("/landsector operation transfer $sectorId $myFaction"))
|
||||
.hoverEvent(net.kyori.adventure.text.event.HoverEvent.showText(Component.text("Click to transfer ownership to your faction")))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Actions Row 1
|
||||
// Check activation
|
||||
val activationResult = sectorService.checkActivationConditions(sectorId)
|
||||
|
||||
if (activationResult.canActivate) {
|
||||
content.append(
|
||||
Component.text("[Activate]", NamedTextColor.DARK_GREEN)
|
||||
.clickEvent(ClickEvent.runCommand("/landsector activate $sectorId"))
|
||||
.clickEvent(ClickEvent.runCommand("/landsector operation activate $sectorId"))
|
||||
.hoverEvent(net.kyori.adventure.text.event.HoverEvent.showText(Component.text("Click to activate sector")))
|
||||
)
|
||||
} else {
|
||||
val reasons = activationResult.reasons.joinToString("\n") { "- $it" }
|
||||
content.append(
|
||||
Component.text("[Activate]", NamedTextColor.GRAY)
|
||||
.hoverEvent(net.kyori.adventure.text.event.HoverEvent.showText(Component.text("Cannot Activate:\n$reasons", NamedTextColor.RED)))
|
||||
)
|
||||
}
|
||||
content.append(Component.text(" "))
|
||||
content.append(
|
||||
Component.text("[Destroy]\n", NamedTextColor.RED)
|
||||
.clickEvent(ClickEvent.runCommand("/landsector cancel $sectorId"))
|
||||
.clickEvent(ClickEvent.runCommand("/landsector operation cancel $sectorId"))
|
||||
.hoverEvent(net.kyori.adventure.text.event.HoverEvent.showText(Component.text("Click to destroy sector")))
|
||||
)
|
||||
|
||||
// Actions Row 2
|
||||
content.append(
|
||||
Component.text("[Get Tool]\n\n", NamedTextColor.DARK_AQUA)
|
||||
.clickEvent(ClickEvent.runCommand("/landsector givetool $sectorId"))
|
||||
.clickEvent(ClickEvent.runCommand("/landsector operation givetool $sectorId"))
|
||||
.hoverEvent(net.kyori.adventure.text.event.HoverEvent.showText(Component.text("Click to get selection tool")))
|
||||
)
|
||||
|
||||
|
|
@ -306,7 +317,7 @@ class SectorListener(
|
|||
content.append(Component.text("\n"))
|
||||
content.append(
|
||||
Component.text("[x] ", NamedTextColor.RED)
|
||||
.clickEvent(ClickEvent.runCommand("/landsector range delete $sectorId ${range.id}"))
|
||||
.clickEvent(ClickEvent.runCommand("/landsector operation range delete $sectorId ${range.id}"))
|
||||
.hoverEvent(net.kyori.adventure.text.event.HoverEvent.showText(Component.text("Click to delete range")))
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package net.hareworks.hcu.landsector.service
|
||||
|
||||
import net.hareworks.hcu.landsector.LandSectorPlugin
|
||||
import net.hareworks.hcu.landsector.database.SectorDraftsTable
|
||||
import net.hareworks.hcu.landsector.database.SectorsTable
|
||||
import net.hareworks.hcu.landsector.model.Sector
|
||||
|
|
@ -18,9 +19,22 @@ import org.jetbrains.exposed.v1.jdbc.andWhere
|
|||
import org.jetbrains.exposed.v1.jdbc.deleteWhere
|
||||
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.sqrt
|
||||
|
||||
// ... class definition ...
|
||||
class SectorService(private val database: Database, private val landService: net.hareworks.hcu.lands.service.LandService) {
|
||||
class SectorService(
|
||||
private val plugin: LandSectorPlugin,
|
||||
private val database: Database,
|
||||
private val landService: net.hareworks.hcu.lands.service.LandService
|
||||
) {
|
||||
|
||||
data class ActivationResult(val canActivate: Boolean, val reasons: List<String>)
|
||||
|
||||
private val hpCache = ConcurrentHashMap<Int, Int>()
|
||||
private val dirtySet = ConcurrentHashMap.newKeySet<Int>()
|
||||
|
|
@ -68,6 +82,143 @@ class SectorService(private val database: Database, private val landService: net
|
|||
}
|
||||
}
|
||||
|
||||
fun checkActivationConditions(sectorId: Int): ActivationResult {
|
||||
val sector = getSector(sectorId) ?: return ActivationResult(false, listOf("Sector not found"))
|
||||
val draft = draftLands[sectorId] ?: return ActivationResult(false, listOf("No activation draft found"))
|
||||
if (draft.data.parts.isEmpty()) return ActivationResult(false, listOf("No land selected"))
|
||||
|
||||
val reasons = mutableListOf<String>()
|
||||
val config = plugin.config
|
||||
|
||||
// 1. Volume Check
|
||||
val minVolume = config.getInt("activation.min-volume", 1000)
|
||||
val currentVolume = draft.data.parts.sumOf { getVolume(it) }
|
||||
if (currentVolume < minVolume) {
|
||||
reasons.add("Volume too small: $currentVolume < $minVolume")
|
||||
}
|
||||
|
||||
// 2. Range Check (Core Protection)
|
||||
val coreRadius = config.getDouble("activation.core-protection.radius", 5.0)
|
||||
val coreHeight = config.getInt("activation.core-protection.height", 10)
|
||||
|
||||
// Define required Cylinder around sector core
|
||||
// Center: sector.x, sector.y, sector.z.
|
||||
val yMin = sector.y - (coreHeight / 2)
|
||||
val yMax = sector.y + (coreHeight + 1) / 2 - 1
|
||||
|
||||
if (!isCylinderCovered(sector.x, sector.y, sector.z, coreRadius, yMin, yMax, draft.data.parts)) {
|
||||
reasons.add("Land must cover core area (R:$coreRadius, H:$coreHeight around core)")
|
||||
}
|
||||
|
||||
// 3. Distance Check
|
||||
val minDist = config.getInt("activation.distance-from-others", 20)
|
||||
val activeSectors = sectorsCache.values.filter {
|
||||
it.world == sector.world && it.landId != null && it.id != sector.id
|
||||
}
|
||||
|
||||
for (otherSector in activeSectors) {
|
||||
val otherLand = landService.getLand(otherSector.landId!!) ?: continue
|
||||
if (isTooClose(draft.data.parts, otherLand.data.parts, minDist)) {
|
||||
reasons.add("Too close to Sector ${otherSector.id} (Min $minDist blocks)")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return ActivationResult(reasons.isEmpty(), reasons)
|
||||
}
|
||||
|
||||
private fun getVolume(shape: Shape): Long {
|
||||
return when (shape) {
|
||||
is Shape.Cuboid -> {
|
||||
val dx = abs(shape.x2 - shape.x1) + 1L
|
||||
val dy = abs(shape.y2 - shape.y1) + 1L
|
||||
val dz = abs(shape.z2 - shape.z1) + 1L
|
||||
dx * dy * dz
|
||||
}
|
||||
is Shape.Cylinder -> {
|
||||
val r = shape.radius
|
||||
val h = (shape.bottomHeight + shape.topHeight + 1).toLong()
|
||||
(Math.PI * r * r).toLong() * h // Approximate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isCylinderCovered(cx: Int, cy: Int, cz: Int, r: Double, yMin: Int, yMax: Int, shapes: List<Shape>): Boolean {
|
||||
val rInt = ceil(r).toInt()
|
||||
val rSq = r * r
|
||||
|
||||
for (y in yMin..yMax) {
|
||||
for (x in -rInt..rInt) {
|
||||
for (z in -rInt..rInt) {
|
||||
if ((x*x + z*z).toDouble() <= rSq) {
|
||||
val gx = cx + x
|
||||
val gz = cz + z
|
||||
if (shapes.none { contains(it, gx, y, gz) }) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun contains(shape: Shape, x: Int, y: Int, z: Int): Boolean {
|
||||
return when (shape) {
|
||||
is Shape.Cuboid -> {
|
||||
x >= min(shape.x1, shape.x2) && x <= max(shape.x1, shape.x2) &&
|
||||
y >= min(shape.y1, shape.y2) && y <= max(shape.y1, shape.y2) &&
|
||||
z >= min(shape.z1, shape.z2) && z <= max(shape.z1, shape.z2)
|
||||
}
|
||||
is Shape.Cylinder -> {
|
||||
if (y < shape.y - shape.bottomHeight || y > shape.y + shape.topHeight) return false
|
||||
val dx = x - shape.x
|
||||
val dz = z - shape.z
|
||||
(dx*dx + dz*dz) <= (shape.radius * shape.radius)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isTooClose(shapes1: List<Shape>, shapes2: List<Shape>, minDist: Int): Boolean {
|
||||
for (s1 in shapes1) {
|
||||
for (s2 in shapes2) {
|
||||
if (getDistance(s1, s2) < minDist) return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun getDistance(s1: Shape, s2: Shape): Double {
|
||||
val bb1 = getBounds(s1)
|
||||
val bb2 = getBounds(s2)
|
||||
|
||||
val dx = max(0, max(bb1.minX - bb2.maxX, bb2.minX - bb1.maxX))
|
||||
val dy = max(0, max(bb1.minY - bb2.maxY, bb2.minY - bb1.maxY))
|
||||
val dz = max(0, max(bb1.minZ - bb2.maxZ, bb2.minZ - bb1.maxZ))
|
||||
|
||||
return sqrt((dx*dx + dy*dy + dz*dz).toDouble())
|
||||
}
|
||||
|
||||
data class Bounds(val minX: Int, val maxX: Int, val minY: Int, val maxY: Int, val minZ: Int, val maxZ: Int)
|
||||
|
||||
private fun getBounds(shape: Shape): Bounds {
|
||||
return when (shape) {
|
||||
is Shape.Cuboid -> Bounds(
|
||||
min(shape.x1, shape.x2), max(shape.x1, shape.x2),
|
||||
min(shape.y1, shape.y2), max(shape.y1, shape.y2),
|
||||
min(shape.z1, shape.z2), max(shape.z1, shape.z2)
|
||||
)
|
||||
is Shape.Cylinder -> {
|
||||
val r = ceil(shape.radius).toInt()
|
||||
Bounds(
|
||||
shape.x - r, shape.x + r,
|
||||
shape.y - shape.bottomHeight, shape.y + shape.topHeight,
|
||||
shape.z - r, shape.z + r
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun createSector(ownerActorId: Int, world: String, x: Int, y: Int, z: Int): Sector? {
|
||||
return transaction(database) {
|
||||
val id = SectorsTable.insert {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ class LandSectorPlugin : JavaPlugin() {
|
|||
override fun onEnable() {
|
||||
instance = this
|
||||
|
||||
saveDefaultConfig()
|
||||
|
||||
selectionService = SelectionService()
|
||||
|
||||
// Register commands
|
||||
|
|
@ -53,7 +55,7 @@ class LandSectorPlugin : JavaPlugin() {
|
|||
return
|
||||
}
|
||||
|
||||
val service = SectorService(db, landService)
|
||||
val service = SectorService(this, db, landService)
|
||||
try {
|
||||
service.init()
|
||||
} catch (e: Exception) {
|
||||
|
|
|
|||
|
|
@ -18,51 +18,17 @@ class LandSectorCommand(private val landSectorPlugin: LandSectorPlugin) {
|
|||
fun register() {
|
||||
kommand(landSectorPlugin) {
|
||||
command("landsector") {
|
||||
literal("givetool") {
|
||||
// Admin Commands
|
||||
literal("reload") {
|
||||
condition { it.isOp }
|
||||
executes {
|
||||
val player = sender as? Player ?: return@executes
|
||||
giveTool(player, null)
|
||||
}
|
||||
integer("sectorId") {
|
||||
executes {
|
||||
val player = sender as? Player ?: return@executes
|
||||
val id = argument<Int>("sectorId")
|
||||
giveTool(player, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
literal("activate") {
|
||||
integer("sectorId") {
|
||||
executes {
|
||||
val id = argument<Int>("sectorId")
|
||||
val service = landSectorPlugin.sectorService ?: return@executes
|
||||
|
||||
if (service.activateSector(id)) {
|
||||
sender.sendMessage(Component.text("Sector #$id activated and land secured!", NamedTextColor.GREEN))
|
||||
} else {
|
||||
sender.sendMessage(Component.text("Failed to activate sector #$id (Already active or empty parts?).", NamedTextColor.RED))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
literal("cancel") {
|
||||
integer("sectorId") {
|
||||
executes {
|
||||
val id = argument<Int>("sectorId")
|
||||
sender.sendMessage(Component.text("Cancellation for Sector #$id pending implementation.", NamedTextColor.YELLOW))
|
||||
// TODO: Cancel logic
|
||||
val player = sender as? Player
|
||||
if (player != null) {
|
||||
landSectorPlugin.selectionService?.clearSelection(player.uniqueId)
|
||||
sender.sendMessage(Component.text("Selection cleared.", NamedTextColor.RED))
|
||||
}
|
||||
}
|
||||
landSectorPlugin.reloadConfig()
|
||||
sender.sendMessage(Component.text("Configuration reloaded.", NamedTextColor.GREEN))
|
||||
}
|
||||
}
|
||||
|
||||
literal("give") {
|
||||
condition { it.isOp }
|
||||
executes {
|
||||
val player = sender as? Player ?: return@executes
|
||||
|
||||
|
|
@ -82,6 +48,7 @@ class LandSectorCommand(private val landSectorPlugin: LandSectorPlugin) {
|
|||
}
|
||||
|
||||
literal("list") {
|
||||
condition { it.isOp }
|
||||
executes {
|
||||
val player = sender as? Player ?: return@executes
|
||||
val service = landSectorPlugin.sectorService
|
||||
|
|
@ -123,46 +90,8 @@ class LandSectorCommand(private val landSectorPlugin: LandSectorPlugin) {
|
|||
}
|
||||
}
|
||||
|
||||
literal("transfer") {
|
||||
integer("sectorId") {
|
||||
integer("targetActorId") {
|
||||
executes {
|
||||
val sectorId = argument<Int>("sectorId")
|
||||
val targetActorId = argument<Int>("targetActorId")
|
||||
val player = sender as? Player ?: return@executes
|
||||
val service = landSectorPlugin.sectorService ?: return@executes
|
||||
|
||||
if (service.transferSector(sectorId, targetActorId)) {
|
||||
sender.sendMessage(Component.text("Transfer successful!", NamedTextColor.GREEN))
|
||||
} else {
|
||||
sender.sendMessage(Component.text("Transfer failed.", NamedTextColor.RED))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
literal("range") {
|
||||
literal("delete") {
|
||||
integer("sectorId") {
|
||||
integer("rangeIndex") {
|
||||
executes {
|
||||
val sectorId = argument<Int>("sectorId")
|
||||
val rangeIndex = argument<Int>("rangeIndex")
|
||||
val service = landSectorPlugin.sectorService ?: return@executes
|
||||
|
||||
if (service.deleteRange(sectorId, rangeIndex)) {
|
||||
sender.sendMessage(Component.text("Range #$rangeIndex in Sector #$sectorId deleted.", NamedTextColor.GREEN))
|
||||
} else {
|
||||
sender.sendMessage(Component.text("Failed to delete range. (Invalid index or sector already confirmed?)", NamedTextColor.RED))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
literal("delete") {
|
||||
condition { it.isOp }
|
||||
integer("id") {
|
||||
executes {
|
||||
val id = argument<Int>("id")
|
||||
|
|
@ -209,6 +138,92 @@ class LandSectorCommand(private val landSectorPlugin: LandSectorPlugin) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// User Operations
|
||||
literal("operation") {
|
||||
literal("givetool") {
|
||||
executes {
|
||||
val player = sender as? Player ?: return@executes
|
||||
giveTool(player, null)
|
||||
}
|
||||
integer("sectorId") {
|
||||
executes {
|
||||
val player = sender as? Player ?: return@executes
|
||||
val id = argument<Int>("sectorId")
|
||||
giveTool(player, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
literal("activate") {
|
||||
integer("sectorId") {
|
||||
executes {
|
||||
val id = argument<Int>("sectorId")
|
||||
val service = landSectorPlugin.sectorService ?: return@executes
|
||||
|
||||
if (service.activateSector(id)) {
|
||||
sender.sendMessage(Component.text("Sector #$id activated and land secured!", NamedTextColor.GREEN))
|
||||
} else {
|
||||
sender.sendMessage(Component.text("Failed to activate sector #$id (Already active or empty parts?).", NamedTextColor.RED))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
literal("cancel") {
|
||||
integer("sectorId") {
|
||||
executes {
|
||||
val id = argument<Int>("sectorId")
|
||||
sender.sendMessage(Component.text("Cancellation for Sector #$id pending implementation.", NamedTextColor.YELLOW))
|
||||
// TODO: Cancel logic
|
||||
val player = sender as? Player
|
||||
if (player != null) {
|
||||
landSectorPlugin.selectionService?.clearSelection(player.uniqueId)
|
||||
sender.sendMessage(Component.text("Selection cleared.", NamedTextColor.RED))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
literal("transfer") {
|
||||
integer("sectorId") {
|
||||
integer("targetActorId") {
|
||||
executes {
|
||||
val sectorId = argument<Int>("sectorId")
|
||||
val targetActorId = argument<Int>("targetActorId")
|
||||
val player = sender as? Player ?: return@executes
|
||||
val service = landSectorPlugin.sectorService ?: return@executes
|
||||
|
||||
if (service.transferSector(sectorId, targetActorId)) {
|
||||
sender.sendMessage(Component.text("Transfer successful!", NamedTextColor.GREEN))
|
||||
} else {
|
||||
sender.sendMessage(Component.text("Transfer failed.", NamedTextColor.RED))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
literal("range") {
|
||||
literal("delete") {
|
||||
integer("sectorId") {
|
||||
integer("rangeIndex") {
|
||||
executes {
|
||||
val sectorId = argument<Int>("sectorId")
|
||||
val rangeIndex = argument<Int>("rangeIndex")
|
||||
val service = landSectorPlugin.sectorService ?: return@executes
|
||||
|
||||
if (service.deleteRange(sectorId, rangeIndex)) {
|
||||
sender.sendMessage(Component.text("Range #$rangeIndex in Sector #$sectorId deleted.", NamedTextColor.GREEN))
|
||||
} else {
|
||||
sender.sendMessage(Component.text("Failed to delete range. (Invalid index or sector already confirmed?)", NamedTextColor.RED))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -270,29 +270,40 @@ class SectorListener(
|
|||
if (myRole == FactionRole.OWNER || myRole == FactionRole.EXEC) {
|
||||
content.append(
|
||||
Component.text("[Transfer to Faction]\n\n", NamedTextColor.GOLD)
|
||||
.clickEvent(ClickEvent.runCommand("/landsector transfer $sectorId $myFaction"))
|
||||
.clickEvent(ClickEvent.runCommand("/landsector operation transfer $sectorId $myFaction"))
|
||||
.hoverEvent(net.kyori.adventure.text.event.HoverEvent.showText(Component.text("Click to transfer ownership to your faction")))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Actions Row 1
|
||||
// Check activation
|
||||
val activationResult = sectorService.checkActivationConditions(sectorId)
|
||||
|
||||
if (activationResult.canActivate) {
|
||||
content.append(
|
||||
Component.text("[Activate]", NamedTextColor.DARK_GREEN)
|
||||
.clickEvent(ClickEvent.runCommand("/landsector activate $sectorId"))
|
||||
.clickEvent(ClickEvent.runCommand("/landsector operation activate $sectorId"))
|
||||
.hoverEvent(net.kyori.adventure.text.event.HoverEvent.showText(Component.text("Click to activate sector")))
|
||||
)
|
||||
} else {
|
||||
val reasons = activationResult.reasons.joinToString("\n") { "- $it" }
|
||||
content.append(
|
||||
Component.text("[Activate]", NamedTextColor.GRAY)
|
||||
.hoverEvent(net.kyori.adventure.text.event.HoverEvent.showText(Component.text("Cannot Activate:\n$reasons", NamedTextColor.RED)))
|
||||
)
|
||||
}
|
||||
content.append(Component.text(" "))
|
||||
content.append(
|
||||
Component.text("[Destroy]\n", NamedTextColor.RED)
|
||||
.clickEvent(ClickEvent.runCommand("/landsector cancel $sectorId"))
|
||||
.clickEvent(ClickEvent.runCommand("/landsector operation cancel $sectorId"))
|
||||
.hoverEvent(net.kyori.adventure.text.event.HoverEvent.showText(Component.text("Click to destroy sector")))
|
||||
)
|
||||
|
||||
// Actions Row 2
|
||||
content.append(
|
||||
Component.text("[Get Tool]\n\n", NamedTextColor.DARK_AQUA)
|
||||
.clickEvent(ClickEvent.runCommand("/landsector givetool $sectorId"))
|
||||
.clickEvent(ClickEvent.runCommand("/landsector operation givetool $sectorId"))
|
||||
.hoverEvent(net.kyori.adventure.text.event.HoverEvent.showText(Component.text("Click to get selection tool")))
|
||||
)
|
||||
|
||||
|
|
@ -306,7 +317,7 @@ class SectorListener(
|
|||
content.append(Component.text("\n"))
|
||||
content.append(
|
||||
Component.text("[x] ", NamedTextColor.RED)
|
||||
.clickEvent(ClickEvent.runCommand("/landsector range delete $sectorId ${range.id}"))
|
||||
.clickEvent(ClickEvent.runCommand("/landsector operation range delete $sectorId ${range.id}"))
|
||||
.hoverEvent(net.kyori.adventure.text.event.HoverEvent.showText(Component.text("Click to delete range")))
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package net.hareworks.hcu.landsector.service
|
||||
|
||||
import net.hareworks.hcu.landsector.LandSectorPlugin
|
||||
import net.hareworks.hcu.landsector.database.SectorDraftsTable
|
||||
import net.hareworks.hcu.landsector.database.SectorsTable
|
||||
import net.hareworks.hcu.landsector.model.Sector
|
||||
|
|
@ -18,9 +19,22 @@ import org.jetbrains.exposed.v1.jdbc.andWhere
|
|||
import org.jetbrains.exposed.v1.jdbc.deleteWhere
|
||||
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.sqrt
|
||||
|
||||
// ... class definition ...
|
||||
class SectorService(private val database: Database, private val landService: net.hareworks.hcu.lands.service.LandService) {
|
||||
class SectorService(
|
||||
private val plugin: LandSectorPlugin,
|
||||
private val database: Database,
|
||||
private val landService: net.hareworks.hcu.lands.service.LandService
|
||||
) {
|
||||
|
||||
data class ActivationResult(val canActivate: Boolean, val reasons: List<String>)
|
||||
|
||||
private val hpCache = ConcurrentHashMap<Int, Int>()
|
||||
private val dirtySet = ConcurrentHashMap.newKeySet<Int>()
|
||||
|
|
@ -68,6 +82,143 @@ class SectorService(private val database: Database, private val landService: net
|
|||
}
|
||||
}
|
||||
|
||||
fun checkActivationConditions(sectorId: Int): ActivationResult {
|
||||
val sector = getSector(sectorId) ?: return ActivationResult(false, listOf("Sector not found"))
|
||||
val draft = draftLands[sectorId] ?: return ActivationResult(false, listOf("No activation draft found"))
|
||||
if (draft.data.parts.isEmpty()) return ActivationResult(false, listOf("No land selected"))
|
||||
|
||||
val reasons = mutableListOf<String>()
|
||||
val config = plugin.config
|
||||
|
||||
// 1. Volume Check
|
||||
val minVolume = config.getInt("activation.min-volume", 1000)
|
||||
val currentVolume = draft.data.parts.sumOf { getVolume(it) }
|
||||
if (currentVolume < minVolume) {
|
||||
reasons.add("Volume too small: $currentVolume < $minVolume")
|
||||
}
|
||||
|
||||
// 2. Range Check (Core Protection)
|
||||
val coreRadius = config.getDouble("activation.core-protection.radius", 5.0)
|
||||
val coreHeight = config.getInt("activation.core-protection.height", 10)
|
||||
|
||||
// Define required Cylinder around sector core
|
||||
// Center: sector.x, sector.y, sector.z.
|
||||
val yMin = sector.y - (coreHeight / 2)
|
||||
val yMax = sector.y + (coreHeight + 1) / 2 - 1
|
||||
|
||||
if (!isCylinderCovered(sector.x, sector.y, sector.z, coreRadius, yMin, yMax, draft.data.parts)) {
|
||||
reasons.add("Land must cover core area (R:$coreRadius, H:$coreHeight around core)")
|
||||
}
|
||||
|
||||
// 3. Distance Check
|
||||
val minDist = config.getInt("activation.distance-from-others", 20)
|
||||
val activeSectors = sectorsCache.values.filter {
|
||||
it.world == sector.world && it.landId != null && it.id != sector.id
|
||||
}
|
||||
|
||||
for (otherSector in activeSectors) {
|
||||
val otherLand = landService.getLand(otherSector.landId!!) ?: continue
|
||||
if (isTooClose(draft.data.parts, otherLand.data.parts, minDist)) {
|
||||
reasons.add("Too close to Sector ${otherSector.id} (Min $minDist blocks)")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return ActivationResult(reasons.isEmpty(), reasons)
|
||||
}
|
||||
|
||||
private fun getVolume(shape: Shape): Long {
|
||||
return when (shape) {
|
||||
is Shape.Cuboid -> {
|
||||
val dx = abs(shape.x2 - shape.x1) + 1L
|
||||
val dy = abs(shape.y2 - shape.y1) + 1L
|
||||
val dz = abs(shape.z2 - shape.z1) + 1L
|
||||
dx * dy * dz
|
||||
}
|
||||
is Shape.Cylinder -> {
|
||||
val r = shape.radius
|
||||
val h = (shape.bottomHeight + shape.topHeight + 1).toLong()
|
||||
(Math.PI * r * r).toLong() * h // Approximate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isCylinderCovered(cx: Int, cy: Int, cz: Int, r: Double, yMin: Int, yMax: Int, shapes: List<Shape>): Boolean {
|
||||
val rInt = ceil(r).toInt()
|
||||
val rSq = r * r
|
||||
|
||||
for (y in yMin..yMax) {
|
||||
for (x in -rInt..rInt) {
|
||||
for (z in -rInt..rInt) {
|
||||
if ((x*x + z*z).toDouble() <= rSq) {
|
||||
val gx = cx + x
|
||||
val gz = cz + z
|
||||
if (shapes.none { contains(it, gx, y, gz) }) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun contains(shape: Shape, x: Int, y: Int, z: Int): Boolean {
|
||||
return when (shape) {
|
||||
is Shape.Cuboid -> {
|
||||
x >= min(shape.x1, shape.x2) && x <= max(shape.x1, shape.x2) &&
|
||||
y >= min(shape.y1, shape.y2) && y <= max(shape.y1, shape.y2) &&
|
||||
z >= min(shape.z1, shape.z2) && z <= max(shape.z1, shape.z2)
|
||||
}
|
||||
is Shape.Cylinder -> {
|
||||
if (y < shape.y - shape.bottomHeight || y > shape.y + shape.topHeight) return false
|
||||
val dx = x - shape.x
|
||||
val dz = z - shape.z
|
||||
(dx*dx + dz*dz) <= (shape.radius * shape.radius)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isTooClose(shapes1: List<Shape>, shapes2: List<Shape>, minDist: Int): Boolean {
|
||||
for (s1 in shapes1) {
|
||||
for (s2 in shapes2) {
|
||||
if (getDistance(s1, s2) < minDist) return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun getDistance(s1: Shape, s2: Shape): Double {
|
||||
val bb1 = getBounds(s1)
|
||||
val bb2 = getBounds(s2)
|
||||
|
||||
val dx = max(0, max(bb1.minX - bb2.maxX, bb2.minX - bb1.maxX))
|
||||
val dy = max(0, max(bb1.minY - bb2.maxY, bb2.minY - bb1.maxY))
|
||||
val dz = max(0, max(bb1.minZ - bb2.maxZ, bb2.minZ - bb1.maxZ))
|
||||
|
||||
return sqrt((dx*dx + dy*dy + dz*dz).toDouble())
|
||||
}
|
||||
|
||||
data class Bounds(val minX: Int, val maxX: Int, val minY: Int, val maxY: Int, val minZ: Int, val maxZ: Int)
|
||||
|
||||
private fun getBounds(shape: Shape): Bounds {
|
||||
return when (shape) {
|
||||
is Shape.Cuboid -> Bounds(
|
||||
min(shape.x1, shape.x2), max(shape.x1, shape.x2),
|
||||
min(shape.y1, shape.y2), max(shape.y1, shape.y2),
|
||||
min(shape.z1, shape.z2), max(shape.z1, shape.z2)
|
||||
)
|
||||
is Shape.Cylinder -> {
|
||||
val r = ceil(shape.radius).toInt()
|
||||
Bounds(
|
||||
shape.x - r, shape.x + r,
|
||||
shape.y - shape.bottomHeight, shape.y + shape.topHeight,
|
||||
shape.z - r, shape.z + r
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun createSector(ownerActorId: Int, world: String, x: Int, y: Int, z: Int): Sector? {
|
||||
return transaction(database) {
|
||||
val id = SectorsTable.insert {
|
||||
|
|
|
|||
6
src/main/resources/config.yml
Normal file
6
src/main/resources/config.yml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
activation:
|
||||
min-volume: 1000
|
||||
distance-from-others: 10
|
||||
core-protection:
|
||||
radius: 5.0
|
||||
height: 10
|
||||
Loading…
Reference in New Issue
Block a user