feat: 回転と破壊・設置
This commit is contained in:
parent
ad4245d764
commit
af4008ee45
|
|
@ -64,6 +64,9 @@ class LandSectorPlugin : JavaPlugin() {
|
||||||
service.flushChanges()
|
service.flushChanges()
|
||||||
}, 6000L, 6000L)
|
}, 6000L, 6000L)
|
||||||
|
|
||||||
|
// Schedule rotation task
|
||||||
|
net.hareworks.hcu.landsector.task.SectorRotationTask(this).runTaskTimer(this, 1L, 1L)
|
||||||
|
|
||||||
logger.info("LandSector initialized with services.")
|
logger.info("LandSector initialized with services.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -103,18 +103,17 @@ class LandSectorCommand(private val landSectorPlugin: LandSectorPlugin) {
|
||||||
val blockBase = world.getBlockAt(x, y - 1, z)
|
val blockBase = world.getBlockAt(x, y - 1, z)
|
||||||
val blockTop = world.getBlockAt(x, y + 1, z)
|
val blockTop = world.getBlockAt(x, y + 1, z)
|
||||||
|
|
||||||
if (blockBase.type == Material.BEDROCK) blockBase.type = Material.AIR
|
if (blockBase.type == Material.DEEPSLATE_TILE_SLAB) blockBase.type = Material.AIR
|
||||||
if (blockTop.type == Material.BEDROCK) blockTop.type = Material.AIR
|
if (blockTop.type == Material.DEEPSLATE_TILE_SLAB) blockTop.type = Material.AIR
|
||||||
|
|
||||||
// Remove Entity
|
// Remove All Entities with this sector ID
|
||||||
val center = Location(world, x + 0.5, y.toDouble(), z + 0.5)
|
val center = Location(world, x + 0.5, y.toDouble(), z + 0.5)
|
||||||
if (center.chunk.isLoaded) {
|
if (center.chunk.isLoaded) {
|
||||||
val entities = world.getNearbyEntities(center, 0.5, 0.5, 0.5)
|
val sectorKey = NamespacedKey(landSectorPlugin, "sector_id")
|
||||||
entities.forEach { entity ->
|
center.chunk.entities.forEach { entity ->
|
||||||
if (entity is Shulker) {
|
if (entity.persistentDataContainer.has(sectorKey, PersistentDataType.INTEGER)) {
|
||||||
val key = NamespacedKey(landSectorPlugin, "sector_id")
|
val sId = entity.persistentDataContainer.get(sectorKey, PersistentDataType.INTEGER)
|
||||||
val sId = entity.persistentDataContainer.get(key, PersistentDataType.INTEGER)
|
if (sId == id) {
|
||||||
if (sId == id || sId == null) {
|
|
||||||
entity.remove()
|
entity.remove()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import org.bukkit.entity.Shulker
|
||||||
import org.bukkit.attribute.Attribute
|
import org.bukkit.attribute.Attribute
|
||||||
import org.bukkit.event.EventHandler
|
import org.bukkit.event.EventHandler
|
||||||
import org.bukkit.event.Listener
|
import org.bukkit.event.Listener
|
||||||
|
import org.bukkit.event.block.BlockBreakEvent
|
||||||
import org.bukkit.event.block.BlockPlaceEvent
|
import org.bukkit.event.block.BlockPlaceEvent
|
||||||
import org.bukkit.event.entity.EntityDamageEvent
|
import org.bukkit.event.entity.EntityDamageEvent
|
||||||
import org.bukkit.event.entity.EntityDamageByEntityEvent
|
import org.bukkit.event.entity.EntityDamageByEntityEvent
|
||||||
|
|
@ -32,8 +33,31 @@ class SectorListener(
|
||||||
private val playerIdService: PlayerIdService
|
private val playerIdService: PlayerIdService
|
||||||
) : Listener {
|
) : 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
|
@EventHandler
|
||||||
fun onPlace(event: BlockPlaceEvent) {
|
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 item = event.itemInHand
|
||||||
val meta = item.itemMeta ?: return
|
val meta = item.itemMeta ?: return
|
||||||
val key = NamespacedKey(plugin, "component")
|
val key = NamespacedKey(plugin, "component")
|
||||||
|
|
@ -42,7 +66,6 @@ class SectorListener(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val player = event.player
|
|
||||||
val playerEntry = playerIdService.find(player.uniqueId)
|
val playerEntry = playerIdService.find(player.uniqueId)
|
||||||
if (playerEntry == null) {
|
if (playerEntry == null) {
|
||||||
player.sendMessage(Component.text("Identity not found.", NamedTextColor.RED))
|
player.sendMessage(Component.text("Identity not found.", NamedTextColor.RED))
|
||||||
|
|
@ -50,40 +73,47 @@ class SectorListener(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val block = event.blockPlaced
|
// ... (rest of the code)
|
||||||
val loc = block.location
|
|
||||||
|
|
||||||
// Space check
|
// Check 3x3x3 space availability (relative to placed block base)
|
||||||
val above1 = block.getRelative(0, 1, 0)
|
val baseLoc = block.location
|
||||||
val above2 = block.getRelative(0, 2, 0)
|
for (dx in -1..1) {
|
||||||
|
for (dy in 0..2) {
|
||||||
if (!above1.type.isAir || !above2.type.isAir) {
|
for (dz in -1..1) {
|
||||||
player.sendMessage(Component.text("Not enough space for Sector Core.", NamedTextColor.RED))
|
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
|
event.isCancelled = true
|
||||||
return
|
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
|
||||||
|
|
||||||
// Record to DB first to get ID
|
|
||||||
val sector = sectorService.createSector(
|
val sector = sectorService.createSector(
|
||||||
playerEntry.actorId,
|
playerEntry.actorId,
|
||||||
player.world.name,
|
player.world.name,
|
||||||
above1.x,
|
centerLoc.blockX,
|
||||||
above1.y,
|
centerLoc.blockY,
|
||||||
above1.z
|
centerLoc.blockZ
|
||||||
)
|
)
|
||||||
|
|
||||||
if (sector == null) {
|
if (sector == null) {
|
||||||
player.sendMessage(Component.text("Failed to create sector record.", NamedTextColor.RED))
|
player.sendMessage(Component.text("Failed to create sector.", NamedTextColor.RED))
|
||||||
event.isCancelled = true
|
event.isCancelled = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create Visuals
|
// Create Visuals
|
||||||
val sectorKey = NamespacedKey(plugin, "sector_id")
|
val sectorKey = NamespacedKey(plugin, "sector_id")
|
||||||
val locCenter = loc.clone().add(0.5, 0.0, 0.5)
|
val visualCenter = centerLoc.clone().add(0.5, 0.0, 0.5) // Center of the block space
|
||||||
|
|
||||||
// 1. Command Block
|
// 1. Command Block (Center + 0.5y) -> Matches old base+1.5
|
||||||
val cbLoc = locCenter.clone().add(0.0, 1.5, 0.0)
|
val cbLoc = visualCenter.clone().add(0.0, 0.5, 0.0)
|
||||||
val cb = player.world.spawnEntity(cbLoc, EntityType.BLOCK_DISPLAY) as BlockDisplay
|
val cb = player.world.spawnEntity(cbLoc, EntityType.BLOCK_DISPLAY) as BlockDisplay
|
||||||
cb.block = Material.COMMAND_BLOCK.createBlockData {
|
cb.block = Material.COMMAND_BLOCK.createBlockData {
|
||||||
(it as org.bukkit.block.data.Directional).facing = org.bukkit.block.BlockFace.UP
|
(it as org.bukkit.block.data.Directional).facing = org.bukkit.block.BlockFace.UP
|
||||||
|
|
@ -96,7 +126,7 @@ class SectorListener(
|
||||||
)
|
)
|
||||||
cb.persistentDataContainer.set(sectorKey, PersistentDataType.INTEGER, sector.id)
|
cb.persistentDataContainer.set(sectorKey, PersistentDataType.INTEGER, sector.id)
|
||||||
|
|
||||||
// 2. Tinted Glass
|
// 2. Tinted Glass (Center + 0.5y) -> Matches old base+1.5
|
||||||
val glass = player.world.spawnEntity(cbLoc, EntityType.BLOCK_DISPLAY) as BlockDisplay
|
val glass = player.world.spawnEntity(cbLoc, EntityType.BLOCK_DISPLAY) as BlockDisplay
|
||||||
glass.block = Material.TINTED_GLASS.createBlockData()
|
glass.block = Material.TINTED_GLASS.createBlockData()
|
||||||
glass.transformation = Transformation(
|
glass.transformation = Transformation(
|
||||||
|
|
@ -107,8 +137,8 @@ class SectorListener(
|
||||||
)
|
)
|
||||||
glass.persistentDataContainer.set(sectorKey, PersistentDataType.INTEGER, sector.id)
|
glass.persistentDataContainer.set(sectorKey, PersistentDataType.INTEGER, sector.id)
|
||||||
|
|
||||||
// 3. Top Cauldron
|
// 3. Top Cauldron (Center + 1.3125y) -> Matches old base+2.3125
|
||||||
val topCauldronLoc = locCenter.clone().add(0.0, 2.3125, 0.0)
|
val topCauldronLoc = visualCenter.clone().add(0.0, 1.3125, 0.0)
|
||||||
val topCauldron = player.world.spawnEntity(topCauldronLoc, EntityType.BLOCK_DISPLAY) as BlockDisplay
|
val topCauldron = player.world.spawnEntity(topCauldronLoc, EntityType.BLOCK_DISPLAY) as BlockDisplay
|
||||||
topCauldron.block = Material.CAULDRON.createBlockData()
|
topCauldron.block = Material.CAULDRON.createBlockData()
|
||||||
topCauldron.transformation = Transformation(
|
topCauldron.transformation = Transformation(
|
||||||
|
|
@ -119,8 +149,8 @@ class SectorListener(
|
||||||
)
|
)
|
||||||
topCauldron.persistentDataContainer.set(sectorKey, PersistentDataType.INTEGER, sector.id)
|
topCauldron.persistentDataContainer.set(sectorKey, PersistentDataType.INTEGER, sector.id)
|
||||||
|
|
||||||
// 4. Bottom Cauldron
|
// 4. Bottom Cauldron (Center - 0.3125y) -> Matches old base+0.6875
|
||||||
val botCauldronLoc = locCenter.clone().add(0.0, 0.6875, 0.0)
|
val botCauldronLoc = visualCenter.clone().add(0.0, -0.3125, 0.0)
|
||||||
val botCauldron = player.world.spawnEntity(botCauldronLoc, EntityType.BLOCK_DISPLAY) as BlockDisplay
|
val botCauldron = player.world.spawnEntity(botCauldronLoc, EntityType.BLOCK_DISPLAY) as BlockDisplay
|
||||||
botCauldron.block = Material.CAULDRON.createBlockData()
|
botCauldron.block = Material.CAULDRON.createBlockData()
|
||||||
botCauldron.transformation = Transformation(
|
botCauldron.transformation = Transformation(
|
||||||
|
|
@ -131,8 +161,8 @@ class SectorListener(
|
||||||
)
|
)
|
||||||
botCauldron.persistentDataContainer.set(sectorKey, PersistentDataType.INTEGER, sector.id)
|
botCauldron.persistentDataContainer.set(sectorKey, PersistentDataType.INTEGER, sector.id)
|
||||||
|
|
||||||
// Spawn Shulker (Hitbox)
|
// Spawn Shulker (Hitbox) at Center
|
||||||
val shulkerLoc = loc.clone().add(0.5, 1.0, 0.5) // Y+1, sits in space 1-2.
|
val shulkerLoc = visualCenter.clone() // Center is exactly y+1 from base
|
||||||
val shulker = player.world.spawnEntity(shulkerLoc, EntityType.SHULKER) as Shulker
|
val shulker = player.world.spawnEntity(shulkerLoc, EntityType.SHULKER) as Shulker
|
||||||
shulker.setAI(false)
|
shulker.setAI(false)
|
||||||
shulker.isInvisible = true
|
shulker.isInvisible = true
|
||||||
|
|
@ -146,16 +176,20 @@ class SectorListener(
|
||||||
// Tag Shulker with Sector ID
|
// Tag Shulker with Sector ID
|
||||||
shulker.persistentDataContainer.set(sectorKey, PersistentDataType.INTEGER, sector.id)
|
shulker.persistentDataContainer.set(sectorKey, PersistentDataType.INTEGER, sector.id)
|
||||||
|
|
||||||
// Place Blocks
|
// 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
|
block.type = Material.DEEPSLATE_TILE_SLAB
|
||||||
val bottomSlab = block.blockData as Slab
|
val bottomSlab = block.blockData as Slab
|
||||||
bottomSlab.type = Slab.Type.BOTTOM
|
bottomSlab.type = Slab.Type.BOTTOM
|
||||||
block.blockData = bottomSlab
|
block.blockData = bottomSlab
|
||||||
|
|
||||||
above2.type = Material.DEEPSLATE_TILE_SLAB
|
val topBlock = centerLoc.clone().add(0.0, 1.0, 0.0).block
|
||||||
val topSlab = above2.blockData as Slab
|
topBlock.type = Material.DEEPSLATE_TILE_SLAB
|
||||||
|
val topSlab = topBlock.blockData as Slab
|
||||||
topSlab.type = Slab.Type.TOP
|
topSlab.type = Slab.Type.TOP
|
||||||
above2.blockData = topSlab
|
topBlock.blockData = topSlab
|
||||||
|
|
||||||
player.sendMessage(Component.text("Sector Core placed!", NamedTextColor.GREEN))
|
player.sendMessage(Component.text("Sector Core placed!", NamedTextColor.GREEN))
|
||||||
}
|
}
|
||||||
|
|
@ -175,28 +209,36 @@ class SectorListener(
|
||||||
val damage = event.finalDamage
|
val damage = event.finalDamage
|
||||||
val maxHealth = entity.getAttribute(Attribute.MAX_HEALTH)?.value ?: 1000.0
|
val maxHealth = entity.getAttribute(Attribute.MAX_HEALTH)?.value ?: 1000.0
|
||||||
|
|
||||||
val newHp = sectorService.reduceHealth(sectorId, damage.toInt())
|
|
||||||
|
|
||||||
// Cancel the event so it doesn't actually die in vanilla terms,
|
// Cancel the event so it doesn't actually die in vanilla terms,
|
||||||
// OR let it happen but force health reset.
|
// 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
|
// 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.
|
// if we want the effect. But resetting health is robust.
|
||||||
// Let's reset health.
|
val oldHp = sectorService.getHealth(sectorId) ?: return // Get current health from service
|
||||||
|
val newHp = sectorService.reduceHealth(sectorId, damage.toInt())
|
||||||
|
|
||||||
entity.health = maxHealth
|
entity.health = maxHealth
|
||||||
entity.noDamageTicks = 0
|
entity.noDamageTicks = 0 // Disable invulnerability
|
||||||
|
|
||||||
if (newHp != null) {
|
if (newHp != null) {
|
||||||
val loc = entity.location
|
val loc = entity.location
|
||||||
val world = entity.world
|
val world = entity.world
|
||||||
|
|
||||||
// Effects
|
// Sound Logic
|
||||||
world.playSound(loc, Sound.BLOCK_ANVIL_PLACE, 1.0f, 0.5f) // Heavy metallic sound
|
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())
|
world.spawnParticle(Particle.BLOCK, loc.add(0.0, 0.5, 0.0), 20, 0.3, 0.3, 0.3, Material.BEDROCK.createBlockData())
|
||||||
|
|
||||||
// Action Bar Display
|
|
||||||
if (event is EntityDamageByEntityEvent && event.damager is Player) {
|
if (event is EntityDamageByEntityEvent && event.damager is Player) {
|
||||||
val player = event.damager as Player
|
val player = event.damager as Player
|
||||||
val percent = newHp.toDouble() / maxHealth.toDouble()
|
|
||||||
|
val percent = newHp.toDouble() / maxHealth
|
||||||
val color = when {
|
val color = when {
|
||||||
percent > 0.5 -> NamedTextColor.GREEN
|
percent > 0.5 -> NamedTextColor.GREEN
|
||||||
percent > 0.2 -> NamedTextColor.YELLOW
|
percent > 0.2 -> NamedTextColor.YELLOW
|
||||||
|
|
@ -229,10 +271,6 @@ class SectorListener(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove blocks.
|
// Remove blocks.
|
||||||
// We know Shulker is at x, y, z.
|
|
||||||
// Base is y-1, Top is y+1
|
|
||||||
val loc = entity.location
|
|
||||||
val world = entity.world
|
|
||||||
val x = loc.blockX
|
val x = loc.blockX
|
||||||
val y = loc.blockY
|
val y = loc.blockY
|
||||||
val z = loc.blockZ
|
val z = loc.blockZ
|
||||||
|
|
@ -244,9 +282,6 @@ class SectorListener(
|
||||||
if (top.type == Material.DEEPSLATE_TILE_SLAB) top.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))
|
world.dropItemNaturally(loc, org.bukkit.inventory.ItemStack(Material.DEEPSLATE_TILE_SLAB, 2))
|
||||||
|
|
||||||
// Maybe delete from DB or mark as destroyed?
|
|
||||||
// For now, valid destruction.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,24 @@ class SectorService(private val database: Database) {
|
||||||
|
|
||||||
private val hpCache = ConcurrentHashMap<Int, Int>()
|
private val hpCache = ConcurrentHashMap<Int, Int>()
|
||||||
private val dirtySet = ConcurrentHashMap.newKeySet<Int>()
|
private val dirtySet = ConcurrentHashMap.newKeySet<Int>()
|
||||||
|
private val sectorsCache = ConcurrentHashMap<Int, Sector>()
|
||||||
|
|
||||||
fun init() {
|
fun init() {
|
||||||
transaction(database) {
|
transaction(database) {
|
||||||
SchemaUtils.createMissingTablesAndColumns(SectorsTable)
|
SchemaUtils.createMissingTablesAndColumns(SectorsTable)
|
||||||
|
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]
|
||||||
|
)
|
||||||
|
sectorsCache[id] = sector
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -37,42 +51,41 @@ class SectorService(private val database: Database) {
|
||||||
}[SectorsTable.id]
|
}[SectorsTable.id]
|
||||||
|
|
||||||
hpCache[id] = 1000
|
hpCache[id] = 1000
|
||||||
Sector(id, ownerActorId, world, x, y, z, 1000)
|
val sector = Sector(id, ownerActorId, world, x, y, z, 1000)
|
||||||
|
sectorsCache[id] = sector
|
||||||
|
sector
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSectorAt(world: String, x: Int, y: Int, z: Int): Sector? {
|
fun getSectorAt(world: String, x: Int, y: Int, z: Int): Sector? {
|
||||||
return transaction(database) {
|
// Optimized to use cache
|
||||||
SectorsTable.selectAll()
|
return sectorsCache.values.firstOrNull {
|
||||||
.andWhere { SectorsTable.world eq world }
|
it.world == world && it.x == x && it.y == y && it.z == z
|
||||||
.andWhere { SectorsTable.x eq x }
|
|
||||||
.andWhere { SectorsTable.y eq y }
|
|
||||||
.andWhere { SectorsTable.z eq z }
|
|
||||||
.map {
|
|
||||||
Sector(
|
|
||||||
it[SectorsTable.id],
|
|
||||||
it[SectorsTable.ownerActorId],
|
|
||||||
it[SectorsTable.world],
|
|
||||||
it[SectorsTable.x],
|
|
||||||
it[SectorsTable.y],
|
|
||||||
it[SectorsTable.z],
|
|
||||||
it[SectorsTable.hp]
|
|
||||||
)
|
|
||||||
}.singleOrNull()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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? {
|
fun reduceHealth(id: Int, amount: Int): Int? {
|
||||||
// Try get from cache
|
// Try get from cache
|
||||||
var currentHp = hpCache[id]
|
var currentHp = hpCache[id]
|
||||||
|
|
||||||
// If not in cache, load from DB
|
// If not in cache, try to init from sectorsCache
|
||||||
if (currentHp == null) {
|
if (currentHp == null) {
|
||||||
val sector = transaction(database) {
|
val sector = sectorsCache[id] ?: return null
|
||||||
SectorsTable.selectAll().andWhere { SectorsTable.id eq id }.singleOrNull()
|
currentHp = sector.hp
|
||||||
} ?: return null // Not found
|
|
||||||
|
|
||||||
currentHp = sector[SectorsTable.hp]
|
|
||||||
hpCache[id] = currentHp
|
hpCache[id] = currentHp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -122,11 +135,33 @@ class SectorService(private val database: Database) {
|
||||||
if (sector != null) {
|
if (sector != null) {
|
||||||
hpCache.remove(id)
|
hpCache.remove(id)
|
||||||
dirtySet.remove(id)
|
dirtySet.remove(id)
|
||||||
|
sectorsCache.remove(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
return sector
|
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 {
|
fun exists(id: Int): Boolean {
|
||||||
if (hpCache.containsKey(id)) return true
|
if (hpCache.containsKey(id)) return true
|
||||||
|
|
||||||
|
|
@ -135,6 +170,25 @@ class SectorService(private val database: Database) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getAllSectors(world: String): List<Sector> {
|
fun getAllSectors(world: String): List<Sector> {
|
||||||
return transaction(database) {
|
return transaction(database) {
|
||||||
SectorsTable.selectAll()
|
SectorsTable.selectAll()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user