feat: update dependencies

This commit is contained in:
Keisuke Hirata 2025-12-09 16:10:22 +09:00
parent 1c809b3def
commit e324a110e5
7 changed files with 189 additions and 26 deletions

@ -1 +1 @@
Subproject commit be85d83428eff52a749747fc495c4bda0e82d035 Subproject commit 5535c259494adcd33e659aae9a171c3ebf16e2cf

View File

@ -18,15 +18,14 @@ val exposedVersion = "1.0.0-rc-4"
dependencies { dependencies {
compileOnly("io.papermc.paper:paper-api:1.21.10-R0.1-SNAPSHOT") compileOnly("io.papermc.paper:paper-api:1.21.10-R0.1-SNAPSHOT")
compileOnly("org.jetbrains.kotlin:kotlin-stdlib") paperLibrary("org.jetbrains.kotlin:kotlin-stdlib:2.1.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0") { paperLibrary("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")
exclude(group = "org.jetbrains.kotlin")
}
compileOnly("com.michael-bull.kotlin-result:kotlin-result:2.1.0") compileOnly("com.michael-bull.kotlin-result:kotlin-result:2.1.0")
compileOnly("net.hareworks.hcu:hcu-core") compileOnly("net.hareworks.hcu:hcu-core")
compileOnly("net.hareworks:kommand-lib") compileOnly("net.hareworks:kommand-lib")
compileOnly("net.hareworks:permits-lib") compileOnly("net.hareworks:permits-lib")
compileOnly("net.hareworks:GhostDisplays")
compileOnly("net.kyori:adventure-api:4.25.0") compileOnly("net.kyori:adventure-api:4.25.0")
compileOnly("net.kyori:adventure-text-minimessage:4.25.0") compileOnly("net.kyori:adventure-text-minimessage:4.25.0")
@ -43,9 +42,10 @@ tasks {
} }
shadowJar { shadowJar {
archiveClassifier.set("min") archiveClassifier.set("min")
minimize() minimize {
dependencies { exclude(dependency("net.hareworks:GhostDisplays"))
exclude(dependency("org.jetbrains.kotlin:kotlin-stdlib")) exclude(dependency("net.hareworks:kommand-lib"))
exclude(dependency("net.hareworks:permits-lib"))
} }
} }
} }
@ -61,6 +61,10 @@ paper {
required = true required = true
load = PaperPluginDescription.RelativeLoadOrder.BEFORE load = PaperPluginDescription.RelativeLoadOrder.BEFORE
} }
register("GhostDisplays") {
required = true
load = PaperPluginDescription.RelativeLoadOrder.BEFORE
}
} }
authors = authors =
listOf( listOf(

@ -1 +1 @@
Subproject commit 520a378cd5c3da6321305d07cc0b402b73292add Subproject commit e2375c0876de9ba30be24af84d90443ea844a99b

View File

@ -1,4 +1,4 @@
rootProject.name = "faction" rootProject.name = "lands"
includeBuild("hcu-core") includeBuild("hcu-core")
includeBuild("hcu-core/kommand-lib") includeBuild("hcu-core/kommand-lib")
includeBuild("hcu-core/kommand-lib/permits-lib") includeBuild("hcu-core/kommand-lib/permits-lib")

View File

@ -58,6 +58,12 @@ class App : JavaPlugin() {
this.playerIdService = pIdService this.playerIdService = pIdService
this.actorIdentityService = actorService this.actorIdentityService = actorService
// Load DisplayService (optional dependency)
val displayService = server.servicesManager.load(net.hareworks.ghostdisplays.api.DisplayService::class.java)
if (displayService == null) {
logger.warning("DisplayService not found. Land name displays will not be shown.")
}
// Register public API as a Bukkit service // Register public API as a Bukkit service
val landsAPI = net.hareworks.hcu.lands.api.LandsAPIImpl(service) val landsAPI = net.hareworks.hcu.lands.api.LandsAPIImpl(service)
server.servicesManager.register( server.servicesManager.register(
@ -67,8 +73,10 @@ class App : JavaPlugin() {
org.bukkit.plugin.ServicePriority.Normal org.bukkit.plugin.ServicePriority.Normal
) )
// Run visualizer every 20 ticks (1 second) // Run visualizer every 10 ticks (0.5 seconds)
server.scheduler.runTaskTimer(this, VisualizerTask(service), 20L, 10L) val visualizerTask = VisualizerTask(service, displayService)
net.hareworks.hcu.lands.task.LandsVisualizer.setVisualizerTask(visualizerTask)
server.scheduler.runTaskTimer(this, visualizerTask, 20L, 10L)
logger.info("Lands plugin initialized successfully.") logger.info("Lands plugin initialized successfully.")
logger.info("LandsAPI registered as a Bukkit service.") logger.info("LandsAPI registered as a Bukkit service.")

View File

@ -99,6 +99,43 @@ data class Land(
return totalVolume return totalVolume
} }
fun getBoundingBox(): net.hareworks.hcu.lands.index.BoundingBox {
if (data.parts.isEmpty()) {
return net.hareworks.hcu.lands.index.BoundingBox(0, 0, 0, 0, 0, 0)
}
var minX = Int.MAX_VALUE
var minY = Int.MAX_VALUE
var minZ = Int.MAX_VALUE
var maxX = Int.MIN_VALUE
var maxY = Int.MIN_VALUE
var maxZ = Int.MIN_VALUE
data.parts.forEach { shape ->
when (shape) {
is Shape.Cuboid -> {
minX = minOf(minX, shape.minX())
maxX = maxOf(maxX, shape.maxX())
minY = minOf(minY, shape.minY())
maxY = maxOf(maxY, shape.maxY())
minZ = minOf(minZ, shape.minZ())
maxZ = maxOf(maxZ, shape.maxZ())
}
is Shape.Cylinder -> {
val r = (shape.radius + 0.5).toInt() + 1
minX = minOf(minX, shape.x - r)
maxX = maxOf(maxX, shape.x + r)
minY = minOf(minY, shape.y - shape.bottomHeight)
maxY = maxOf(maxY, shape.y + shape.topHeight)
minZ = minOf(minZ, shape.z - r)
maxZ = maxOf(maxZ, shape.z + r)
}
}
}
return net.hareworks.hcu.lands.index.BoundingBox(minX, minY, minZ, maxX, maxY, maxZ)
}
} }
@Serializable @Serializable

View File

@ -2,20 +2,32 @@ package net.hareworks.hcu.lands.task
import net.hareworks.hcu.lands.model.Shape import net.hareworks.hcu.lands.model.Shape
import net.hareworks.hcu.lands.service.LandService import net.hareworks.hcu.lands.service.LandService
import net.hareworks.ghostdisplays.api.DisplayController
import net.hareworks.ghostdisplays.api.DisplayService
import org.bukkit.Bukkit import org.bukkit.Bukkit
import org.bukkit.Color import org.bukkit.Color
import org.bukkit.Location
import org.bukkit.Particle import org.bukkit.Particle
import org.bukkit.entity.Player import org.bukkit.entity.Player
import org.bukkit.entity.TextDisplay
import java.util.UUID import java.util.UUID
import java.util.concurrent.ConcurrentHashMap
import kotlin.math.abs
import kotlin.math.cos import kotlin.math.cos
import kotlin.math.sin import kotlin.math.sin
object LandsVisualizer { object LandsVisualizer {
private val enabledPlayers = mutableSetOf<UUID>() private val enabledPlayers = mutableSetOf<UUID>()
private var visualizerTask: VisualizerTask? = null
fun setVisualizerTask(task: VisualizerTask) {
visualizerTask = task
}
fun toggle(player: Player) { fun toggle(player: Player) {
if (enabledPlayers.contains(player.uniqueId)) { if (enabledPlayers.contains(player.uniqueId)) {
enabledPlayers.remove(player.uniqueId) enabledPlayers.remove(player.uniqueId)
visualizerTask?.cleanupDisplaysForPlayer(player.uniqueId)
player.sendMessage("Visualization disabled.") player.sendMessage("Visualization disabled.")
} else { } else {
enabledPlayers.add(player.uniqueId) enabledPlayers.add(player.uniqueId)
@ -26,7 +38,12 @@ object LandsVisualizer {
fun isEnabled(player: Player) = enabledPlayers.contains(player.uniqueId) fun isEnabled(player: Player) = enabledPlayers.contains(player.uniqueId)
} }
class VisualizerTask(private val landService: LandService) : Runnable { class VisualizerTask(
private val landService: LandService,
private val displayService: DisplayService?
) : Runnable {
private val logger = org.bukkit.Bukkit.getLogger()
private val displayControllers = ConcurrentHashMap<UUID, MutableMap<Int, DisplayController<TextDisplay>>>()
override fun run() { override fun run() {
Bukkit.getOnlinePlayers().forEach { p -> Bukkit.getOnlinePlayers().forEach { p ->
if (LandsVisualizer.isEnabled(p)) { if (LandsVisualizer.isEnabled(p)) {
@ -43,6 +60,30 @@ class VisualizerTask(private val landService: LandService) : Runnable {
} }
} }
// Manage TextDisplays for land names
if (displayService != null) {
val activeDisplays = displayControllers.getOrPut(p.uniqueId) { mutableMapOf() }
val nearbyLandIds = nearbyLands.map { it.id }.toSet()
// Update displays for nearby lands
nearbyLands.forEach { land ->
val controller = activeDisplays.getOrPut(land.id) {
val newController = createDisplayForLand(p, land)
logger.info("[LandsDisplay] Created display for land '${land.name}' (ID: ${land.id}) for player ${p.name}")
newController
}
updateDisplayPosition(controller, p, land)
}
// Remove displays for lands no longer nearby
val toRemove = activeDisplays.keys.filter { it !in nearbyLandIds }
toRemove.forEach { landId ->
activeDisplays[landId]?.destroy()
activeDisplays.remove(landId)
logger.info("[LandsDisplay] Removed display for land ID $landId for player ${p.name} (out of range)")
}
}
nearbyLands.forEach { land -> nearbyLands.forEach { land ->
// Verify that this is the current version of the land // Verify that this is the current version of the land
val currentLand = landService.getLand(land.id) val currentLand = landService.getLand(land.id)
@ -162,19 +203,6 @@ class VisualizerTask(private val landService: LandService) : Runnable {
drawLine(player, x, bottomY, z, x, topY, z, edgeColor, 0.5) drawLine(player, x, bottomY, z, x, topY, z, edgeColor, 0.5)
} }
// Draw top face outline in red (outer edges only)
// Optimized: Calculate outline once for both faces as they share the same XZ shape
val faceOutline = cylinder.getOptimizedFaceOutline()
val redColor = Particle.DustOptions(Color.RED, 1.0f)
val topFaceY = cylinder.y + cylinder.topHeight
drawFaceOutline(player, faceOutline, topFaceY + 1.0, redColor, cylinder)
// Draw bottom face outline in red (outer edges only)
val bottomFaceY = cylinder.y - cylinder.bottomHeight
drawFaceOutline(player, faceOutline, bottomFaceY.toDouble(), redColor, cylinder)
} }
/** /**
@ -275,4 +303,90 @@ class VisualizerTask(private val landService: LandService) : Runnable {
player.spawnParticle(Particle.DUST, x, y, z, 1, 0.0, 0.0, 0.0, dustOptions) player.spawnParticle(Particle.DUST, x, y, z, 1, 0.0, 0.0, 0.0, dustOptions)
} }
} }
private fun createDisplayForLand(player: Player, land: net.hareworks.hcu.lands.model.Land): DisplayController<TextDisplay> {
val position = calculateFaceAttachmentPoint(player, land)
val controller = displayService!!.createTextDisplay(position)
controller.applyEntityUpdate { display ->
display.text(net.kyori.adventure.text.Component.text(land.name))
display.billboard = org.bukkit.entity.Display.Billboard.VERTICAL
}
controller.show(player)
logger.info("[LandsDisplay] Display for '${land.name}' made visible to ${player.name} at ${position.blockX}, ${position.blockY}, ${position.blockZ}")
return controller
}
private fun updateDisplayPosition(controller: DisplayController<TextDisplay>, player: Player, land: net.hareworks.hcu.lands.model.Land) {
val newPosition = calculateFaceAttachmentPoint(player, land)
val currentLoc = controller.display.location
// Only update if position changed significantly (>0.5 blocks)
if (currentLoc.distanceSquared(newPosition) > 0.25) {
controller.applyEntityUpdate { display ->
display.teleport(newPosition)
}
}
}
private fun calculateFaceAttachmentPoint(player: Player, land: net.hareworks.hcu.lands.model.Land): Location {
val bbox = land.getBoundingBox()
val px = player.location.x
val py = player.location.y
val pz = player.location.z
// Check if player is within XZ footprint
val withinX = px >= bbox.minX && px <= bbox.maxX + 1.0
val withinZ = pz >= bbox.minZ && pz <= bbox.maxZ + 1.0
if (withinX && withinZ) {
// Player is above or below the land → use top/bottom face
return if (py > bbox.maxY + 1.0) {
// Attach to top face
Location(
player.world,
px.coerceIn(bbox.minX.toDouble(), bbox.maxX + 1.0),
bbox.maxY + 1.1,
pz.coerceIn(bbox.minZ.toDouble(), bbox.maxZ + 1.0)
)
} else {
// Attach to bottom face
Location(
player.world,
px.coerceIn(bbox.minX.toDouble(), bbox.maxX + 1.0),
bbox.minY - 0.1,
pz.coerceIn(bbox.minZ.toDouble(), bbox.maxZ + 1.0)
)
}
}
// Player is outside XZ footprint → use nearest vertical face
val clampedX = px.coerceIn(bbox.minX.toDouble(), bbox.maxX + 1.0)
val clampedY = py.coerceIn(bbox.minY.toDouble(), bbox.maxY + 1.0)
val clampedZ = pz.coerceIn(bbox.minZ.toDouble(), bbox.maxZ + 1.0)
// Determine which face is nearest
val distToWest = abs(px - bbox.minX)
val distToEast = abs(px - (bbox.maxX + 1.0))
val distToNorth = abs(pz - bbox.minZ)
val distToSouth = abs(pz - (bbox.maxZ + 1.0))
val minDist = minOf(distToWest, distToEast, distToNorth, distToSouth)
return when (minDist) {
distToWest -> Location(player.world, bbox.minX - 0.1, clampedY, clampedZ)
distToEast -> Location(player.world, bbox.maxX + 1.1, clampedY, clampedZ)
distToNorth -> Location(player.world, clampedX, clampedY, bbox.minZ - 0.1)
else -> Location(player.world, clampedX, clampedY, bbox.maxZ + 1.1)
}
}
fun cleanupDisplaysForPlayer(playerId: UUID) {
val count = displayControllers[playerId]?.size ?: 0
displayControllers[playerId]?.values?.forEach { it.destroy() }
displayControllers.remove(playerId)
if (count > 0) {
val playerName = Bukkit.getPlayer(playerId)?.name ?: playerId.toString()
logger.info("[LandsDisplay] Cleaned up $count display(s) for player $playerName")
}
}
} }