feat: 設置と削除・HP
This commit is contained in:
parent
5771e1b9d1
commit
ad4245d764
|
|
@ -59,10 +59,16 @@ class LandSectorPlugin : JavaPlugin() {
|
||||||
|
|
||||||
server.pluginManager.registerEvents(SectorListener(this, service, pIdService), this)
|
server.pluginManager.registerEvents(SectorListener(this, service, pIdService), this)
|
||||||
|
|
||||||
|
// Schedule auto-save every 5 minutes
|
||||||
|
server.scheduler.runTaskTimerAsynchronously(this, Runnable {
|
||||||
|
service.flushChanges()
|
||||||
|
}, 6000L, 6000L)
|
||||||
|
|
||||||
logger.info("LandSector initialized with services.")
|
logger.info("LandSector initialized with services.")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDisable() {
|
override fun onDisable() {
|
||||||
|
sectorService?.flushChanges()
|
||||||
logger.info("LandSector plugin has been disabled!")
|
logger.info("LandSector plugin has been disabled!")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,22 @@
|
||||||
package net.hareworks.hcu.landsector.command
|
package net.hareworks.hcu.landsector.command
|
||||||
|
|
||||||
import net.hareworks.hcu.landsector.LandSectorPlugin
|
import net.hareworks.hcu.landsector.LandSectorPlugin
|
||||||
|
import net.hareworks.hcu.landsector.model.Sector
|
||||||
import net.hareworks.kommand_lib.kommand
|
import net.hareworks.kommand_lib.kommand
|
||||||
import net.kyori.adventure.text.Component
|
import net.kyori.adventure.text.Component
|
||||||
import net.kyori.adventure.text.format.NamedTextColor
|
import net.kyori.adventure.text.format.NamedTextColor
|
||||||
|
import org.bukkit.Bukkit
|
||||||
|
import org.bukkit.Location
|
||||||
import org.bukkit.Material
|
import org.bukkit.Material
|
||||||
import org.bukkit.NamespacedKey
|
import org.bukkit.NamespacedKey
|
||||||
import org.bukkit.entity.Player
|
import org.bukkit.entity.Player
|
||||||
|
import org.bukkit.entity.Shulker
|
||||||
import org.bukkit.inventory.ItemStack
|
import org.bukkit.inventory.ItemStack
|
||||||
import org.bukkit.persistence.PersistentDataType
|
import org.bukkit.persistence.PersistentDataType
|
||||||
|
|
||||||
class LandSectorCommand(private val plugin: LandSectorPlugin) {
|
class LandSectorCommand(private val landSectorPlugin: LandSectorPlugin) {
|
||||||
fun register() {
|
fun register() {
|
||||||
kommand(plugin) {
|
kommand(landSectorPlugin) {
|
||||||
command("landsector") {
|
command("landsector") {
|
||||||
literal("give") {
|
literal("give") {
|
||||||
executes {
|
executes {
|
||||||
|
|
@ -22,7 +26,7 @@ class LandSectorCommand(private val plugin: LandSectorPlugin) {
|
||||||
val meta = item.itemMeta
|
val meta = item.itemMeta
|
||||||
meta.displayName(Component.text("Sector Core", NamedTextColor.LIGHT_PURPLE))
|
meta.displayName(Component.text("Sector Core", NamedTextColor.LIGHT_PURPLE))
|
||||||
meta.persistentDataContainer.set(
|
meta.persistentDataContainer.set(
|
||||||
NamespacedKey(plugin, "component"),
|
NamespacedKey(landSectorPlugin, "component"),
|
||||||
PersistentDataType.STRING,
|
PersistentDataType.STRING,
|
||||||
"sector_core"
|
"sector_core"
|
||||||
)
|
)
|
||||||
|
|
@ -32,6 +36,97 @@ class LandSectorCommand(private val plugin: LandSectorPlugin) {
|
||||||
sender.sendMessage(Component.text("Gave 1 Sector Core.", NamedTextColor.GREEN))
|
sender.sendMessage(Component.text("Gave 1 Sector Core.", NamedTextColor.GREEN))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
literal("list") {
|
||||||
|
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") {
|
||||||
|
integer("id") {
|
||||||
|
executes {
|
||||||
|
val id = argument<Int>("id")
|
||||||
|
val service = landSectorPlugin.sectorService
|
||||||
|
if (service == null) {
|
||||||
|
sender.sendMessage(Component.text("SectorService not ready.", NamedTextColor.RED))
|
||||||
|
return@executes
|
||||||
|
}
|
||||||
|
|
||||||
|
val sector = service.deleteSector(id)
|
||||||
|
if (sector == null) {
|
||||||
|
sender.sendMessage(Component.text("Sector #$id not found.", NamedTextColor.RED))
|
||||||
|
} else {
|
||||||
|
// Physical removal
|
||||||
|
val world = Bukkit.getWorld(sector.world)
|
||||||
|
if (world != null) {
|
||||||
|
val x = sector.x
|
||||||
|
val y = sector.y
|
||||||
|
val z = sector.z
|
||||||
|
|
||||||
|
val blockBase = world.getBlockAt(x, y - 1, z)
|
||||||
|
val blockTop = world.getBlockAt(x, y + 1, z)
|
||||||
|
|
||||||
|
if (blockBase.type == Material.BEDROCK) blockBase.type = Material.AIR
|
||||||
|
if (blockTop.type == Material.BEDROCK) blockTop.type = Material.AIR
|
||||||
|
|
||||||
|
// Remove Entity
|
||||||
|
val center = Location(world, x + 0.5, y.toDouble(), z + 0.5)
|
||||||
|
if (center.chunk.isLoaded) {
|
||||||
|
val entities = world.getNearbyEntities(center, 0.5, 0.5, 0.5)
|
||||||
|
entities.forEach { entity ->
|
||||||
|
if (entity is Shulker) {
|
||||||
|
val key = NamespacedKey(landSectorPlugin, "sector_id")
|
||||||
|
val sId = entity.persistentDataContainer.get(key, PersistentDataType.INTEGER)
|
||||||
|
if (sId == id || sId == null) {
|
||||||
|
entity.remove()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sender.sendMessage(Component.text("Sector #$id deleted.", NamedTextColor.GREEN))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ object SectorsTable : Table("land_sectors") {
|
||||||
val x = integer("x")
|
val x = integer("x")
|
||||||
val y = integer("y")
|
val y = integer("y")
|
||||||
val z = integer("z")
|
val z = integer("z")
|
||||||
|
val hp = integer("hp").default(1000)
|
||||||
|
|
||||||
override val primaryKey = PrimaryKey(id)
|
override val primaryKey = PrimaryKey(id)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,24 @@ import net.kyori.adventure.text.Component
|
||||||
import net.kyori.adventure.text.format.NamedTextColor
|
import net.kyori.adventure.text.format.NamedTextColor
|
||||||
import org.bukkit.Material
|
import org.bukkit.Material
|
||||||
import org.bukkit.NamespacedKey
|
import org.bukkit.NamespacedKey
|
||||||
|
import org.bukkit.Particle
|
||||||
|
import org.bukkit.Sound
|
||||||
import org.bukkit.entity.EntityType
|
import org.bukkit.entity.EntityType
|
||||||
import org.bukkit.entity.Shulker
|
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.BlockPlaceEvent
|
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.persistence.PersistentDataType
|
||||||
|
import org.bukkit.util.Transformation
|
||||||
|
import org.joml.Vector3f
|
||||||
|
import org.joml.Quaternionf
|
||||||
|
import org.bukkit.block.data.type.Slab
|
||||||
|
|
||||||
class SectorListener(
|
class SectorListener(
|
||||||
private val plugin: LandSectorPlugin,
|
private val plugin: LandSectorPlugin,
|
||||||
|
|
@ -52,22 +63,8 @@ class SectorListener(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Spawn Shulker
|
// Record to DB first to get ID
|
||||||
val shulkerLoc = loc.clone().add(0.5, 1.0, 0.5)
|
val sector = sectorService.createSector(
|
||||||
val shulker = player.world.spawnEntity(shulkerLoc, EntityType.SHULKER) as Shulker
|
|
||||||
shulker.setAI(false)
|
|
||||||
shulker.isInvulnerable = true
|
|
||||||
shulker.isInvisible = true
|
|
||||||
|
|
||||||
val param = shulker.getAttribute(Attribute.MAX_HEALTH)
|
|
||||||
param?.baseValue = 1000.0
|
|
||||||
shulker.health = 1000.0
|
|
||||||
|
|
||||||
// Place top bedrock
|
|
||||||
above2.type = Material.BEDROCK
|
|
||||||
|
|
||||||
// Record to DB (using Shulker pos as center)
|
|
||||||
sectorService.createSector(
|
|
||||||
playerEntry.actorId,
|
playerEntry.actorId,
|
||||||
player.world.name,
|
player.world.name,
|
||||||
above1.x,
|
above1.x,
|
||||||
|
|
@ -75,6 +72,240 @@ class SectorListener(
|
||||||
above1.z
|
above1.z
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (sector == null) {
|
||||||
|
player.sendMessage(Component.text("Failed to create sector record.", NamedTextColor.RED))
|
||||||
|
event.isCancelled = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create Visuals
|
||||||
|
val sectorKey = NamespacedKey(plugin, "sector_id")
|
||||||
|
val locCenter = loc.clone().add(0.5, 0.0, 0.5)
|
||||||
|
|
||||||
|
// 1. Command Block
|
||||||
|
val cbLoc = locCenter.clone().add(0.0, 1.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
|
||||||
|
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
|
||||||
|
val topCauldronLoc = locCenter.clone().add(0.0, 2.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
|
||||||
|
val botCauldronLoc = locCenter.clone().add(0.0, 0.6875, 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)
|
||||||
|
val shulkerLoc = loc.clone().add(0.5, 1.0, 0.5) // Y+1, sits in space 1-2.
|
||||||
|
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
|
||||||
|
block.type = Material.DEEPSLATE_TILE_SLAB
|
||||||
|
val bottomSlab = block.blockData as Slab
|
||||||
|
bottomSlab.type = Slab.Type.BOTTOM
|
||||||
|
block.blockData = bottomSlab
|
||||||
|
|
||||||
|
above2.type = Material.DEEPSLATE_TILE_SLAB
|
||||||
|
val topSlab = above2.blockData as Slab
|
||||||
|
topSlab.type = Slab.Type.TOP
|
||||||
|
above2.blockData = topSlab
|
||||||
|
|
||||||
player.sendMessage(Component.text("Sector Core placed!", NamedTextColor.GREEN))
|
player.sendMessage(Component.text("Sector Core placed!", NamedTextColor.GREEN))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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
|
||||||
|
|
||||||
|
val newHp = sectorService.reduceHealth(sectorId, damage.toInt())
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
// Let's reset health.
|
||||||
|
entity.health = maxHealth
|
||||||
|
entity.noDamageTicks = 0
|
||||||
|
|
||||||
|
if (newHp != null) {
|
||||||
|
val loc = entity.location
|
||||||
|
val world = entity.world
|
||||||
|
|
||||||
|
// Effects
|
||||||
|
world.playSound(loc, Sound.BLOCK_ANVIL_PLACE, 1.0f, 0.5f) // Heavy metallic sound
|
||||||
|
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) {
|
||||||
|
val player = event.damager as Player
|
||||||
|
val percent = newHp.toDouble() / maxHealth.toDouble()
|
||||||
|
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.
|
||||||
|
// 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 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))
|
||||||
|
|
||||||
|
// Maybe delete from DB or mark as destroyed?
|
||||||
|
// For now, valid destruction.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,5 +6,6 @@ data class Sector(
|
||||||
val world: String,
|
val world: String,
|
||||||
val x: Int,
|
val x: Int,
|
||||||
val y: Int,
|
val y: Int,
|
||||||
val z: Int
|
val z: Int,
|
||||||
|
val hp: Int
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,18 @@ import org.jetbrains.exposed.v1.jdbc.Database
|
||||||
import org.jetbrains.exposed.v1.jdbc.SchemaUtils
|
import org.jetbrains.exposed.v1.jdbc.SchemaUtils
|
||||||
import org.jetbrains.exposed.v1.jdbc.insert
|
import org.jetbrains.exposed.v1.jdbc.insert
|
||||||
import org.jetbrains.exposed.v1.jdbc.selectAll
|
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.andWhere
|
||||||
|
import org.jetbrains.exposed.v1.jdbc.deleteWhere
|
||||||
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
|
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
class SectorService(private val database: Database) {
|
class SectorService(private val database: Database) {
|
||||||
|
|
||||||
|
private val hpCache = ConcurrentHashMap<Int, Int>()
|
||||||
|
private val dirtySet = ConcurrentHashMap.newKeySet<Int>()
|
||||||
|
|
||||||
fun init() {
|
fun init() {
|
||||||
transaction(database) {
|
transaction(database) {
|
||||||
SchemaUtils.createMissingTablesAndColumns(SectorsTable)
|
SchemaUtils.createMissingTablesAndColumns(SectorsTable)
|
||||||
|
|
@ -26,9 +33,11 @@ class SectorService(private val database: Database) {
|
||||||
it[SectorsTable.x] = x
|
it[SectorsTable.x] = x
|
||||||
it[SectorsTable.y] = y
|
it[SectorsTable.y] = y
|
||||||
it[SectorsTable.z] = z
|
it[SectorsTable.z] = z
|
||||||
|
it[SectorsTable.hp] = 1000
|
||||||
}[SectorsTable.id]
|
}[SectorsTable.id]
|
||||||
|
|
||||||
Sector(id, ownerActorId, world, x, y, z)
|
hpCache[id] = 1000
|
||||||
|
Sector(id, ownerActorId, world, x, y, z, 1000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -46,9 +55,106 @@ class SectorService(private val database: Database) {
|
||||||
it[SectorsTable.world],
|
it[SectorsTable.world],
|
||||||
it[SectorsTable.x],
|
it[SectorsTable.x],
|
||||||
it[SectorsTable.y],
|
it[SectorsTable.y],
|
||||||
it[SectorsTable.z]
|
it[SectorsTable.z],
|
||||||
|
it[SectorsTable.hp]
|
||||||
)
|
)
|
||||||
}.singleOrNull()
|
}.singleOrNull()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun reduceHealth(id: Int, amount: Int): Int? {
|
||||||
|
// Try get from cache
|
||||||
|
var currentHp = hpCache[id]
|
||||||
|
|
||||||
|
// If not in cache, load from DB
|
||||||
|
if (currentHp == null) {
|
||||||
|
val sector = transaction(database) {
|
||||||
|
SectorsTable.selectAll().andWhere { SectorsTable.id eq id }.singleOrNull()
|
||||||
|
} ?: return null // Not found
|
||||||
|
|
||||||
|
currentHp = sector[SectorsTable.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
|
||||||
|
|
||||||
|
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]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sector != null) {
|
||||||
|
hpCache.remove(id)
|
||||||
|
dirtySet.remove(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sector
|
||||||
|
}
|
||||||
|
|
||||||
|
fun exists(id: Int): Boolean {
|
||||||
|
if (hpCache.containsKey(id)) return true
|
||||||
|
|
||||||
|
return transaction(database) {
|
||||||
|
SectorsTable.selectAll().andWhere { SectorsTable.id eq id }.count() > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAllSectors(world: String): List<Sector> {
|
||||||
|
return transaction(database) {
|
||||||
|
SectorsTable.selectAll()
|
||||||
|
.andWhere { SectorsTable.world eq world }
|
||||||
|
.map {
|
||||||
|
// Update HP from cache if exists
|
||||||
|
val id = it[SectorsTable.id]
|
||||||
|
val cachedHp = hpCache[id]
|
||||||
|
val hp = cachedHp ?: it[SectorsTable.hp]
|
||||||
|
|
||||||
|
Sector(
|
||||||
|
id,
|
||||||
|
it[SectorsTable.ownerActorId],
|
||||||
|
it[SectorsTable.world],
|
||||||
|
it[SectorsTable.x],
|
||||||
|
it[SectorsTable.y],
|
||||||
|
it[SectorsTable.z],
|
||||||
|
hp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user