diff --git a/CHANGELOG.md b/CHANGELOG.md index 21b57c7..a3079b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## [Unreleased] +### Added +- GH-22: Always on healthbars for NAME or SCOREBOARD via special-case `always` duration (@tajobe) + ## [0.4.0] - 2025-02-17 ### Changed - MC 1.21, Kotlin 2.1, Gradle 8.12 (@tajobe) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d9fbee2..6514f91 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/src/main/kotlin/org/simplemc/simplehealthbars2/DamageListener.kt b/src/main/kotlin/org/simplemc/simplehealthbars2/DamageListener.kt index 2f652ff..2822bab 100644 --- a/src/main/kotlin/org/simplemc/simplehealthbars2/DamageListener.kt +++ b/src/main/kotlin/org/simplemc/simplehealthbars2/DamageListener.kt @@ -8,7 +8,11 @@ import org.bukkit.event.EventPriority import org.bukkit.event.Listener import org.bukkit.event.entity.EntityDamageByEntityEvent import org.bukkit.event.entity.EntityDeathEvent +import org.bukkit.event.entity.EntitySpawnEvent +import org.bukkit.event.player.PlayerJoinEvent +import org.bukkit.event.player.PlayerQuitEvent import org.bukkit.plugin.Plugin +import org.simplemc.simplehealthbars2.healthbar.Healthbar import org.simplemc.simplehealthbars2.healthbar.MobHealthbar import org.simplemc.simplehealthbars2.healthbar.PlayerHealthbar import java.util.UUID @@ -18,13 +22,34 @@ class DamageListener( private val playerHealthbars: Map, private val mobHealthbars: Map, ) : Listener, AutoCloseable { - private data class RemoveHealthbarTask(val taskId: Int, val task: () -> Unit) + private data class RemoveHealthbarTask(val taskId: Int?, val removeAction: () -> Unit) private val scheduler = Bukkit.getScheduler() private val removeHealthbarTasks: MutableMap = mutableMapOf() + // @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) - fun onEntityDamageEvent(event: EntityDamageByEntityEvent) { + fun onEntitySpawn(event: EntitySpawnEvent) { + val entity = event.entity as? LivingEntity + entity?.healthbar?.let { healthbar -> + if (healthbar.durationTicks == null) { + healthbar(null, entity, 0.0) + } + } + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) + fun onPlayerJoin(event: PlayerJoinEvent) { + event.player.healthbar?.let { healthbar -> + if (healthbar.durationTicks == null) { + healthbar(null, event.player, 0.0) + } + } + } + // + + @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) + fun onEntityDamageByEntityEvent(event: EntityDamageByEntityEvent) { val target = event.entity as? LivingEntity ?: return val source = event.damager as? LivingEntity @@ -33,33 +58,39 @@ class DamageListener( source?.let { healthbar(target, it, 0.0) } } + @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) + fun onPlayerQuit(event: PlayerQuitEvent) = clearEntityHealthbar(event.player) + /** * Remove the healthbar from dying entities immediately */ @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) - fun onEntityDeathEvent(event: EntityDeathEvent) { - removeHealthbarTasks[event.entity.uniqueId]?.let { - scheduler.cancelTask(it.taskId) - it.task() - } - } + fun onEntityDeathEvent(event: EntityDeathEvent) = clearEntityHealthbar(event.entity) private fun healthbar(source: LivingEntity?, target: LivingEntity, damage: Double) { // cancel scheduled healthbar removal and run it now to prepare for new (updated) healthbar - removeHealthbarTasks[target.uniqueId]?.let { - scheduler.cancelTask(it.taskId) - it.task() + clearEntityHealthbar(target) + + // update healthbar and schedule its removal task if necessary + val healthbar = target.healthbar + healthbar?.updateHealth(source, target, damage)?.let { removeAction -> + val taskId = healthbar.durationTicks?.let { ticks -> + scheduler.scheduleSyncDelayedTask(plugin, removeAction, ticks) + } + removeHealthbarTasks[target.uniqueId] = RemoveHealthbarTask(taskId, removeAction) } + } - val healthbar = when (target) { - is Player -> playerHealthbars[target.world.name] ?: playerHealthbars[null] - else -> mobHealthbars[target.world.name] ?: mobHealthbars[null] + private val LivingEntity.healthbar: Healthbar? + get(): Healthbar? = when (this) { + is Player -> playerHealthbars[world.name] ?: playerHealthbars[null] + else -> mobHealthbars[world.name] ?: mobHealthbars[null] } - // update healthbar and schedule its removal task if available - healthbar?.updateHealth(source, target, damage)?.let { - val taskId = scheduler.scheduleSyncDelayedTask(plugin, it, healthbar.durationTicks) - removeHealthbarTasks[target.uniqueId] = RemoveHealthbarTask(taskId, it) + private fun clearEntityHealthbar(entity: LivingEntity) { + removeHealthbarTasks.remove(entity.uniqueId)?.let { + it.taskId?.let { taskId -> scheduler.cancelTask(taskId) } + it.removeAction() } } @@ -67,8 +98,6 @@ class DamageListener( * Remember to remove all healthbars on close */ override fun close() { - removeHealthbarTasks - .mapNotNull { (_, removeTask) -> removeTask?.task } - .forEach { it() } + removeHealthbarTasks.forEach { (_, removeTask) -> removeTask?.removeAction() } } } diff --git a/src/main/kotlin/org/simplemc/simplehealthbars2/SimpleHealthbars2.kt b/src/main/kotlin/org/simplemc/simplehealthbars2/SimpleHealthbars2.kt index 949b69d..434eb68 100644 --- a/src/main/kotlin/org/simplemc/simplehealthbars2/SimpleHealthbars2.kt +++ b/src/main/kotlin/org/simplemc/simplehealthbars2/SimpleHealthbars2.kt @@ -62,10 +62,10 @@ class SimpleHealthbars2 : JavaPlugin() { Loaded Healthbar configs: Player bars: ${barsConfigToString(playerHealthbars)} - + Mob bars: ${barsConfigToString(mobHealthbars)} - + """.trimIndent() } @@ -87,7 +87,7 @@ class SimpleHealthbars2 : JavaPlugin() { ScoreboardHealthbar.Config( useMainScoreboard = config.getBoolean("useMainScoreboard", false), style = Healthbar.Style.valueOf(checkNotNull(config.getString("style", "ABSOLUTE"))), - duration = Duration.ofSeconds(config.getLong("duration", 5)), + duration = loadBarDuration(config), ), ) Healthbar.Type.NONE -> null @@ -96,12 +96,20 @@ class SimpleHealthbars2 : JavaPlugin() { private fun loadStringBar(config: ConfigurationSection) = StringHealthbar.Config( style = Healthbar.Style.valueOf(checkNotNull(config.getString("style", "BAR"))), - duration = Duration.ofSeconds(config.getLong("duration", 5)), + duration = loadBarDuration(config), length = config.getInt("length", 20), char = config.getInt("char", 0x25ae).toChar(), showMobNames = config.getBoolean("showMobNames", true), ) + private fun loadBarDuration(config: ConfigurationSection): Duration? = config.getString("duration").let { + if (it == "always") { + null + } else { + Duration.ofSeconds(it?.toLongOrNull() ?: 5L) + } + } + override fun onDisable() { listener.close() Bukkit.getScoreboardManager()?.mainScoreboard?.getObjective(OBJECTIVE_NAME)?.unregister() diff --git a/src/main/kotlin/org/simplemc/simplehealthbars2/healthbar/Healthbar.kt b/src/main/kotlin/org/simplemc/simplehealthbars2/healthbar/Healthbar.kt index 0553b8c..d49d85f 100644 --- a/src/main/kotlin/org/simplemc/simplehealthbars2/healthbar/Healthbar.kt +++ b/src/main/kotlin/org/simplemc/simplehealthbars2/healthbar/Healthbar.kt @@ -9,12 +9,12 @@ interface Healthbar { interface Config { val style: Style - val duration: Duration + val duration: Duration? } val config: Config - val durationTicks: Long - get() = config.duration.seconds * 20 + val durationTicks: Long? + get() = config.duration?.seconds?.times(20) /** * Update target's healthbar with latest health diff --git a/src/main/kotlin/org/simplemc/simplehealthbars2/healthbar/ScoreboardHealthbar.kt b/src/main/kotlin/org/simplemc/simplehealthbars2/healthbar/ScoreboardHealthbar.kt index 9e53eda..6732b6b 100644 --- a/src/main/kotlin/org/simplemc/simplehealthbars2/healthbar/ScoreboardHealthbar.kt +++ b/src/main/kotlin/org/simplemc/simplehealthbars2/healthbar/ScoreboardHealthbar.kt @@ -21,7 +21,7 @@ class ScoreboardHealthbar(override val config: Config) : PlayerHealthbar { data class Config( val useMainScoreboard: Boolean = false, override val style: Healthbar.Style = Healthbar.Style.ABSOLUTE, - override val duration: Duration = Duration.ofSeconds(5), + override val duration: Duration? = Duration.ofSeconds(5), ) : Healthbar.Config private val objective: Objective diff --git a/src/main/kotlin/org/simplemc/simplehealthbars2/healthbar/StringHealthbar.kt b/src/main/kotlin/org/simplemc/simplehealthbars2/healthbar/StringHealthbar.kt index 471d014..a3f51ec 100644 --- a/src/main/kotlin/org/simplemc/simplehealthbars2/healthbar/StringHealthbar.kt +++ b/src/main/kotlin/org/simplemc/simplehealthbars2/healthbar/StringHealthbar.kt @@ -10,7 +10,7 @@ import kotlin.math.ceil abstract class StringHealthbar(final override val config: Config) : Healthbar { data class Config( override val style: Healthbar.Style = Healthbar.Style.BAR, - override val duration: Duration = Duration.ofSeconds(5), + override val duration: Duration? = Duration.ofSeconds(5), val length: Int = 20, val char: Char = 0x25ae.toChar(), val showMobNames: Boolean = true,