bin/削除・設定の調整
This commit is contained in:
parent
167daab8cd
commit
33f3b4f878
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -4,3 +4,4 @@ build/
|
|||
*.iml
|
||||
.idea/
|
||||
out/
|
||||
bin
|
||||
|
|
@ -1,100 +0,0 @@
|
|||
package net.hareworks.hcu.landsector
|
||||
|
||||
import net.hareworks.hcu.core.Main
|
||||
import net.hareworks.hcu.core.player.PlayerIdService
|
||||
import net.hareworks.hcu.landsector.command.LandSectorCommand
|
||||
import net.hareworks.hcu.landsector.listener.SectorListener
|
||||
import net.hareworks.hcu.landsector.service.SelectionService
|
||||
import net.hareworks.hcu.landsector.service.SectorService
|
||||
import org.bukkit.plugin.java.JavaPlugin
|
||||
|
||||
class LandSectorPlugin : JavaPlugin() {
|
||||
|
||||
companion object {
|
||||
lateinit var instance: LandSectorPlugin
|
||||
private set
|
||||
}
|
||||
|
||||
var sectorService: SectorService? = null
|
||||
var selectionService: SelectionService? = null
|
||||
|
||||
override fun onEnable() {
|
||||
instance = this
|
||||
|
||||
saveDefaultConfig()
|
||||
|
||||
selectionService = SelectionService()
|
||||
|
||||
// Register commands
|
||||
LandSectorCommand(this).register()
|
||||
|
||||
// Defer service init
|
||||
server.scheduler.runTaskLater(this, Runnable {
|
||||
initializeServices()
|
||||
}, 60L)
|
||||
|
||||
logger.info("LandSector plugin has been enabled!")
|
||||
}
|
||||
|
||||
private fun initializeServices() {
|
||||
val db = try {
|
||||
Main.instance.exposedDatabase()
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
|
||||
if (db == null) {
|
||||
logger.severe("hcu-core database not ready.")
|
||||
return
|
||||
}
|
||||
|
||||
val landsPlugin = server.pluginManager.getPlugin("lands") as? net.hareworks.hcu.lands.App
|
||||
val landService = landsPlugin?.landService
|
||||
if (landService == null) {
|
||||
logger.severe("Lands plugin or service not ready.")
|
||||
return
|
||||
}
|
||||
|
||||
val service = SectorService(this, db, landService)
|
||||
try {
|
||||
service.init()
|
||||
} catch (e: Exception) {
|
||||
logger.severe("Failed to init DB: ${e.message}")
|
||||
return
|
||||
}
|
||||
this.sectorService = service
|
||||
|
||||
val pIdService = server.servicesManager.load(PlayerIdService::class.java)
|
||||
if (pIdService == null) {
|
||||
logger.severe("PlayerIdService not found")
|
||||
return
|
||||
}
|
||||
|
||||
val selService = selectionService
|
||||
if (selService != null) {
|
||||
val factionService = net.hareworks.hcu.faction.service.FactionService()
|
||||
server.pluginManager.registerEvents(SectorListener(this, service, pIdService, selService, factionService), this)
|
||||
server.pluginManager.registerEvents(net.hareworks.hcu.landsector.listener.SelectionListener(this, selService, service), this)
|
||||
|
||||
// Schedule visualization task
|
||||
net.hareworks.hcu.landsector.task.SelectionVisualizerTask(this, selService, service).runTaskTimer(this, 0L, 4L)
|
||||
} else {
|
||||
logger.severe("SelectionService not initialized!")
|
||||
}
|
||||
|
||||
// Schedule auto-save every 5 minutes
|
||||
server.scheduler.runTaskTimerAsynchronously(this, Runnable {
|
||||
service.flushChanges()
|
||||
}, 6000L, 6000L)
|
||||
|
||||
// Schedule rotation task
|
||||
net.hareworks.hcu.landsector.task.SectorRotationTask(this).runTaskTimer(this, 1L, 1L)
|
||||
|
||||
logger.info("LandSector initialized with services.")
|
||||
}
|
||||
|
||||
override fun onDisable() {
|
||||
sectorService?.flushChanges()
|
||||
logger.info("LandSector plugin has been disabled!")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,257 +0,0 @@
|
|||
package net.hareworks.hcu.landsector.command
|
||||
|
||||
import net.hareworks.hcu.landsector.LandSectorPlugin
|
||||
import net.hareworks.hcu.landsector.model.Sector
|
||||
import net.hareworks.kommand_lib.kommand
|
||||
import net.kyori.adventure.text.Component
|
||||
import net.kyori.adventure.text.format.NamedTextColor
|
||||
import org.bukkit.Bukkit
|
||||
import org.bukkit.Location
|
||||
import org.bukkit.Material
|
||||
import org.bukkit.NamespacedKey
|
||||
import org.bukkit.entity.Player
|
||||
import org.bukkit.entity.Shulker
|
||||
import org.bukkit.inventory.ItemStack
|
||||
import org.bukkit.persistence.PersistentDataType
|
||||
|
||||
class LandSectorCommand(private val landSectorPlugin: LandSectorPlugin) {
|
||||
fun register() {
|
||||
kommand(landSectorPlugin) {
|
||||
command("landsector") {
|
||||
// Admin Commands
|
||||
literal("reload") {
|
||||
condition { it.isOp }
|
||||
executes {
|
||||
landSectorPlugin.reloadConfig()
|
||||
sender.sendMessage(Component.text("Configuration reloaded.", NamedTextColor.GREEN))
|
||||
}
|
||||
}
|
||||
|
||||
literal("give") {
|
||||
condition { it.isOp }
|
||||
executes {
|
||||
val player = sender as? Player ?: return@executes
|
||||
|
||||
val item = ItemStack(Material.BEDROCK)
|
||||
val meta = item.itemMeta
|
||||
meta.displayName(Component.text("Sector Core", NamedTextColor.LIGHT_PURPLE))
|
||||
meta.persistentDataContainer.set(
|
||||
NamespacedKey(landSectorPlugin, "component"),
|
||||
PersistentDataType.STRING,
|
||||
"sector_core"
|
||||
)
|
||||
item.itemMeta = meta
|
||||
|
||||
player.inventory.addItem(item)
|
||||
sender.sendMessage(Component.text("Gave 1 Sector Core.", NamedTextColor.GREEN))
|
||||
}
|
||||
}
|
||||
|
||||
literal("list") {
|
||||
condition { it.isOp }
|
||||
executes {
|
||||
val player = sender as? Player ?: return@executes
|
||||
val service = landSectorPlugin.sectorService
|
||||
if (service == null) {
|
||||
sender.sendMessage(Component.text("SectorService not ready.", NamedTextColor.RED))
|
||||
return@executes
|
||||
}
|
||||
|
||||
val worldName = player.world.name
|
||||
val loc = player.location
|
||||
|
||||
val sectors = service.getAllSectors(worldName)
|
||||
.sortedBy { sector ->
|
||||
val sx = sector.x + 0.5
|
||||
val sy = sector.y + 1.0
|
||||
val sz = sector.z + 0.5
|
||||
|
||||
(loc.x - sx) * (loc.x - sx) +
|
||||
(loc.y - sy) * (loc.y - sy) +
|
||||
(loc.z - sz) * (loc.z - sz)
|
||||
}
|
||||
|
||||
sender.sendMessage(Component.text("=== Sector List (${sectors.size}) ===", NamedTextColor.GOLD))
|
||||
sectors.forEach { sector ->
|
||||
val distSq = (loc.x - (sector.x + 0.5)) * (loc.x - (sector.x + 0.5)) +
|
||||
(loc.y - (sector.y + 1.0)) * (loc.y - (sector.y + 1.0)) +
|
||||
(loc.z - (sector.z + 0.5)) * (loc.z - (sector.z + 0.5))
|
||||
val dist = Math.sqrt(distSq).toInt()
|
||||
|
||||
sender.sendMessage(
|
||||
Component.text()
|
||||
.append(Component.text("#${sector.id} ", NamedTextColor.YELLOW))
|
||||
.append(Component.text("(${sector.x}, ${sector.y}, ${sector.z}) ", NamedTextColor.GRAY))
|
||||
.append(Component.text("HP: ${sector.hp} ", NamedTextColor.RED))
|
||||
.append(Component.text("- ${dist}m", NamedTextColor.AQUA))
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
literal("delete") {
|
||||
condition { it.isOp }
|
||||
integer("id") {
|
||||
executes {
|
||||
val id = argument<Int>("id")
|
||||
val service = landSectorPlugin.sectorService
|
||||
if (service == null) {
|
||||
sender.sendMessage(Component.text("SectorService not ready.", NamedTextColor.RED))
|
||||
return@executes
|
||||
}
|
||||
|
||||
val sector = service.deleteSector(id)
|
||||
if (sector == null) {
|
||||
sender.sendMessage(Component.text("Sector #$id not found.", NamedTextColor.RED))
|
||||
} else {
|
||||
// Physical removal
|
||||
val world = Bukkit.getWorld(sector.world)
|
||||
if (world != null) {
|
||||
val x = sector.x
|
||||
val y = sector.y
|
||||
val z = sector.z
|
||||
|
||||
val blockBase = world.getBlockAt(x, y - 1, z)
|
||||
val blockTop = world.getBlockAt(x, y + 1, z)
|
||||
|
||||
if (blockBase.type == Material.DEEPSLATE_TILE_SLAB) blockBase.type = Material.AIR
|
||||
if (blockTop.type == Material.DEEPSLATE_TILE_SLAB) blockTop.type = Material.AIR
|
||||
|
||||
// Remove All Entities with this sector ID
|
||||
val center = Location(world, x + 0.5, y.toDouble(), z + 0.5)
|
||||
if (center.chunk.isLoaded) {
|
||||
val sectorKey = NamespacedKey(landSectorPlugin, "sector_id")
|
||||
center.chunk.entities.forEach { entity ->
|
||||
if (entity.persistentDataContainer.has(sectorKey, PersistentDataType.INTEGER)) {
|
||||
val sId = entity.persistentDataContainer.get(sectorKey, PersistentDataType.INTEGER)
|
||||
if (sId == id) {
|
||||
entity.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sender.sendMessage(Component.text("Sector #$id deleted.", NamedTextColor.GREEN))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun giveTool(player: Player, sectorId: Int?) {
|
||||
val item = ItemStack(Material.FLINT)
|
||||
val meta = item.itemMeta
|
||||
meta.displayName(Component.text("Range Selection Tool" + (if (sectorId != null) " (#$sectorId)" else ""), NamedTextColor.AQUA))
|
||||
meta.persistentDataContainer.set(
|
||||
NamespacedKey(landSectorPlugin, "component"),
|
||||
PersistentDataType.STRING,
|
||||
"land_sector_tool"
|
||||
)
|
||||
if (sectorId != null) {
|
||||
meta.persistentDataContainer.set(
|
||||
NamespacedKey(landSectorPlugin, "sector_id"),
|
||||
PersistentDataType.INTEGER,
|
||||
sectorId
|
||||
)
|
||||
}
|
||||
meta.lore(listOf(
|
||||
Component.text("Left Click: Switch Mode", NamedTextColor.GRAY),
|
||||
Component.text("Right Click: Select Position", NamedTextColor.GRAY),
|
||||
Component.text("Sneaking acts as modifier", NamedTextColor.DARK_GRAY)
|
||||
) + if (sectorId != null) listOf(Component.text("Linked to Sector #$sectorId", NamedTextColor.GOLD)) else emptyList())
|
||||
item.itemMeta = meta
|
||||
|
||||
player.inventory.addItem(item)
|
||||
player.sendMessage(Component.text("Gave Range Selection Tool${if (sectorId != null) " for Sector #$sectorId" else ""}.", NamedTextColor.GREEN))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
package net.hareworks.hcu.landsector.database
|
||||
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import net.hareworks.hcu.lands.model.LandData
|
||||
import org.jetbrains.exposed.v1.core.Table
|
||||
|
||||
object SectorDraftsTable : Table("sector_drafts") {
|
||||
val sectorId = integer("sector_id").references(SectorsTable.id)
|
||||
val data = text("data").transform(
|
||||
{ json -> Json.decodeFromString(LandData.serializer(), json) },
|
||||
{ value -> Json.encodeToString(LandData.serializer(), value) }
|
||||
)
|
||||
|
||||
override val primaryKey = PrimaryKey(sectorId)
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
package net.hareworks.hcu.landsector.database
|
||||
|
||||
import org.jetbrains.exposed.v1.core.Table
|
||||
|
||||
object SectorsTable : Table("land_sectors") {
|
||||
val id = integer("id").autoIncrement()
|
||||
val ownerActorId = integer("owner_actor_id")
|
||||
val world = varchar("world", 64)
|
||||
val x = integer("x")
|
||||
val y = integer("y")
|
||||
val z = integer("z")
|
||||
val hp = integer("hp").default(1000)
|
||||
|
||||
val landId = integer("land_id").nullable()
|
||||
|
||||
override val primaryKey = PrimaryKey(id)
|
||||
}
|
||||
|
|
@ -1,491 +0,0 @@
|
|||
package net.hareworks.hcu.landsector.listener
|
||||
|
||||
import net.hareworks.hcu.landsector.LandSectorPlugin
|
||||
import net.hareworks.hcu.landsector.service.SectorService
|
||||
import net.hareworks.hcu.core.player.PlayerIdService
|
||||
import net.kyori.adventure.text.Component
|
||||
import net.kyori.adventure.text.format.NamedTextColor
|
||||
import org.bukkit.Material
|
||||
import org.bukkit.NamespacedKey
|
||||
import org.bukkit.Particle
|
||||
import org.bukkit.Sound
|
||||
import org.bukkit.entity.EntityType
|
||||
import org.bukkit.entity.Shulker
|
||||
import org.bukkit.attribute.Attribute
|
||||
import org.bukkit.event.EventHandler
|
||||
import org.bukkit.event.Listener
|
||||
import org.bukkit.event.block.BlockBreakEvent
|
||||
import org.bukkit.event.block.BlockPlaceEvent
|
||||
import org.bukkit.event.entity.EntityDamageEvent
|
||||
import org.bukkit.event.entity.EntityDamageByEntityEvent
|
||||
import org.bukkit.event.world.ChunkLoadEvent
|
||||
import org.bukkit.entity.Player
|
||||
import org.bukkit.entity.BlockDisplay
|
||||
import org.bukkit.persistence.PersistentDataType
|
||||
import org.bukkit.util.Transformation
|
||||
import org.joml.Vector3f
|
||||
import org.joml.Quaternionf
|
||||
import org.bukkit.block.data.type.Slab
|
||||
|
||||
import net.hareworks.hcu.landsector.service.SelectionService
|
||||
import net.kyori.adventure.text.event.ClickEvent
|
||||
import org.bukkit.event.player.PlayerInteractEntityEvent
|
||||
|
||||
import net.hareworks.hcu.faction.service.FactionService
|
||||
import net.hareworks.hcu.faction.database.schema.FactionRole
|
||||
|
||||
class SectorListener(
|
||||
private val plugin: LandSectorPlugin,
|
||||
private val sectorService: SectorService,
|
||||
private val playerIdService: PlayerIdService,
|
||||
private val selectionService: SelectionService,
|
||||
private val factionService: FactionService
|
||||
) : Listener {
|
||||
|
||||
@EventHandler
|
||||
fun onBreak(event: BlockBreakEvent) {
|
||||
val block = event.block
|
||||
val loc = block.location
|
||||
val player = event.player
|
||||
|
||||
if (sectorService.isSectorBlock(loc.world.name, loc.blockX, loc.blockY, loc.blockZ)) {
|
||||
player.sendMessage(Component.text("You cannot break Sector Core blocks!", NamedTextColor.RED))
|
||||
event.isCancelled = true
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
fun onPlace(event: BlockPlaceEvent) {
|
||||
val block = event.block
|
||||
val loc = block.location
|
||||
val player = event.player
|
||||
|
||||
// Global protection check
|
||||
if (sectorService.isSectorArea(loc.world.name, loc.blockX, loc.blockY, loc.blockZ)) {
|
||||
player.sendMessage(Component.text("This area is protected by a Sector Core.", NamedTextColor.RED))
|
||||
event.isCancelled = true
|
||||
return
|
||||
}
|
||||
|
||||
val item = event.itemInHand
|
||||
val meta = item.itemMeta ?: return
|
||||
val key = NamespacedKey(plugin, "component")
|
||||
|
||||
if (meta.persistentDataContainer.get(key, PersistentDataType.STRING) != "sector_core") {
|
||||
return
|
||||
}
|
||||
|
||||
val playerEntry = playerIdService.find(player.uniqueId)
|
||||
if (playerEntry == null) {
|
||||
player.sendMessage(Component.text("Identity not found.", NamedTextColor.RED))
|
||||
event.isCancelled = true
|
||||
return
|
||||
}
|
||||
|
||||
// ... (rest of the code)
|
||||
|
||||
// Check 3x3x3 space availability (relative to placed block base)
|
||||
val baseLoc = block.location
|
||||
for (dx in -1..1) {
|
||||
for (dy in 0..2) {
|
||||
for (dz in -1..1) {
|
||||
if (dx == 0 && dy == 0 && dz == 0) continue // Skip the placed block itself
|
||||
val checkLoc = baseLoc.clone().add(dx.toDouble(), dy.toDouble(), dz.toDouble())
|
||||
if (!checkLoc.block.type.isAir) {
|
||||
player.sendMessage(Component.text("Not enough space! Need 3x3x3 free area.", NamedTextColor.RED))
|
||||
event.isCancelled = true
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Define Center Location (This will be the DB coordinates)
|
||||
val centerLoc = baseLoc.clone().add(0.0, 1.0, 0.0) // y+1 from base
|
||||
|
||||
val sector = sectorService.createSector(
|
||||
playerEntry.actorId,
|
||||
player.world.name,
|
||||
centerLoc.blockX,
|
||||
centerLoc.blockY,
|
||||
centerLoc.blockZ
|
||||
)
|
||||
|
||||
if (sector == null) {
|
||||
player.sendMessage(Component.text("Failed to create sector.", NamedTextColor.RED))
|
||||
event.isCancelled = true
|
||||
return
|
||||
}
|
||||
|
||||
// Create Visuals
|
||||
val sectorKey = NamespacedKey(plugin, "sector_id")
|
||||
val visualCenter = centerLoc.clone().add(0.5, 0.0, 0.5) // Center of the block space
|
||||
|
||||
// 1. Command Block (Center + 0.5y) -> Matches old base+1.5
|
||||
val cbLoc = visualCenter.clone().add(0.0, 0.5, 0.0)
|
||||
val cb = player.world.spawnEntity(cbLoc, EntityType.BLOCK_DISPLAY) as BlockDisplay
|
||||
cb.block = Material.COMMAND_BLOCK.createBlockData {
|
||||
(it as org.bukkit.block.data.Directional).facing = org.bukkit.block.BlockFace.UP
|
||||
}
|
||||
cb.transformation = Transformation(
|
||||
Vector3f(-0.25f, -0.25f, -0.25f),
|
||||
Quaternionf(0f, 0f, 0f, 1f),
|
||||
Vector3f(0.5f, 0.5f, 0.5f),
|
||||
Quaternionf(0f, 0f, 0f, 1f)
|
||||
)
|
||||
cb.persistentDataContainer.set(sectorKey, PersistentDataType.INTEGER, sector.id)
|
||||
|
||||
// 2. Tinted Glass (Center + 0.5y) -> Matches old base+1.5
|
||||
val glass = player.world.spawnEntity(cbLoc, EntityType.BLOCK_DISPLAY) as BlockDisplay
|
||||
glass.block = Material.TINTED_GLASS.createBlockData()
|
||||
glass.transformation = Transformation(
|
||||
Vector3f(-0.4375f, -0.4375f, -0.4375f),
|
||||
Quaternionf(0f, 0f, 0f, 1f),
|
||||
Vector3f(0.875f, 0.875f, 0.875f),
|
||||
Quaternionf(0f, 0f, 0f, 1f)
|
||||
)
|
||||
glass.persistentDataContainer.set(sectorKey, PersistentDataType.INTEGER, sector.id)
|
||||
|
||||
// 3. Top Cauldron (Center + 1.3125y) -> Matches old base+2.3125
|
||||
val topCauldronLoc = visualCenter.clone().add(0.0, 1.3125, 0.0)
|
||||
val topCauldron = player.world.spawnEntity(topCauldronLoc, EntityType.BLOCK_DISPLAY) as BlockDisplay
|
||||
topCauldron.block = Material.CAULDRON.createBlockData()
|
||||
topCauldron.transformation = Transformation(
|
||||
Vector3f(0.4990f, -0.4990f, 0.4990f),
|
||||
Quaternionf(0f, 1f, 0f, 0f),
|
||||
Vector3f(0.9980f, 0.9980f, 0.9980f),
|
||||
Quaternionf(0f, 0f, 0f, 1f)
|
||||
)
|
||||
topCauldron.persistentDataContainer.set(sectorKey, PersistentDataType.INTEGER, sector.id)
|
||||
|
||||
// 4. Bottom Cauldron (Center - 0.3125y) -> Matches old base+0.6875
|
||||
val botCauldronLoc = visualCenter.clone().add(0.0, -0.3125, 0.0)
|
||||
val botCauldron = player.world.spawnEntity(botCauldronLoc, EntityType.BLOCK_DISPLAY) as BlockDisplay
|
||||
botCauldron.block = Material.CAULDRON.createBlockData()
|
||||
botCauldron.transformation = Transformation(
|
||||
Vector3f(-0.4990f, 0.4990f, 0.4990f),
|
||||
Quaternionf(1f, 0f, 0f, 0f),
|
||||
Vector3f(0.9980f, 0.9980f, 0.9980f),
|
||||
Quaternionf(0f, 0f, 0f, 1f)
|
||||
)
|
||||
botCauldron.persistentDataContainer.set(sectorKey, PersistentDataType.INTEGER, sector.id)
|
||||
|
||||
// Spawn Shulker (Hitbox) at Center
|
||||
val shulkerLoc = visualCenter.clone() // Center is exactly y+1 from base
|
||||
val shulker = player.world.spawnEntity(shulkerLoc, EntityType.SHULKER) as Shulker
|
||||
shulker.setAI(false)
|
||||
shulker.isInvisible = true
|
||||
// shulker.isInvulnerable = true // REMOVED to allow damage
|
||||
|
||||
val param = shulker.getAttribute(Attribute.MAX_HEALTH)
|
||||
param?.baseValue = 1000.0
|
||||
shulker.health = 1000.0
|
||||
shulker.maximumNoDamageTicks = 0
|
||||
|
||||
// Tag Shulker with Sector ID
|
||||
shulker.persistentDataContainer.set(sectorKey, PersistentDataType.INTEGER, sector.id)
|
||||
|
||||
// Place Blocks (Base and Top)
|
||||
// Base is at center.y - 1 (The placed block)
|
||||
// Top is at center.y + 1
|
||||
|
||||
block.type = Material.DEEPSLATE_TILE_SLAB
|
||||
val bottomSlab = block.blockData as Slab
|
||||
bottomSlab.type = Slab.Type.BOTTOM
|
||||
block.blockData = bottomSlab
|
||||
|
||||
val topBlock = centerLoc.clone().add(0.0, 1.0, 0.0).block
|
||||
topBlock.type = Material.DEEPSLATE_TILE_SLAB
|
||||
val topSlab = topBlock.blockData as Slab
|
||||
topSlab.type = Slab.Type.TOP
|
||||
topBlock.blockData = topSlab
|
||||
|
||||
player.sendMessage(Component.text("Sector Core placed!", NamedTextColor.GREEN))
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
fun onInteractEntity(event: PlayerInteractEntityEvent) {
|
||||
if (event.hand != org.bukkit.inventory.EquipmentSlot.HAND) return
|
||||
val entity = event.rightClicked
|
||||
if (entity !is Shulker) return
|
||||
|
||||
val sectorKey = NamespacedKey(plugin, "sector_id")
|
||||
if (!entity.persistentDataContainer.has(sectorKey, PersistentDataType.INTEGER)) return
|
||||
|
||||
val sectorId = entity.persistentDataContainer.get(sectorKey, PersistentDataType.INTEGER) ?: return
|
||||
val player = event.player
|
||||
|
||||
// Ownership Check
|
||||
val pEntry = playerIdService.find(player.uniqueId)
|
||||
if (pEntry == null) return
|
||||
val sector = sectorService.getSector(sectorId) ?: return
|
||||
|
||||
// Resolve Owner Name
|
||||
var ownerName = "Unknown"
|
||||
// Try getting faction name
|
||||
val factionName = factionService.getFactionName(sector.ownerActorId)
|
||||
if (factionName != null) {
|
||||
ownerName = "Faction: $factionName"
|
||||
} else {
|
||||
// Assume player. Ideally resolve UUID from ActorID, but for now ID or fallback
|
||||
// We don't have direct Player Actor ID -> Name cache here easily without querying DB or UUID service
|
||||
// Just show ID for now to be safe, or "Player #ID"
|
||||
ownerName = "Player #${sector.ownerActorId}"
|
||||
}
|
||||
|
||||
val canManage = if (sector.ownerActorId == pEntry.actorId) {
|
||||
true
|
||||
} else {
|
||||
// Or if owner is my faction and I am EXEC/OWNER
|
||||
val myFaction = factionService.getFactionOfPlayer(player.uniqueId)
|
||||
if (myFaction == sector.ownerActorId) {
|
||||
val myRole = factionService.getRole(myFaction, player.uniqueId)
|
||||
myRole == FactionRole.OWNER || myRole == FactionRole.EXEC
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
if (!canManage) {
|
||||
player.sendMessage(Component.text("You are not the owner of this sector.", NamedTextColor.RED))
|
||||
return
|
||||
}
|
||||
|
||||
// Create Written Book for UI
|
||||
val book = org.bukkit.inventory.ItemStack(Material.WRITTEN_BOOK)
|
||||
val meta = book.itemMeta as org.bukkit.inventory.meta.BookMeta
|
||||
|
||||
meta.title(Component.text("Sector Manager"))
|
||||
meta.author(Component.text("System"))
|
||||
|
||||
// Build Page Content
|
||||
val content = Component.text()
|
||||
.append(Component.text("Sector Core @ ${sector.x},${sector.y},${sector.z}\n", NamedTextColor.BLACK))
|
||||
.append(Component.text("Owner: $ownerName\n\n", NamedTextColor.DARK_GRAY))
|
||||
|
||||
// Transfer Check
|
||||
val myFaction = factionService.getFactionOfPlayer(player.uniqueId)
|
||||
if (myFaction != null && sector.ownerActorId != myFaction) {
|
||||
val myRole = factionService.getRole(myFaction, player.uniqueId)
|
||||
if (myRole == FactionRole.OWNER || myRole == FactionRole.EXEC) {
|
||||
content.append(
|
||||
Component.text("[Transfer to Faction]\n\n", NamedTextColor.GOLD)
|
||||
.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 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 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 operation givetool $sectorId"))
|
||||
.hoverEvent(net.kyori.adventure.text.event.HoverEvent.showText(Component.text("Click to get selection tool")))
|
||||
)
|
||||
|
||||
// Ranges List
|
||||
val ranges = sectorService.getRanges(sectorId)
|
||||
if (ranges.isEmpty()) {
|
||||
content.append(Component.text("Parts: None", NamedTextColor.GRAY))
|
||||
} else {
|
||||
content.append(Component.text("Parts:", NamedTextColor.BLACK))
|
||||
ranges.forEach { range ->
|
||||
content.append(Component.text("\n"))
|
||||
content.append(
|
||||
Component.text("[x] ", NamedTextColor.RED)
|
||||
.clickEvent(ClickEvent.runCommand("/landsector operation 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))
|
||||
}
|
||||
}
|
||||
|
||||
meta.addPages(content.build())
|
||||
book.itemMeta = meta
|
||||
|
||||
player.openBook(book)
|
||||
event.isCancelled = true
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
fun onDamage(event: EntityDamageEvent) {
|
||||
val entity = event.entity
|
||||
if (entity !is Shulker) return
|
||||
|
||||
val sectorKey = NamespacedKey(plugin, "sector_id")
|
||||
if (!entity.persistentDataContainer.has(sectorKey, PersistentDataType.INTEGER)) return
|
||||
|
||||
val sectorId = entity.persistentDataContainer.get(sectorKey, PersistentDataType.INTEGER) ?: return
|
||||
|
||||
// Prevent vanilla death logic but show damage effect
|
||||
// We restore health to max after processing
|
||||
val damage = event.finalDamage
|
||||
val maxHealth = entity.getAttribute(Attribute.MAX_HEALTH)?.value ?: 1000.0
|
||||
|
||||
// Cancel the event so it doesn't actually die in vanilla terms,
|
||||
// OR let it happen but force health reset.
|
||||
// Cancelling prevents the red flash in some versions/cases, so setting damage to 0 is often better
|
||||
// if we want the effect. But resetting health is robust.
|
||||
val oldHp = sectorService.getHealth(sectorId) ?: return // Get current health from service
|
||||
val newHp = sectorService.reduceHealth(sectorId, damage.toInt())
|
||||
|
||||
entity.health = maxHealth
|
||||
entity.noDamageTicks = 0 // Disable invulnerability
|
||||
|
||||
if (newHp != null) {
|
||||
val loc = entity.location
|
||||
val world = entity.world
|
||||
|
||||
// Sound Logic
|
||||
val threshold = 100 // 10% of 1000
|
||||
val crossedThreshold = (oldHp / threshold) > (newHp / threshold)
|
||||
|
||||
if (crossedThreshold) {
|
||||
world.playSound(loc, Sound.BLOCK_ANVIL_PLACE, 1.0f, 0.5f)
|
||||
} else {
|
||||
world.playSound(loc, Sound.BLOCK_STONE_BREAK, 1.0f, 1.0f)
|
||||
}
|
||||
|
||||
world.spawnParticle(Particle.BLOCK, loc.add(0.0, 0.5, 0.0), 20, 0.3, 0.3, 0.3, Material.BEDROCK.createBlockData())
|
||||
|
||||
if (event is EntityDamageByEntityEvent && event.damager is Player) {
|
||||
val player = event.damager as Player
|
||||
|
||||
val percent = newHp.toDouble() / maxHealth
|
||||
val color = when {
|
||||
percent > 0.5 -> NamedTextColor.GREEN
|
||||
percent > 0.2 -> NamedTextColor.YELLOW
|
||||
else -> NamedTextColor.RED
|
||||
}
|
||||
|
||||
val progressBar = createProgressBar(newHp, maxHealth.toInt(), color)
|
||||
|
||||
val message = Component.text()
|
||||
.append(Component.text("Sector Core ", NamedTextColor.GOLD))
|
||||
.append(progressBar)
|
||||
.append(Component.text(" "))
|
||||
.append(Component.text(newHp, color))
|
||||
.append(Component.text("/", NamedTextColor.GRAY))
|
||||
.append(Component.text(maxHealth.toInt(), NamedTextColor.GRAY))
|
||||
.build()
|
||||
|
||||
player.sendActionBar(message)
|
||||
}
|
||||
|
||||
if (newHp <= 0) {
|
||||
// Destruction
|
||||
entity.remove()
|
||||
|
||||
// Remove other visuals in chunk
|
||||
val chunk = entity.chunk
|
||||
chunk.entities.forEach { ent ->
|
||||
val sId = ent.persistentDataContainer.get(sectorKey, PersistentDataType.INTEGER)
|
||||
if (sId == sectorId) ent.remove()
|
||||
}
|
||||
|
||||
// Remove blocks.
|
||||
val x = loc.blockX
|
||||
val y = loc.blockY
|
||||
val z = loc.blockZ
|
||||
|
||||
val base = world.getBlockAt(x, y - 1, z)
|
||||
val top = world.getBlockAt(x, y + 1, z)
|
||||
|
||||
if (base.type == Material.DEEPSLATE_TILE_SLAB) base.type = Material.AIR
|
||||
if (top.type == Material.DEEPSLATE_TILE_SLAB) top.type = Material.AIR
|
||||
|
||||
world.dropItemNaturally(loc, org.bukkit.inventory.ItemStack(Material.DEEPSLATE_TILE_SLAB, 2))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createProgressBar(current: Int, max: Int, color: NamedTextColor): Component {
|
||||
val totalBars = 20
|
||||
val percent = current.toDouble() / max.toDouble()
|
||||
val filledBars = (totalBars * percent).toInt().coerceIn(0, totalBars)
|
||||
val emptyBars = totalBars - filledBars
|
||||
|
||||
return Component.text()
|
||||
.append(Component.text("[", NamedTextColor.DARK_GRAY))
|
||||
.append(Component.text("|".repeat(filledBars), color))
|
||||
.append(Component.text("|".repeat(emptyBars), NamedTextColor.GRAY))
|
||||
.append(Component.text("]", NamedTextColor.DARK_GRAY))
|
||||
.build()
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
fun onChunkLoad(event: ChunkLoadEvent) {
|
||||
if (event.isNewChunk) return // Optimization: New chunks won't have old sectors to cleanup
|
||||
|
||||
val chunk = event.chunk
|
||||
val entities = chunk.entities
|
||||
val sectorKey = NamespacedKey(plugin, "sector_id")
|
||||
|
||||
entities.forEach { entity ->
|
||||
if (entity.persistentDataContainer.has(sectorKey, PersistentDataType.INTEGER)) {
|
||||
val sectorId = entity.persistentDataContainer.get(sectorKey, PersistentDataType.INTEGER) ?: return@forEach
|
||||
|
||||
// Check existance async
|
||||
plugin.server.scheduler.runTaskAsynchronously(plugin, Runnable {
|
||||
val exists = sectorService.exists(sectorId)
|
||||
if (!exists) {
|
||||
plugin.server.scheduler.runTask(plugin, Runnable {
|
||||
// Re-verify ent validity
|
||||
if (entity.isValid) {
|
||||
entity.remove()
|
||||
// We should also clean up blocks, but that requires knowing where they are relative to entity
|
||||
val loc = entity.location
|
||||
val world = entity.world
|
||||
val x = loc.blockX
|
||||
val y = loc.blockY
|
||||
val z = loc.blockZ
|
||||
|
||||
// Entity is at x, y, z
|
||||
// Base is y-1, Top is y+1
|
||||
val base = world.getBlockAt(x, y - 1, z)
|
||||
val top = world.getBlockAt(x, y + 1, z)
|
||||
|
||||
if (base.type == Material.DEEPSLATE_TILE_SLAB) base.type = Material.AIR
|
||||
if (top.type == Material.DEEPSLATE_TILE_SLAB) top.type = Material.AIR
|
||||
|
||||
plugin.logger.info("Removed orphaned sector artifacts for ID $sectorId at $x,$y,$z")
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,204 +0,0 @@
|
|||
package net.hareworks.hcu.landsector.listener
|
||||
|
||||
import net.hareworks.hcu.landsector.LandSectorPlugin
|
||||
import net.hareworks.hcu.landsector.service.SelectionService
|
||||
import net.hareworks.hcu.landsector.service.SectorService
|
||||
import net.hareworks.hcu.landsector.model.SelectionMode
|
||||
import net.kyori.adventure.text.Component
|
||||
import net.kyori.adventure.text.format.NamedTextColor
|
||||
import org.bukkit.Material
|
||||
import org.bukkit.NamespacedKey
|
||||
import org.bukkit.event.EventHandler
|
||||
import org.bukkit.event.Listener
|
||||
import org.bukkit.event.block.Action
|
||||
import org.bukkit.event.player.PlayerInteractEvent
|
||||
import org.bukkit.persistence.PersistentDataType
|
||||
import org.bukkit.inventory.EquipmentSlot
|
||||
|
||||
class SelectionListener(
|
||||
private val plugin: LandSectorPlugin,
|
||||
private val selectionService: SelectionService,
|
||||
private val sectorService: SectorService
|
||||
) : Listener {
|
||||
|
||||
@EventHandler
|
||||
fun onInteract(event: PlayerInteractEvent) {
|
||||
val item = event.item ?: return
|
||||
if (item.type != Material.FLINT) return
|
||||
|
||||
val key = NamespacedKey(plugin, "component")
|
||||
if (item.itemMeta?.persistentDataContainer?.get(key, PersistentDataType.STRING) != "land_sector_tool") {
|
||||
return
|
||||
}
|
||||
|
||||
// Only Main Hand to avoid double fire
|
||||
if (event.hand != EquipmentSlot.HAND) return
|
||||
|
||||
val player = event.player
|
||||
val selection = selectionService.getSelection(player.uniqueId)
|
||||
|
||||
// Left Click: Cancel only
|
||||
if (event.action == Action.LEFT_CLICK_AIR || event.action == Action.LEFT_CLICK_BLOCK) {
|
||||
event.isCancelled = true
|
||||
return
|
||||
}
|
||||
|
||||
// Right Click: Set Points
|
||||
if (event.action == Action.RIGHT_CLICK_BLOCK || event.action == Action.RIGHT_CLICK_AIR) {
|
||||
event.isCancelled = true // Prevent placing or item usage
|
||||
|
||||
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.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}, CenterMode: ${selection.isCenterMode})", NamedTextColor.GREEN))
|
||||
player.playSound(player.location, org.bukkit.Sound.UI_BUTTON_CLICK, 1.0f, 2.0f)
|
||||
} else {
|
||||
// Set Point 2
|
||||
if (selection.point2 != null) {
|
||||
// Resetting, treat as P1
|
||||
selection.point1 = loc
|
||||
selection.point2 = null
|
||||
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
|
||||
val itemStack = event.item
|
||||
val sectorKey = NamespacedKey(plugin, "sector_id")
|
||||
if (itemStack != null && itemStack.itemMeta.persistentDataContainer.has(sectorKey, PersistentDataType.INTEGER)) {
|
||||
val sId = itemStack.itemMeta.persistentDataContainer.get(sectorKey, PersistentDataType.INTEGER)!!
|
||||
val p1 = selection.point1!!
|
||||
val p2 = selection.point2!!
|
||||
|
||||
// Add Range
|
||||
val range = sectorService.addRange(
|
||||
sId,
|
||||
selection.mode,
|
||||
p1.blockX, p1.blockY, p1.blockZ,
|
||||
p2.blockX, p2.blockY, p2.blockZ,
|
||||
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
|
||||
selection.point2 = null
|
||||
} else {
|
||||
player.sendMessage(Component.text("Position 2 set at ${loc.blockX}, ${loc.blockY}, ${loc.blockZ}", NamedTextColor.GREEN))
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
fun onDrop(event: org.bukkit.event.player.PlayerDropItemEvent) {
|
||||
val item = event.itemDrop.itemStack
|
||||
val key = NamespacedKey(plugin, "component")
|
||||
if (item.itemMeta?.persistentDataContainer?.get(key, PersistentDataType.STRING) == "land_sector_tool") {
|
||||
event.isCancelled = true
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
fun onInventoryClick(event: org.bukkit.event.inventory.InventoryClickEvent) {
|
||||
val player = event.whoClicked as? org.bukkit.entity.Player ?: return
|
||||
val clickedInv = event.clickedInventory
|
||||
val action = event.action
|
||||
|
||||
fun isTool(stack: org.bukkit.inventory.ItemStack?): Boolean {
|
||||
if (stack == null || stack.type != Material.FLINT) return false
|
||||
val key = NamespacedKey(plugin, "component")
|
||||
return stack.itemMeta?.persistentDataContainer?.get(key, PersistentDataType.STRING) == "land_sector_tool"
|
||||
}
|
||||
|
||||
// 1. Prevent dropping via inventory
|
||||
if (action.name.startsWith("DROP")) {
|
||||
if (isTool(event.currentItem) || isTool(event.cursor)) {
|
||||
event.isCancelled = true
|
||||
player.sendMessage(Component.text("You cannot drop this tool.", NamedTextColor.RED))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Prevent interaction with tool in external inventories (Top Inventory)
|
||||
// If clicking inside an inventory that is NOT the player's inventory
|
||||
if (clickedInv != null && clickedInv != player.inventory) {
|
||||
// If trying to place tool (Cursor has tool)
|
||||
if (isTool(event.cursor)) {
|
||||
event.isCancelled = true
|
||||
player.sendMessage(Component.text("You cannot store this tool.", NamedTextColor.RED))
|
||||
return
|
||||
}
|
||||
|
||||
// If trying to swap tool from hotbar (Number key)
|
||||
if (event.click == org.bukkit.event.inventory.ClickType.NUMBER_KEY) {
|
||||
val swappedItem = player.inventory.getItem(event.hotbarButton)
|
||||
if (isTool(swappedItem)) {
|
||||
event.isCancelled = true
|
||||
player.sendMessage(Component.text("You cannot store this tool.", NamedTextColor.RED))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// If trying to take tool IS in external inventory? (Shouldn't happen, but prevent taking just in case)
|
||||
// This safeguards if somehow it got there.
|
||||
if (isTool(event.currentItem)) {
|
||||
event.isCancelled = true
|
||||
// Allow them to break the item? No, just block interaction.
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Prevent Shift-Clicking tool FROM player inventory TO external inventory
|
||||
if (clickedInv == player.inventory && event.isShiftClick) {
|
||||
if (isTool(event.currentItem)) {
|
||||
// If top inventory is NOT Crafting/Creative (Personal), block transfer
|
||||
val topType = event.view.topInventory.type
|
||||
if (topType != org.bukkit.event.inventory.InventoryType.CRAFTING &&
|
||||
topType != org.bukkit.event.inventory.InventoryType.CREATIVE) {
|
||||
|
||||
event.isCancelled = true
|
||||
player.sendMessage(Component.text("You cannot store this tool.", NamedTextColor.RED))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
package net.hareworks.hcu.landsector.model
|
||||
|
||||
data class Sector(
|
||||
val id: Int,
|
||||
val ownerActorId: Int,
|
||||
val world: String,
|
||||
val x: Int,
|
||||
val y: Int,
|
||||
val z: Int,
|
||||
val hp: Int,
|
||||
val landId: Int? = null
|
||||
)
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
package net.hareworks.hcu.landsector.model
|
||||
|
||||
data class SectorRange(
|
||||
val id: Int,
|
||||
val sectorId: Int,
|
||||
val type: SelectionMode,
|
||||
val x1: Int,
|
||||
val y1: Int,
|
||||
val z1: Int,
|
||||
val x2: Int,
|
||||
val y2: Int,
|
||||
val z2: Int,
|
||||
val isSneaking: Boolean
|
||||
)
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
package net.hareworks.hcu.landsector.model
|
||||
|
||||
import org.bukkit.Location
|
||||
|
||||
enum class SelectionMode {
|
||||
CUBOID,
|
||||
CYLINDER
|
||||
}
|
||||
|
||||
data class SelectionData(
|
||||
var mode: SelectionMode = SelectionMode.CUBOID,
|
||||
var point1: Location? = null,
|
||||
var point2: Location? = null,
|
||||
var isCenterMode: Boolean = false // Replaces old p1Sneaking logic with explicit toggle
|
||||
)
|
||||
|
|
@ -1,637 +0,0 @@
|
|||
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
|
||||
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
|
||||
import org.jetbrains.exposed.v1.jdbc.insert
|
||||
import org.jetbrains.exposed.v1.jdbc.selectAll
|
||||
import org.jetbrains.exposed.v1.jdbc.select
|
||||
import org.jetbrains.exposed.v1.jdbc.update
|
||||
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 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>()
|
||||
private val sectorsCache = ConcurrentHashMap<Int, Sector>()
|
||||
|
||||
// Draft storage: SectorId -> Land (Draft)
|
||||
private val draftLands = ConcurrentHashMap<Int, net.hareworks.hcu.lands.model.Land>()
|
||||
|
||||
fun init() {
|
||||
transaction(database) {
|
||||
SchemaUtils.createMissingTablesAndColumns(SectorsTable, SectorDraftsTable)
|
||||
|
||||
// Load Sectors
|
||||
SectorsTable.selectAll().forEach {
|
||||
val id = it[SectorsTable.id]
|
||||
val sector = Sector(
|
||||
id,
|
||||
it[SectorsTable.ownerActorId],
|
||||
it[SectorsTable.world],
|
||||
it[SectorsTable.x],
|
||||
it[SectorsTable.y],
|
||||
it[SectorsTable.z],
|
||||
it[SectorsTable.hp],
|
||||
it[SectorsTable.landId]
|
||||
)
|
||||
sectorsCache[id] = sector
|
||||
}
|
||||
|
||||
// Load Drafts
|
||||
SectorDraftsTable.selectAll().forEach {
|
||||
val sId = it[SectorDraftsTable.sectorId]
|
||||
val data = it[SectorDraftsTable.data]
|
||||
val sector = sectorsCache[sId]
|
||||
|
||||
if (sector != null && sector.landId == null) {
|
||||
draftLands[sId] = net.hareworks.hcu.lands.model.Land(
|
||||
id = -1,
|
||||
name = "Draft Sector $sId",
|
||||
actorId = sector.ownerActorId,
|
||||
world = sector.world,
|
||||
data = data
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
it[SectorsTable.ownerActorId] = ownerActorId
|
||||
it[SectorsTable.world] = world
|
||||
it[SectorsTable.x] = x
|
||||
it[SectorsTable.y] = y
|
||||
it[SectorsTable.z] = z
|
||||
it[SectorsTable.hp] = 1000
|
||||
it[SectorsTable.landId] = null
|
||||
}[SectorsTable.id]
|
||||
|
||||
hpCache[id] = 1000
|
||||
val sector = Sector(id, ownerActorId, world, x, y, z, 1000, null)
|
||||
sectorsCache[id] = sector
|
||||
|
||||
// Initialize draft land
|
||||
val draftLand = net.hareworks.hcu.lands.model.Land(
|
||||
id = -1, // Dummy ID
|
||||
name = "Draft Sector $id",
|
||||
actorId = ownerActorId,
|
||||
world = world,
|
||||
data = net.hareworks.hcu.lands.model.LandData()
|
||||
)
|
||||
draftLands[id] = draftLand
|
||||
|
||||
// Persist draft
|
||||
SectorDraftsTable.insert {
|
||||
it[SectorDraftsTable.sectorId] = id
|
||||
it[SectorDraftsTable.data] = draftLand.data
|
||||
}
|
||||
|
||||
sector
|
||||
}
|
||||
}
|
||||
|
||||
fun getSectorAt(world: String, x: Int, y: Int, z: Int): Sector? {
|
||||
// Optimized to use cache
|
||||
return sectorsCache.values.firstOrNull {
|
||||
it.world == world && it.x == x && it.y == y && it.z == z
|
||||
}
|
||||
}
|
||||
|
||||
fun getSector(id: Int): Sector? {
|
||||
return sectorsCache[id] ?: transaction(database) {
|
||||
SectorsTable.selectAll().andWhere { SectorsTable.id eq id }.singleOrNull()?.let {
|
||||
Sector(
|
||||
it[SectorsTable.id],
|
||||
it[SectorsTable.ownerActorId],
|
||||
it[SectorsTable.world],
|
||||
it[SectorsTable.x],
|
||||
it[SectorsTable.y],
|
||||
it[SectorsTable.z],
|
||||
it[SectorsTable.hp],
|
||||
it[SectorsTable.landId]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getHealth(id: Int): Int? {
|
||||
// Try get from cache
|
||||
var currentHp = hpCache[id]
|
||||
|
||||
// If not in cache, load from DB
|
||||
if (currentHp == null) {
|
||||
val sector = sectorsCache[id] ?: return null
|
||||
|
||||
currentHp = sector.hp
|
||||
hpCache[id] = currentHp
|
||||
}
|
||||
return currentHp
|
||||
}
|
||||
|
||||
fun reduceHealth(id: Int, amount: Int): Int? {
|
||||
// Try get from cache
|
||||
var currentHp = hpCache[id]
|
||||
|
||||
// If not in cache, try to init from sectorsCache
|
||||
if (currentHp == null) {
|
||||
val sector = sectorsCache[id] ?: return null
|
||||
currentHp = sector.hp
|
||||
hpCache[id] = currentHp
|
||||
}
|
||||
|
||||
val newHp = (currentHp!! - amount).coerceAtLeast(0)
|
||||
hpCache[id] = newHp
|
||||
dirtySet.add(id)
|
||||
|
||||
return newHp
|
||||
}
|
||||
|
||||
fun flushChanges() {
|
||||
if (dirtySet.isEmpty()) return
|
||||
|
||||
// Take a snapshot of keys to update
|
||||
val toUpdate = dirtySet.toMutableSet()
|
||||
dirtySet.removeAll(toUpdate) // Clear them from dirty set so we don't re-save unless modified again
|
||||
|
||||
if (toUpdate.isEmpty()) return
|
||||
|
||||
transaction(database) {
|
||||
toUpdate.forEach { id ->
|
||||
val hp = hpCache[id] ?: return@forEach
|
||||
SectorsTable.update({ SectorsTable.id eq id }) {
|
||||
it[SectorsTable.hp] = hp
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteSector(id: Int): Sector? {
|
||||
val sector = transaction(database) {
|
||||
val record = SectorsTable.selectAll().andWhere { SectorsTable.id eq id }.singleOrNull() ?: return@transaction null
|
||||
|
||||
SectorDraftsTable.deleteWhere { SectorDraftsTable.sectorId eq id }
|
||||
SectorsTable.deleteWhere { SectorsTable.id eq id }
|
||||
|
||||
Sector(
|
||||
record[SectorsTable.id],
|
||||
record[SectorsTable.ownerActorId],
|
||||
record[SectorsTable.world],
|
||||
record[SectorsTable.x],
|
||||
record[SectorsTable.y],
|
||||
record[SectorsTable.z],
|
||||
record[SectorsTable.hp],
|
||||
record[SectorsTable.landId]
|
||||
)
|
||||
}
|
||||
|
||||
if (sector != null) {
|
||||
hpCache.remove(id)
|
||||
dirtySet.remove(id)
|
||||
sectorsCache.remove(id)
|
||||
draftLands.remove(id)
|
||||
|
||||
if (sector.landId != null) {
|
||||
landService.deleteLand(sector.landId)
|
||||
}
|
||||
}
|
||||
|
||||
return sector
|
||||
}
|
||||
|
||||
fun isSectorBlock(world: String, x: Int, y: Int, z: Int): Boolean {
|
||||
// Sector blocks are at center.y - 1 (Base) and center.y + 1 (Top)
|
||||
// Center is at sector.y
|
||||
return sectorsCache.values.any {
|
||||
it.world == world &&
|
||||
it.x == x &&
|
||||
it.z == z &&
|
||||
(it.y - 1 == y || it.y + 1 == y)
|
||||
}
|
||||
}
|
||||
|
||||
fun isSectorArea(world: String, x: Int, y: Int, z: Int): Boolean {
|
||||
// Check 3x3x3 around center (sector.x, sector.y, sector.z)
|
||||
return sectorsCache.values.any {
|
||||
it.world == world &&
|
||||
x >= it.x - 1 && x <= it.x + 1 &&
|
||||
y >= it.y - 1 && y <= it.y + 1 &&
|
||||
z >= it.z - 1 && z <= it.z + 1
|
||||
}
|
||||
}
|
||||
|
||||
fun exists(id: Int): Boolean {
|
||||
if (hpCache.containsKey(id)) return true
|
||||
|
||||
return transaction(database) {
|
||||
SectorsTable.selectAll().andWhere { SectorsTable.id eq id }.count() > 0
|
||||
}
|
||||
}
|
||||
|
||||
fun getAllSectorsList(): List<Sector> {
|
||||
return transaction(database) {
|
||||
SectorsTable.selectAll().map {
|
||||
val id = it[SectorsTable.id]
|
||||
val cachedHp = hpCache[id]
|
||||
val hp = cachedHp ?: it[SectorsTable.hp]
|
||||
Sector(
|
||||
id,
|
||||
it[SectorsTable.ownerActorId],
|
||||
it[SectorsTable.world],
|
||||
it[SectorsTable.x],
|
||||
it[SectorsTable.y],
|
||||
it[SectorsTable.z],
|
||||
hp,
|
||||
it[SectorsTable.landId]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getAllSectors(world: String): List<Sector> {
|
||||
return transaction(database) {
|
||||
SectorsTable.selectAll()
|
||||
.andWhere { SectorsTable.world eq world }
|
||||
.map {
|
||||
// Update HP from cache if exists
|
||||
val id = it[SectorsTable.id]
|
||||
val cachedHp = hpCache[id]
|
||||
val hp = cachedHp ?: it[SectorsTable.hp]
|
||||
|
||||
Sector(
|
||||
id,
|
||||
it[SectorsTable.ownerActorId],
|
||||
it[SectorsTable.world],
|
||||
it[SectorsTable.x],
|
||||
it[SectorsTable.y],
|
||||
it[SectorsTable.z],
|
||||
hp,
|
||||
it[SectorsTable.landId]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun addRange(sectorId: Int, mode: SelectionMode, x1: Int, y1: Int, z1: Int, x2: Int, y2: Int, z2: Int, isSneaking: Boolean): Any? {
|
||||
val sector = getSector(sectorId) ?: return null
|
||||
|
||||
// Cannot edit if already confirmed
|
||||
if (sector.landId != null) return null
|
||||
|
||||
val draft = draftLands.computeIfAbsent(sectorId) {
|
||||
net.hareworks.hcu.lands.model.Land(
|
||||
id = -1,
|
||||
name = "Draft Sector $sectorId",
|
||||
actorId = sector.ownerActorId,
|
||||
world = sector.world,
|
||||
data = net.hareworks.hcu.lands.model.LandData()
|
||||
)
|
||||
}
|
||||
|
||||
val shape = when (mode) {
|
||||
SelectionMode.CUBOID -> {
|
||||
net.hareworks.hcu.lands.model.Shape.Cuboid(x1, y1, z1, x2, y2, z2)
|
||||
}
|
||||
SelectionMode.CYLINDER -> {
|
||||
// Convert bounds to cylinder params
|
||||
// Approximating from bounding box
|
||||
val minX = minOf(x1, x2)
|
||||
val maxX = maxOf(x1, x2)
|
||||
val minZ = minOf(z1, z2)
|
||||
val maxZ = maxOf(z1, z2)
|
||||
val minY = minOf(y1, y2)
|
||||
val maxY = maxOf(y1, y2)
|
||||
|
||||
val cx = (minX + maxX) / 2
|
||||
val cz = (minZ + maxZ) / 2
|
||||
val cy = (minY + maxY) / 2
|
||||
|
||||
// Radius is max dist from center to edge of bounding box in X or Z
|
||||
val radiusX = (maxX - minX) / 2.0
|
||||
val radiusZ = (maxZ - minZ) / 2.0
|
||||
val radius = maxOf(radiusX, radiusZ)
|
||||
|
||||
val height = maxY - minY + 1
|
||||
val bottom = (cy - minY)
|
||||
val top = (maxY - cy)
|
||||
|
||||
net.hareworks.hcu.lands.model.Shape.Cylinder(cx, cy, cz, radius, bottom, top)
|
||||
}
|
||||
else -> return null
|
||||
}
|
||||
|
||||
// Use LandService modify pattern or direct replacement since stored in local map
|
||||
val newParts = draft.data.parts.toMutableList()
|
||||
newParts.add(shape)
|
||||
val newDraft = draft.copy(data = draft.data.copy(parts = newParts))
|
||||
draftLands[sectorId] = newDraft
|
||||
|
||||
// Persist change
|
||||
transaction(database) {
|
||||
// Check if exists first? Or upsert if supported easily.
|
||||
// We know it exists from creation, but safety check.
|
||||
val count = SectorDraftsTable.update({ SectorDraftsTable.sectorId eq sectorId }) {
|
||||
it[SectorDraftsTable.data] = newDraft.data
|
||||
}
|
||||
if (count == 0) {
|
||||
SectorDraftsTable.insert {
|
||||
it[SectorDraftsTable.sectorId] = sectorId
|
||||
it[SectorDraftsTable.data] = newDraft.data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return shape
|
||||
}
|
||||
|
||||
fun getRanges(sectorId: Int): List<SectorRange> {
|
||||
val sector = getSector(sectorId) ?: return emptyList()
|
||||
|
||||
val land = if (sector.landId != null) {
|
||||
landService.getLand(sector.landId)
|
||||
} else {
|
||||
draftLands[sectorId]
|
||||
} ?: return emptyList()
|
||||
|
||||
// Convert Shapes back to SectorRanges for UI compatibility
|
||||
return land.data.parts.mapIndexed { index, shape ->
|
||||
when (shape) {
|
||||
is net.hareworks.hcu.lands.model.Shape.Cuboid -> {
|
||||
SectorRange(
|
||||
index, // Use index as ID for UI
|
||||
sectorId,
|
||||
SelectionMode.CUBOID,
|
||||
shape.x1, shape.y1, shape.z1,
|
||||
shape.x2, shape.y2, shape.z2,
|
||||
false
|
||||
)
|
||||
}
|
||||
is net.hareworks.hcu.lands.model.Shape.Cylinder -> {
|
||||
SectorRange(
|
||||
index,
|
||||
sectorId,
|
||||
SelectionMode.CYLINDER,
|
||||
shape.x, shape.y, shape.z,
|
||||
shape.radius.toInt(), shape.bottomHeight, shape.topHeight,
|
||||
false
|
||||
)
|
||||
}
|
||||
// Default fallback for unknown shapes if any (shouldn't happen with current limited types)
|
||||
else -> SectorRange(index, sectorId, SelectionMode.CUBOID, 0,0,0,0,0,0, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
val draft = draftLands[sectorId] ?: return false
|
||||
if (rangeIndex < 0 || rangeIndex >= draft.data.parts.size) return false
|
||||
|
||||
val newParts = draft.data.parts.toMutableList()
|
||||
newParts.removeAt(rangeIndex)
|
||||
val newDraft = draft.copy(data = draft.data.copy(parts = newParts))
|
||||
draftLands[sectorId] = newDraft
|
||||
|
||||
// Persist change
|
||||
transaction(database) {
|
||||
SectorDraftsTable.update({ SectorDraftsTable.sectorId eq sectorId }) {
|
||||
it[SectorDraftsTable.data] = newDraft.data
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
fun activateSector(sectorId: Int): Boolean {
|
||||
val sector = getSector(sectorId) ?: return false
|
||||
if (sector.landId != null) return false // Already active
|
||||
|
||||
val draft = draftLands[sectorId] ?: return false
|
||||
if (draft.data.parts.isEmpty()) return false
|
||||
|
||||
val landName = "Sector_${sectorId}_${System.currentTimeMillis()}"
|
||||
|
||||
if (landService.createLand(landName, draft.actorId, draft.world)) {
|
||||
// Find the created land to get ID
|
||||
val lands = landService.findLandsByName(landName)
|
||||
val createdLand = lands.firstOrNull() ?: return false
|
||||
|
||||
// Update parts
|
||||
landService.modifyLand(createdLand.id) { data ->
|
||||
data.copy(parts = draft.data.parts)
|
||||
}
|
||||
|
||||
// Update Sector DB & Delete Draft
|
||||
transaction(database) {
|
||||
SectorsTable.update({ SectorsTable.id eq sectorId }) {
|
||||
it[SectorsTable.landId] = createdLand.id
|
||||
}
|
||||
SectorDraftsTable.deleteWhere { SectorDraftsTable.sectorId eq sectorId }
|
||||
}
|
||||
|
||||
// Update cache & Clear draft
|
||||
sectorsCache[sectorId] = sector.copy(landId = createdLand.id)
|
||||
draftLands.remove(sectorId)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun transferSector(id: Int, newOwnerActorId: Int): Boolean {
|
||||
return transaction(database) {
|
||||
val updated = SectorsTable.update({ SectorsTable.id eq id }) {
|
||||
it[ownerActorId] = newOwnerActorId
|
||||
}
|
||||
if (updated > 0) {
|
||||
// Update cache
|
||||
val current = sectorsCache[id]
|
||||
if (current != null) {
|
||||
sectorsCache[id] = current.copy(ownerActorId = newOwnerActorId)
|
||||
}
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
package net.hareworks.hcu.landsector.service
|
||||
|
||||
import net.hareworks.hcu.landsector.model.SelectionData
|
||||
import org.bukkit.Location
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.ceil
|
||||
|
||||
class SelectionService {
|
||||
private val selections = ConcurrentHashMap<UUID, SelectionData>()
|
||||
|
||||
fun getSelection(uuid: UUID): SelectionData {
|
||||
return selections.computeIfAbsent(uuid) { SelectionData() }
|
||||
}
|
||||
|
||||
fun clearSelection(uuid: UUID) {
|
||||
selections.remove(uuid)
|
||||
}
|
||||
|
||||
fun getAreaDetails(uuid: UUID): String {
|
||||
val data = getSelection(uuid)
|
||||
if (data.point1 == null || data.point2 == null) {
|
||||
return "No complete selection."
|
||||
}
|
||||
|
||||
val p1 = data.point1!!
|
||||
val p2 = data.point2!!
|
||||
|
||||
return if (data.mode == net.hareworks.hcu.landsector.model.SelectionMode.CUBOID) {
|
||||
val width: Int
|
||||
val height: Int
|
||||
val length: Int
|
||||
|
||||
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 {
|
||||
// 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)
|
||||
width = dx * 2 + 1
|
||||
height = dy * 2 + 1
|
||||
length = dz * 2 + 1
|
||||
}
|
||||
"Cuboid: ${width}x${height}x${length} (${width * height * length} blocks)"
|
||||
} else {
|
||||
// Cylinder
|
||||
val dx = p1.blockX - p2.blockX
|
||||
val dz = p1.blockZ - p2.blockZ
|
||||
// Use ceil(dist - 0.5) to find the minimum integer radius R such that (dist <= R + 0.5)
|
||||
// This ensures the point is included in the R+0.5 boundary without overshooting to the next integer.
|
||||
val dist = Math.sqrt((dx * dx + dz * dz).toDouble())
|
||||
val baseRadius = ceil(dist - 0.5)
|
||||
val radius = baseRadius + 0.5
|
||||
|
||||
val totalHeight = if (!data.isCenterMode) {
|
||||
// Normal: P1 center, symmetric height
|
||||
val h = abs(p1.blockY - p2.blockY)
|
||||
h * 2 + 1
|
||||
} else {
|
||||
// Center Mode: P1 base, P2 top (or vice versa, height is diff)
|
||||
abs(p1.blockY - p2.blockY) + 1
|
||||
}
|
||||
|
||||
val volume = PI * radius * radius * totalHeight
|
||||
"Cylinder: r=${"%.0f".format(baseRadius)}, h=$totalHeight (~${ceil(volume).toInt()} blocks)"
|
||||
}
|
||||
}
|
||||
|
||||
fun getCost(uuid: UUID): Double {
|
||||
val data = getSelection(uuid)
|
||||
if (data.point1 == null || data.point2 == null) {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
// Placeholder cost logic: 1 block = 1 unit?
|
||||
// Need to refine based on actual volume
|
||||
val p1 = data.point1!!
|
||||
val p2 = data.point2!!
|
||||
|
||||
val volume = if (data.mode == net.hareworks.hcu.landsector.model.SelectionMode.CUBOID) {
|
||||
val width = abs(p1.blockX - p2.blockX) + 1
|
||||
val height = abs(p1.blockY - p2.blockY) + 1
|
||||
val length = abs(p1.blockZ - p2.blockZ) + 1
|
||||
(width * height * length).toDouble()
|
||||
} else {
|
||||
val dx = p1.blockX - p2.blockX
|
||||
val dz = p1.blockZ - p2.blockZ
|
||||
val dist = Math.sqrt((dx * dx + dz * dz).toDouble())
|
||||
val baseRadius = ceil(dist - 0.5)
|
||||
val radius = baseRadius + 0.5
|
||||
val height = abs(p1.blockY - p2.blockY) + 1
|
||||
PI * radius * radius * height
|
||||
}
|
||||
|
||||
return volume * 0.5 // Example rate
|
||||
}
|
||||
}
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
package net.hareworks.hcu.landsector.task
|
||||
|
||||
import net.hareworks.hcu.landsector.LandSectorPlugin
|
||||
import org.bukkit.Bukkit
|
||||
import org.bukkit.Material
|
||||
import org.bukkit.NamespacedKey
|
||||
import org.bukkit.entity.BlockDisplay
|
||||
import org.bukkit.entity.EntityType
|
||||
import org.bukkit.persistence.PersistentDataType
|
||||
import org.bukkit.scheduler.BukkitRunnable
|
||||
import org.bukkit.util.Transformation
|
||||
import org.joml.Quaternionf
|
||||
import org.joml.Vector3f
|
||||
import kotlin.math.PI
|
||||
|
||||
class SectorRotationTask(private val plugin: LandSectorPlugin) : BukkitRunnable() {
|
||||
|
||||
private val sectorKey = NamespacedKey(plugin, "sector_id")
|
||||
private val scale = Vector3f(0.5f, 0.5f, 0.5f)
|
||||
private val centerOffset = Vector3f(0.25f, 0.25f, 0.25f) // Scale 0.5 * Model Center (0.5, 0.5, 0.5)
|
||||
|
||||
override fun run() {
|
||||
val sectors = plugin.sectorService?.getAllSectorsList() ?: return
|
||||
// Bukkit.getLogger().info("Task running. Sectors count: ${sectors.size}")
|
||||
|
||||
sectors.forEach { sector ->
|
||||
val world = Bukkit.getWorld(sector.world) ?: return@forEach
|
||||
// Simple check if chunk is loaded to avoid loading chunks
|
||||
val chunkX = sector.x shr 4
|
||||
val chunkZ = sector.z shr 4
|
||||
|
||||
if (!world.isChunkLoaded(chunkX, chunkZ)) {
|
||||
// Bukkit.getLogger().info("Chunk not loaded for sector ${sector.id}")
|
||||
return@forEach
|
||||
}
|
||||
|
||||
// Check entities at the core location
|
||||
val centerLoc = org.bukkit.Location(world, sector.x + 0.5, sector.y + 0.5, sector.z + 0.5)
|
||||
|
||||
// Search radius expanded to 2.0 to ensure hit
|
||||
val entities = world.getNearbyEntities(centerLoc, 2.0, 2.0, 2.0)
|
||||
|
||||
// Bukkit.getLogger().info("Checking sector ${sector.id} at $centerLoc. Found entities: ${entities.size}")
|
||||
|
||||
entities.forEach { entity ->
|
||||
if (entity.type == EntityType.BLOCK_DISPLAY && entity.persistentDataContainer.has(sectorKey, PersistentDataType.INTEGER)) {
|
||||
val bd = entity as BlockDisplay
|
||||
// Check if it's the command block
|
||||
if (bd.block.material == Material.COMMAND_BLOCK) {
|
||||
// Bukkit.getLogger().info("Rotater found target: ${bd.uniqueId}")
|
||||
updateRotation(bd)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateRotation(bd: BlockDisplay) {
|
||||
// Continuous rotation based on time: 1 full rotation every 4 seconds
|
||||
val periodMs = 4000L
|
||||
val time = System.currentTimeMillis()
|
||||
val phase = (time % periodMs).toDouble() / periodMs.toDouble()
|
||||
val angle = (phase * 2 * Math.PI).toFloat() // 0 to 2PI
|
||||
|
||||
// Log for debug (once per 100 ticks approx to avoid spam? No, just once to verify)
|
||||
// Bukkit.getLogger().info("Rotating BD: angle=$angle")
|
||||
|
||||
// Rotation around Y axis
|
||||
val rot = Quaternionf().rotateY(angle)
|
||||
|
||||
val centerOffset = Vector3f(0.25f, 0.25f, 0.25f) // The center in Scaled space
|
||||
// T = - (R * C)
|
||||
val rotatedOffset = Vector3f(centerOffset).rotate(rot)
|
||||
val newTranslation = Vector3f(rotatedOffset).negate()
|
||||
|
||||
val newTrans = Transformation(
|
||||
newTranslation,
|
||||
rot,
|
||||
scale,
|
||||
Quaternionf(0f, 0f, 0f, 1f)
|
||||
)
|
||||
|
||||
// Interpolation settings
|
||||
bd.interpolationDuration = 3 // Short duration to match high frequency update
|
||||
bd.interpolationDelay = -1 // Start immediately
|
||||
bd.transformation = newTrans
|
||||
}
|
||||
}
|
||||
|
|
@ -1,229 +0,0 @@
|
|||
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
|
||||
import org.bukkit.Bukkit
|
||||
import org.bukkit.Color
|
||||
import org.bukkit.Material
|
||||
import org.bukkit.NamespacedKey
|
||||
import org.bukkit.entity.Player
|
||||
import org.bukkit.inventory.EquipmentSlot
|
||||
import org.bukkit.persistence.PersistentDataType
|
||||
import org.bukkit.plugin.java.JavaPlugin
|
||||
import org.bukkit.scheduler.BukkitRunnable
|
||||
import kotlin.math.abs
|
||||
|
||||
class SelectionVisualizerTask(
|
||||
private val plugin: JavaPlugin,
|
||||
private val selectionService: SelectionService,
|
||||
private val sectorService: SectorService
|
||||
) : BukkitRunnable() {
|
||||
|
||||
override fun run() {
|
||||
for (player in Bukkit.getOnlinePlayers()) {
|
||||
visualize(player)
|
||||
}
|
||||
}
|
||||
|
||||
private fun visualize(player: Player) {
|
||||
val key = NamespacedKey(plugin, "component")
|
||||
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
|
||||
|
||||
var p2 = selection.point2
|
||||
if (p2 == null) {
|
||||
val target = player.getTargetBlockExact(30)
|
||||
if (target != null) {
|
||||
p2 = target.location
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
var minX: Double
|
||||
var minY: Double
|
||||
var minZ: Double
|
||||
var maxX: Double
|
||||
var maxY: Double
|
||||
var maxZ: Double
|
||||
|
||||
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()
|
||||
maxX = maxOf(p1.blockX, p2.blockX).toDouble() + 1.0
|
||||
maxY = maxOf(p1.blockY, p2.blockY).toDouble() + 1.0
|
||||
maxZ = maxOf(p1.blockZ, p2.blockZ).toDouble() + 1.0
|
||||
} else {
|
||||
// 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)
|
||||
|
||||
minX = (p1.blockX - dx).toDouble()
|
||||
maxX = (p1.blockX + dx).toDouble() + 1.0
|
||||
minY = (p1.blockY - dy).toDouble()
|
||||
maxY = (p1.blockY + dy).toDouble() + 1.0
|
||||
minZ = (p1.blockZ - dz).toDouble()
|
||||
maxZ = (p1.blockZ + dz).toDouble() + 1.0
|
||||
}
|
||||
|
||||
GeometryVisualizer.drawCuboid(player, minX, minY, minZ, maxX, maxY, maxZ)
|
||||
|
||||
} else {
|
||||
val centerX: 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
|
||||
val maxY: Double
|
||||
|
||||
val dx = p1.blockX - p2.blockX
|
||||
val dz = p1.blockZ - p2.blockZ
|
||||
val dist = Math.sqrt((dx * dx + dz * dz).toDouble())
|
||||
radius = kotlin.math.ceil(dist - 0.5)
|
||||
|
||||
if (!selection.isCenterMode) {
|
||||
// Normal: P1 is Center, Symmetric Height
|
||||
centerX = p1.blockX + 0.5
|
||||
centerY = p1.blockY + 0.5
|
||||
centerZ = p1.blockZ + 0.5
|
||||
|
||||
val hDiff = abs(p1.blockY - p2.blockY)
|
||||
val baseBlockY = p1.blockY - hDiff
|
||||
val topBlockY = p1.blockY + hDiff
|
||||
|
||||
minY = baseBlockY.toDouble()
|
||||
maxY = topBlockY.toDouble() + 1.0
|
||||
} else {
|
||||
// Center/Base Mode: P1 is Base, P2 defines Height/Radius
|
||||
centerX = p1.blockX + 0.5
|
||||
centerZ = p1.blockZ + 0.5
|
||||
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
|
||||
}
|
||||
|
||||
// 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>>()
|
||||
val actualRadius = radius + 0.5
|
||||
val radiusSq = actualRadius * actualRadius
|
||||
val rInt = actualRadius.toInt() + 1
|
||||
|
||||
for (dx in -rInt..rInt) {
|
||||
for (dz in -rInt..rInt) {
|
||||
if (dx * dx + dz * dz <= radiusSq) {
|
||||
blocks.add(Pair(cX + dx, cZ + dz))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val color = Color.fromRGB(100, 200, 255)
|
||||
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
|
||||
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
api-version: 1.21.10
|
||||
name: landsector
|
||||
version: 1.0-SNAPSHOT
|
||||
main: net.hareworks.hcu.landsector.LandSectorPlugin
|
||||
description: Land sector management plugin for HCU server
|
||||
authors:
|
||||
- Hare-K02
|
||||
dependencies:
|
||||
server:
|
||||
faction:
|
||||
load: BEFORE
|
||||
required: true
|
||||
join-classpath: true
|
||||
hcu-core:
|
||||
load: BEFORE
|
||||
required: true
|
||||
join-classpath: true
|
||||
lands:
|
||||
load: BEFORE
|
||||
required: true
|
||||
join-classpath: true
|
||||
|
|
@ -12,15 +12,22 @@ import org.bukkit.NamespacedKey
|
|||
import org.bukkit.entity.Player
|
||||
import org.bukkit.entity.Shulker
|
||||
import org.bukkit.inventory.ItemStack
|
||||
import org.bukkit.permissions.PermissionDefault
|
||||
import org.bukkit.persistence.PersistentDataType
|
||||
|
||||
class LandSectorCommand(private val landSectorPlugin: LandSectorPlugin) {
|
||||
fun register() {
|
||||
kommand(landSectorPlugin) {
|
||||
permissions {
|
||||
namespace = "landsector"
|
||||
rootSegment = "command"
|
||||
defaultDescription { ctx -> "Allows /${ctx.commandName} (${ctx.path.joinToString(" ")})" }
|
||||
}
|
||||
|
||||
command("landsector") {
|
||||
// Admin Commands
|
||||
literal("reload") {
|
||||
condition { it.isOp }
|
||||
permission { defaultValue = PermissionDefault.OP }
|
||||
executes {
|
||||
landSectorPlugin.reloadConfig()
|
||||
sender.sendMessage(Component.text("Configuration reloaded.", NamedTextColor.GREEN))
|
||||
|
|
@ -28,7 +35,7 @@ class LandSectorCommand(private val landSectorPlugin: LandSectorPlugin) {
|
|||
}
|
||||
|
||||
literal("give") {
|
||||
condition { it.isOp }
|
||||
permission { defaultValue = PermissionDefault.OP }
|
||||
executes {
|
||||
val player = sender as? Player ?: return@executes
|
||||
|
||||
|
|
@ -48,7 +55,7 @@ class LandSectorCommand(private val landSectorPlugin: LandSectorPlugin) {
|
|||
}
|
||||
|
||||
literal("list") {
|
||||
condition { it.isOp }
|
||||
permission { defaultValue = PermissionDefault.OP }
|
||||
executes {
|
||||
val player = sender as? Player ?: return@executes
|
||||
val service = landSectorPlugin.sectorService
|
||||
|
|
@ -91,7 +98,7 @@ class LandSectorCommand(private val landSectorPlugin: LandSectorPlugin) {
|
|||
}
|
||||
|
||||
literal("delete") {
|
||||
condition { it.isOp }
|
||||
permission { defaultValue = PermissionDefault.OP }
|
||||
integer("id") {
|
||||
executes {
|
||||
val id = argument<Int>("id")
|
||||
|
|
@ -141,6 +148,7 @@ class LandSectorCommand(private val landSectorPlugin: LandSectorPlugin) {
|
|||
|
||||
// User Operations
|
||||
literal("operation") {
|
||||
permission { defaultValue = PermissionDefault.TRUE }
|
||||
literal("givetool") {
|
||||
executes {
|
||||
val player = sender as? Player ?: return@executes
|
||||
|
|
|
|||
|
|
@ -121,12 +121,26 @@ class SelectionListener(
|
|||
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))
|
||||
|
||||
val modifierName = if (selection.mode == SelectionMode.CUBOID) {
|
||||
if (selection.isCenterMode) "Diagonal" else "Corner"
|
||||
} else {
|
||||
if (selection.isCenterMode) "Bottom" else "Center"
|
||||
}
|
||||
|
||||
player.sendMessage(Component.text("Selection Modifier: $modifierName", 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))
|
||||
|
||||
val modifierName = if (selection.mode == SelectionMode.CUBOID) {
|
||||
if (selection.isCenterMode) "Diagonal" else "Corner"
|
||||
} else {
|
||||
if (selection.isCenterMode) "Bottom" else "Center"
|
||||
}
|
||||
|
||||
player.sendMessage(Component.text("Shape: ${selection.mode} ($modifierName)", NamedTextColor.YELLOW))
|
||||
player.playSound(player.location, org.bukkit.Sound.ITEM_BOOK_PAGE_TURN, 1.0f, 1.0f)
|
||||
|
||||
// Reset selection on mode switch
|
||||
|
|
|
|||
|
|
@ -103,8 +103,9 @@ class SectorService(
|
|||
|
||||
// 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 height is even, prioritize upper side
|
||||
val yMin = sector.y - (coreHeight - 1) / 2
|
||||
val yMax = sector.y + coreHeight / 2
|
||||
|
||||
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)")
|
||||
|
|
@ -458,29 +459,25 @@ class SectorService(
|
|||
net.hareworks.hcu.lands.model.Shape.Cuboid(x1, y1, z1, x2, y2, z2)
|
||||
}
|
||||
SelectionMode.CYLINDER -> {
|
||||
// Convert bounds to cylinder params
|
||||
// Approximating from bounding box
|
||||
val minX = minOf(x1, x2)
|
||||
val maxX = maxOf(x1, x2)
|
||||
val minZ = minOf(z1, z2)
|
||||
val maxZ = maxOf(z1, z2)
|
||||
val minY = minOf(y1, y2)
|
||||
val maxY = maxOf(y1, y2)
|
||||
val dx = x2 - x1
|
||||
val dz = z2 - z1
|
||||
val dist = kotlin.math.sqrt((dx * dx + dz * dz).toDouble())
|
||||
val radius = kotlin.math.ceil(dist - 0.5)
|
||||
|
||||
val cx = (minX + maxX) / 2
|
||||
val cz = (minZ + maxZ) / 2
|
||||
val cy = (minY + maxY) / 2
|
||||
// isSneaking corresponds to isCenterMode
|
||||
if (isSneaking) {
|
||||
// Center/Base Mode: P1 is anchor, extends to P2 height
|
||||
// P1 (y1) is the base reference point
|
||||
val dy = y2 - y1
|
||||
val bottom = if (dy < 0) -dy else 0
|
||||
val top = if (dy > 0) dy else 0
|
||||
|
||||
// Radius is max dist from center to edge of bounding box in X or Z
|
||||
val radiusX = (maxX - minX) / 2.0
|
||||
val radiusZ = (maxZ - minZ) / 2.0
|
||||
val radius = maxOf(radiusX, radiusZ)
|
||||
|
||||
val height = maxY - minY + 1
|
||||
val bottom = (cy - minY)
|
||||
val top = (maxY - cy)
|
||||
|
||||
net.hareworks.hcu.lands.model.Shape.Cylinder(cx, cy, cz, radius, bottom, top)
|
||||
net.hareworks.hcu.lands.model.Shape.Cylinder(x1, y1, z1, radius, bottom, top)
|
||||
} else {
|
||||
// Corner/Symmetric Mode: P1 is Center, symmetric height to P2
|
||||
val dy = kotlin.math.abs(y2 - y1)
|
||||
net.hareworks.hcu.lands.model.Shape.Cylinder(x1, y1, z1, radius, dy, dy)
|
||||
}
|
||||
}
|
||||
else -> return null
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user