diff --git a/CHANGELOG.md b/CHANGELOG.md index 4090bc3..8c2b5fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## [Unreleased] ### Added +- GH-4: Sound support (@tajobe) - GH-3: Title type announcement sender (@tajobe) - MIT license @@ -13,6 +14,7 @@ - Config Auto-reload is now a repeating task as intended - Config actually created on first run - Copy updated default header when config is updated +- Chat sender advancing the current message per player ### Removed - Offline variant is now the default jar, no longer producing an "online" version diff --git a/README.md b/README.md index 3529972..726c1f4 100755 --- a/README.md +++ b/README.md @@ -25,19 +25,26 @@ Messages can be sent server-wide or controlled by permissions after a delay and - `random`(boolean, optional): if list of messages should be sent in random order - `delay`(duration*, optional - default 0): Delay after loading to send message - `repeat`(duration*, optional): time between sending/repeating each message + - `sound`(SoundConfig, optional): + - `sound`([Sound]): Sound to send with announcement + - `volume`(float, optional): Volume of sound to send between 0 and 1, default 1 + - `pitch`(float, optional): Pitch of sound to send between 0.5 and 2, default 1 - `includesPermissions`**(String list, optional): Only send announcement to players with these permissions - `excludesPermissions`**(String list, optional): Exclude players with these permissions from receiving the announcement - - `` + - ...additional options depending on announcement type - `Chat` type announcement: - - `messages`(String list): Message(s) to send + - `messages`(ChatMessage list): Message(s) to send + - `message`(string): message string + - `sound`(SoundConfig, optional): Override announcement SoundConfig - `Boss` type announcement: - `hold`(duration*): Time for boss bar to be on screen - - `color`(BarColor): Color of bar, one of PINK, BLUE, RED, GREEN, YELLOW, PURPLE, WHITE - - `style`(BarStyle): Style of bar, one of SOLID, SEGMENTED_6, SEGMENTED_10, SEGMENTED_12, SEGMENTED_20 + - `color`([BarColor]): Color of bar, one of PINK, BLUE, RED, GREEN, YELLOW, PURPLE, WHITE + - `style`([BarStyle]): Style of bar, one of SOLID, SEGMENTED_6, SEGMENTED_10, SEGMENTED_12, SEGMENTED_20 - `animate`(boolean): if bar should animate over hold time - `reverseAnimation`(boolean): if animation should be reversed - `messages`(BossBarMessage list): - `message`(string): message string + - `sound`(SoundConfig, optional): Override announcement SoundConfig - ...boss bar config overrides per message eg hold, color, style, animate, etc... - `Title` type announcement: - `fadeIn`(duration*): Time it takes for title to fade in @@ -46,6 +53,7 @@ Messages can be sent server-wide or controlled by permissions after a delay and - `messages`(TitleMessage list): - `title`(string): title string - `subtitle`(string): subtitle string, appears below title slightly smaller + - `sound`(SoundConfig, optional): Override announcement SoundConfig - ...title config overrides eg fadeIn, stay, fadeOut... - `config-version`: **Internal use for configuration migrations, do not edit** @@ -61,47 +69,57 @@ Messages can be sent server-wide or controlled by permissions after a delay and ```yaml autoReload: 10m announcements: - - type: Chat - delay: 30s - repeat: 2m - includesPermissions: - - permissions.build - - another.permission - excludesPermissions: - - permissions.admin - messages: - - hello - - world - - type: Chat - repeat: 1m 40s - messages: - - abc - - xyz - - type: Title - repeat: 30s - messages: - - title: Title! - subtitle: Subtitle! - - title: Title only custom durations - fadeIn: 100ms - stay: 10s - fadeOut: 1s - fadeIn: 500ms - stay: 5s - fadeOut: 500ms - - type: Boss - random: true - repeat: 15s - messages: - - message: eyy - - message: custom bar config - hold: 10s - color: GREEN - style: SEGMENTED_20 - reverseAnimation: true - hold: 5s - color: PURPLE - style: SOLID - animate: true + - type: Chat + delay: 30s + repeat: 2m + includesPermissions: + - permissions.build + - another.permission + excludesPermissions: + - permissions.admin + messages: + - message: hello + - message: world + - type: Chat + repeat: 1m 40s + sound: + sound: AMBIENT_CAVE + volume: .5 + pitch: 2 + messages: + - message: abc + sound: + sound: BLOCK_ENCHANTMENT_TABLE_USE + - message: xyz + - type: Title + repeat: 30s + messages: + - title: Title! + subtitle: Subtitle! + - title: Title only custom durations + fadeIn: 100ms + stay: 10s + fadeOut: 1s + fadeIn: 500ms + stay: 5s + fadeOut: 500ms + - type: Boss + random: true + repeat: 15s + messages: + - message: eyy + - message: custom bar config + hold: 10s + color: GREEN + style: SEGMENTED_20 + reverseAnimation: true + hold: 5s + color: PURPLE + style: SOLID + animate: true config-version: 1 ``` + +[Sound]: https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/Sound.html +[BarColor]: https://hub.spigotmc.org/javadocs/spigot/org/bukkit/boss/BarColor.html +[BarStyle]: https://hub.spigotmc.org/javadocs/spigot/org/bukkit/boss/BarStyle.html diff --git a/src/main/kotlin/org/simplemc/simpleannounce/config/SimpleAnnounceConfig.kt b/src/main/kotlin/org/simplemc/simpleannounce/config/SimpleAnnounceConfig.kt index 35cb256..1c83bc1 100644 --- a/src/main/kotlin/org/simplemc/simpleannounce/config/SimpleAnnounceConfig.kt +++ b/src/main/kotlin/org/simplemc/simpleannounce/config/SimpleAnnounceConfig.kt @@ -4,8 +4,10 @@ import com.fasterxml.jackson.annotation.JsonAlias import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonTypeInfo import com.fasterxml.jackson.annotation.JsonUnwrapped +import org.bukkit.Sound import org.bukkit.boss.BarColor import org.bukkit.boss.BarStyle +import org.simplemc.simpleannounce.config.SimpleAnnounceConfig.AnnouncementConfig.Chat.ChatMessage import org.simplemc.simpleannounce.inTicks import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds @@ -16,8 +18,8 @@ data class SimpleAnnounceConfig( val announcements: List>, ) { companion object { - private fun Duration?.checkNullZeroOrPositive(name: String) { - check(this == null || this == Duration.Companion.ZERO || this.isPositive()) { + private fun Duration?.requireNullZeroOrPositive(name: String) { + require(this == null || this == Duration.Companion.ZERO || this.isPositive()) { "When set, $name must be >= 0s" } } @@ -27,20 +29,25 @@ data class SimpleAnnounceConfig( val autoReloadTicks = autoReload?.inTicks init { - check(autoReload == null || autoReload == Duration.Companion.ZERO || autoReload.inWholeMinutes >= 1) { + require(autoReload == null || autoReload == Duration.Companion.ZERO || autoReload.inWholeMinutes >= 1) { "When set, Auto Reload Duration must be > 1 minute" } } @JsonTypeInfo(use = JsonTypeInfo.Id.SIMPLE_NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") - sealed class AnnouncementConfig { + sealed class AnnouncementConfig { abstract val random: Boolean abstract val delay: Duration abstract val repeat: Duration? + abstract val sound: SoundConfig? abstract val includesPermissions: List abstract val excludesPermissions: List abstract val messages: List + interface Message { + val sound: SoundConfig? + } + @JsonIgnore val delayTicks = delay.inTicks.toInt() @@ -48,23 +55,27 @@ data class SimpleAnnounceConfig( val repeatTicks = repeat?.inTicks?.toInt() init { - delay.checkNullZeroOrPositive("delay") - repeat.checkNullZeroOrPositive("repeat") + delay.requireNullZeroOrPositive("delay") + repeat.requireNullZeroOrPositive("repeat") } data class Chat( override val random: Boolean = false, override val delay: Duration = Duration.Companion.ZERO, override val repeat: Duration? = null, + override val sound: SoundConfig? = null, override val includesPermissions: List = emptyList(), override val excludesPermissions: List = emptyList(), - @field:JsonAlias("message") override val messages: List, - ) : AnnouncementConfig() + @field:JsonAlias("message") override val messages: List, + ) : AnnouncementConfig() { + data class ChatMessage(val message: String, override val sound: SoundConfig? = null) : Message + } data class Boss( override val random: Boolean = false, override val delay: Duration = Duration.Companion.ZERO, override val repeat: Duration? = null, + override val sound: SoundConfig? = null, override val includesPermissions: List = emptyList(), override val excludesPermissions: List = emptyList(), @field:JsonAlias("message") override val messages: List, @@ -72,8 +83,9 @@ data class SimpleAnnounceConfig( ) : AnnouncementConfig() { data class BossBarMessage( val message: String, + override val sound: SoundConfig? = null, @field:JsonUnwrapped val barConfig: BarConfig? = null, - ) { + ) : Message { init { require(message.length <= 64) { "Boss Bar text must be <= 64 characters" } } @@ -90,7 +102,7 @@ data class SimpleAnnounceConfig( val holdTicks = hold.inTicks init { - hold.checkNullZeroOrPositive("hold") + hold.requireNullZeroOrPositive("hold") } } } @@ -99,6 +111,7 @@ data class SimpleAnnounceConfig( override val random: Boolean = false, override val delay: Duration = Duration.Companion.ZERO, override val repeat: Duration? = null, + override val sound: SoundConfig? = null, override val includesPermissions: List = emptyList(), override val excludesPermissions: List = emptyList(), @field:JsonAlias("message") override val messages: List, @@ -107,8 +120,9 @@ data class SimpleAnnounceConfig( data class TitleMessage( val title: String, val subtitle: String? = null, + override val sound: SoundConfig? = null, @field:JsonUnwrapped val titleConfig: TitleConfig? = null, - ) + ) : Message data class TitleConfig( val fadeIn: Duration = 500.milliseconds, @@ -125,11 +139,18 @@ data class SimpleAnnounceConfig( val fadeOutTicks = fadeOut.inTicks.toInt() init { - fadeIn.checkNullZeroOrPositive("fadeIn") - stay.checkNullZeroOrPositive("stay") - fadeOut.checkNullZeroOrPositive("fadeOut") + fadeIn.requireNullZeroOrPositive("fadeIn") + stay.requireNullZeroOrPositive("stay") + fadeOut.requireNullZeroOrPositive("fadeOut") } } } + + data class SoundConfig(val sound: Sound, val volume: Float = 1F, val pitch: Float = 1F) { + init { + require(volume >= 0 && volume <= 1) { "Sound volume must be between 0 and 1" } + require(pitch >= 0.5 && pitch <= 2) { "Sound pitch must be between 0.5 and 2" } + } + } } } diff --git a/src/main/kotlin/org/simplemc/simpleannounce/sender/AnnouncementSender.kt b/src/main/kotlin/org/simplemc/simpleannounce/sender/AnnouncementSender.kt index f0404a2..34d4d46 100644 --- a/src/main/kotlin/org/simplemc/simpleannounce/sender/AnnouncementSender.kt +++ b/src/main/kotlin/org/simplemc/simpleannounce/sender/AnnouncementSender.kt @@ -4,14 +4,14 @@ import io.github.oshai.kotlinlogging.KotlinLogging import org.bukkit.Bukkit import org.bukkit.entity.Player import org.bukkit.plugin.Plugin -import org.simplemc.simpleannounce.config.SimpleAnnounceConfig +import org.simplemc.simpleannounce.config.SimpleAnnounceConfig.AnnouncementConfig import org.simplemc.simpleannounce.inTicks import java.util.concurrent.atomic.AtomicInteger import kotlin.random.Random private val logger = KotlinLogging.logger("SimpleAnnounce AnnouncementSender") -abstract class AnnouncementSender>( +abstract class AnnouncementSender>( internal val plugin: Plugin, internal val announcement: ConfigType, ) : Runnable { @@ -36,7 +36,15 @@ abstract class AnnouncementSender Unit) { + val sound = message.sound ?: announcement.sound + Bukkit.getOnlinePlayers().filterNotNull().filter(this::shouldSendTo).forEach { + messageAction(it) + sound?.let { sound -> it.playSound(it.location, sound.sound, sound.volume, sound.pitch) } + } + } + + internal fun getNextMessage(): MessageType = when { announcement.messages.size == 1 -> announcement.messages[0] announcement.random -> announcement.messages[Random.nextInt(announcement.messages.size)] else -> { diff --git a/src/main/kotlin/org/simplemc/simpleannounce/sender/BossBarSender.kt b/src/main/kotlin/org/simplemc/simpleannounce/sender/BossBarSender.kt index 9bef14a..f81d790 100644 --- a/src/main/kotlin/org/simplemc/simpleannounce/sender/BossBarSender.kt +++ b/src/main/kotlin/org/simplemc/simpleannounce/sender/BossBarSender.kt @@ -11,7 +11,7 @@ class BossBarSender( announcement: Boss, ) : AnnouncementSender(plugin, announcement) { override fun run() { - val message = getNextAnnouncement() + val message = getNextMessage() val barConfig = message.barConfig ?: announcement.barConfig // create the bar @@ -19,7 +19,7 @@ class BossBarSender( bar.progress = if (barConfig.reverseAnimation) 1.0 else 0.0 // show bar to players - Bukkit.getOnlinePlayers().filterNotNull().filter(this::shouldSendTo).forEach(bar::addPlayer) + send(message, bar::addPlayer) bar.isVisible = true // set up animation diff --git a/src/main/kotlin/org/simplemc/simpleannounce/sender/ChatSender.kt b/src/main/kotlin/org/simplemc/simpleannounce/sender/ChatSender.kt index bfae817..8ba1c94 100644 --- a/src/main/kotlin/org/simplemc/simpleannounce/sender/ChatSender.kt +++ b/src/main/kotlin/org/simplemc/simpleannounce/sender/ChatSender.kt @@ -1,17 +1,14 @@ package org.simplemc.simpleannounce.sender -import org.bukkit.Bukkit import org.bukkit.plugin.Plugin import org.simplemc.simpleannounce.config.SimpleAnnounceConfig.AnnouncementConfig.Chat class ChatSender( plugin: Plugin, announcement: Chat, -) : AnnouncementSender(plugin, announcement) { +) : AnnouncementSender(plugin, announcement) { override fun run() { - Bukkit.getOnlinePlayers() - .filterNotNull() - .filter(this::shouldSendTo) - .forEach { it.sendMessage(getNextAnnouncement()) } + val message = getNextMessage() + send(message) { it.sendMessage(message.message) } } } diff --git a/src/main/kotlin/org/simplemc/simpleannounce/sender/TitleSender.kt b/src/main/kotlin/org/simplemc/simpleannounce/sender/TitleSender.kt index 5cb7862..0b74b8f 100644 --- a/src/main/kotlin/org/simplemc/simpleannounce/sender/TitleSender.kt +++ b/src/main/kotlin/org/simplemc/simpleannounce/sender/TitleSender.kt @@ -1,6 +1,5 @@ package org.simplemc.simpleannounce.sender -import org.bukkit.Bukkit import org.bukkit.plugin.Plugin import org.simplemc.simpleannounce.config.SimpleAnnounceConfig.AnnouncementConfig.Title @@ -9,20 +8,17 @@ class TitleSender( announcement: Title, ) : AnnouncementSender(plugin, announcement) { override fun run() { - val message = getNextAnnouncement() + val message = getNextMessage() val titleConfig = message.titleConfig ?: announcement.titleConfig - Bukkit.getOnlinePlayers() - .filterNotNull() - .filter(this::shouldSendTo) - .forEach { - it.sendTitle( - message.title, - message.subtitle, - titleConfig.fadeInTicks, - titleConfig.stayTicks, - titleConfig.fadeOutTicks, - ) - } + send(message) { + it.sendTitle( + message.title, + message.subtitle, + titleConfig.fadeInTicks, + titleConfig.stayTicks, + titleConfig.fadeOutTicks, + ) + } } } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index c30eddc..a239db7 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -14,6 +14,10 @@ # random(boolean, optional): # delay(duration, optional - default 0): # repeat(duration, optional):