diff --git a/.gitignore b/.gitignore index 6d58be0..fd62d2d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ build/ *.iml .idea/ out/ +bin \ No newline at end of file diff --git a/bin/main/net/hareworks/hcu/landsector/LandSectorPlugin.kt b/bin/main/net/hareworks/hcu/landsector/LandSectorPlugin.kt deleted file mode 100644 index a2abbc5..0000000 --- a/bin/main/net/hareworks/hcu/landsector/LandSectorPlugin.kt +++ /dev/null @@ -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!") - } -} diff --git a/bin/main/net/hareworks/hcu/landsector/command/LandSectorCommand.kt b/bin/main/net/hareworks/hcu/landsector/command/LandSectorCommand.kt deleted file mode 100644 index 8c91d6a..0000000 --- a/bin/main/net/hareworks/hcu/landsector/command/LandSectorCommand.kt +++ /dev/null @@ -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("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("sectorId") - giveTool(player, id) - } - } - } - - literal("activate") { - integer("sectorId") { - executes { - val id = argument("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("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("sectorId") - val targetActorId = argument("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("sectorId") - val rangeIndex = argument("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)) - } -} diff --git a/bin/main/net/hareworks/hcu/landsector/database/SectorDraftsTable.kt b/bin/main/net/hareworks/hcu/landsector/database/SectorDraftsTable.kt deleted file mode 100644 index 25ba3ed..0000000 --- a/bin/main/net/hareworks/hcu/landsector/database/SectorDraftsTable.kt +++ /dev/null @@ -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) -} diff --git a/bin/main/net/hareworks/hcu/landsector/database/SectorsTable.kt b/bin/main/net/hareworks/hcu/landsector/database/SectorsTable.kt deleted file mode 100644 index d304da9..0000000 --- a/bin/main/net/hareworks/hcu/landsector/database/SectorsTable.kt +++ /dev/null @@ -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) -} diff --git a/bin/main/net/hareworks/hcu/landsector/listener/SectorListener.kt b/bin/main/net/hareworks/hcu/landsector/listener/SectorListener.kt deleted file mode 100644 index 8396c59..0000000 --- a/bin/main/net/hareworks/hcu/landsector/listener/SectorListener.kt +++ /dev/null @@ -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") - } - }) - } - }) - } - } - } -} diff --git a/bin/main/net/hareworks/hcu/landsector/listener/SelectionListener.kt b/bin/main/net/hareworks/hcu/landsector/listener/SelectionListener.kt deleted file mode 100644 index e9ce31c..0000000 --- a/bin/main/net/hareworks/hcu/landsector/listener/SelectionListener.kt +++ /dev/null @@ -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 - } - } - } - } -} diff --git a/bin/main/net/hareworks/hcu/landsector/model/Sector.kt b/bin/main/net/hareworks/hcu/landsector/model/Sector.kt deleted file mode 100644 index 83499b9..0000000 --- a/bin/main/net/hareworks/hcu/landsector/model/Sector.kt +++ /dev/null @@ -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 -) diff --git a/bin/main/net/hareworks/hcu/landsector/model/SectorRange.kt b/bin/main/net/hareworks/hcu/landsector/model/SectorRange.kt deleted file mode 100644 index 292bb29..0000000 --- a/bin/main/net/hareworks/hcu/landsector/model/SectorRange.kt +++ /dev/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 -) diff --git a/bin/main/net/hareworks/hcu/landsector/model/SelectionData.kt b/bin/main/net/hareworks/hcu/landsector/model/SelectionData.kt deleted file mode 100644 index 7e2e840..0000000 --- a/bin/main/net/hareworks/hcu/landsector/model/SelectionData.kt +++ /dev/null @@ -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 -) diff --git a/bin/main/net/hareworks/hcu/landsector/service/SectorService.kt b/bin/main/net/hareworks/hcu/landsector/service/SectorService.kt deleted file mode 100644 index 05525da..0000000 --- a/bin/main/net/hareworks/hcu/landsector/service/SectorService.kt +++ /dev/null @@ -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) - - private val hpCache = ConcurrentHashMap() - private val dirtySet = ConcurrentHashMap.newKeySet() - private val sectorsCache = ConcurrentHashMap() - - // Draft storage: SectorId -> Land (Draft) - private val draftLands = ConcurrentHashMap() - - 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() - 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): 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, shapes2: List, 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 { - 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 { - 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 { - 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 { - 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 - } - } - } -} - - diff --git a/bin/main/net/hareworks/hcu/landsector/service/SelectionService.kt b/bin/main/net/hareworks/hcu/landsector/service/SelectionService.kt deleted file mode 100644 index f79b08f..0000000 --- a/bin/main/net/hareworks/hcu/landsector/service/SelectionService.kt +++ /dev/null @@ -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() - - 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 - } -} diff --git a/bin/main/net/hareworks/hcu/landsector/task/SectorRotationTask.kt b/bin/main/net/hareworks/hcu/landsector/task/SectorRotationTask.kt deleted file mode 100644 index 6d87210..0000000 --- a/bin/main/net/hareworks/hcu/landsector/task/SectorRotationTask.kt +++ /dev/null @@ -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 - } -} diff --git a/bin/main/net/hareworks/hcu/landsector/task/SelectionVisualizerTask.kt b/bin/main/net/hareworks/hcu/landsector/task/SelectionVisualizerTask.kt deleted file mode 100644 index 0074d36..0000000 --- a/bin/main/net/hareworks/hcu/landsector/task/SelectionVisualizerTask.kt +++ /dev/null @@ -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>() - 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 - diff --git a/bin/main/paper-plugin.yml b/bin/main/paper-plugin.yml deleted file mode 100644 index d92f321..0000000 --- a/bin/main/paper-plugin.yml +++ /dev/null @@ -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 diff --git a/src/main/kotlin/net/hareworks/hcu/landsector/command/LandSectorCommand.kt b/src/main/kotlin/net/hareworks/hcu/landsector/command/LandSectorCommand.kt index 8c91d6a..6781289 100644 --- a/src/main/kotlin/net/hareworks/hcu/landsector/command/LandSectorCommand.kt +++ b/src/main/kotlin/net/hareworks/hcu/landsector/command/LandSectorCommand.kt @@ -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("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 diff --git a/src/main/kotlin/net/hareworks/hcu/landsector/listener/SelectionListener.kt b/src/main/kotlin/net/hareworks/hcu/landsector/listener/SelectionListener.kt index e9ce31c..db389c5 100644 --- a/src/main/kotlin/net/hareworks/hcu/landsector/listener/SelectionListener.kt +++ b/src/main/kotlin/net/hareworks/hcu/landsector/listener/SelectionListener.kt @@ -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 diff --git a/src/main/kotlin/net/hareworks/hcu/landsector/service/SectorService.kt b/src/main/kotlin/net/hareworks/hcu/landsector/service/SectorService.kt index 05525da..2c8aec6 100644 --- a/src/main/kotlin/net/hareworks/hcu/landsector/service/SectorService.kt +++ b/src/main/kotlin/net/hareworks/hcu/landsector/service/SectorService.kt @@ -102,9 +102,10 @@ class SectorService( 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 + // Center: sector.x, sector.y, sector.z. + // 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 - - // 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) + // 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 + + 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 }