Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## [Unreleased]
### Added
- GH-4: Sound support (@tajobe)
- GH-3: Title type announcement sender (@tajobe)
- MIT license

Expand All @@ -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
Expand Down
110 changes: 64 additions & 46 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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>`
- ...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
Expand All @@ -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**

Expand All @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -16,8 +18,8 @@ data class SimpleAnnounceConfig(
val announcements: List<AnnouncementConfig<*>>,
) {
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"
}
}
Expand All @@ -27,53 +29,63 @@ 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<T> {
sealed class AnnouncementConfig<T : AnnouncementConfig.Message> {
abstract val random: Boolean
abstract val delay: Duration
abstract val repeat: Duration?
abstract val sound: SoundConfig?
abstract val includesPermissions: List<String>
abstract val excludesPermissions: List<String>
abstract val messages: List<T>

interface Message {
val sound: SoundConfig?
}

@JsonIgnore
val delayTicks = delay.inTicks.toInt()

@JsonIgnore
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<String> = emptyList(),
override val excludesPermissions: List<String> = emptyList(),
@field:JsonAlias("message") override val messages: List<String>,
) : AnnouncementConfig<String>()
@field:JsonAlias("message") override val messages: List<ChatMessage>,
) : AnnouncementConfig<ChatMessage>() {
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<String> = emptyList(),
override val excludesPermissions: List<String> = emptyList(),
@field:JsonAlias("message") override val messages: List<BossBarMessage>,
@field:JsonUnwrapped val barConfig: BarConfig = BarConfig(),
) : AnnouncementConfig<Boss.BossBarMessage>() {
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" }
}
Expand All @@ -90,7 +102,7 @@ data class SimpleAnnounceConfig(
val holdTicks = hold.inTicks

init {
hold.checkNullZeroOrPositive("hold")
hold.requireNullZeroOrPositive("hold")
}
}
}
Expand All @@ -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<String> = emptyList(),
override val excludesPermissions: List<String> = emptyList(),
@field:JsonAlias("message") override val messages: List<TitleMessage>,
Expand All @@ -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,
Expand All @@ -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" }
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<MessageType, ConfigType : SimpleAnnounceConfig.AnnouncementConfig<MessageType>>(
abstract class AnnouncementSender<MessageType : AnnouncementConfig.Message, ConfigType : AnnouncementConfig<MessageType>>(
internal val plugin: Plugin,
internal val announcement: ConfigType,
) : Runnable {
Expand All @@ -36,7 +36,15 @@ abstract class AnnouncementSender<MessageType, ConfigType : SimpleAnnounceConfig
return hasAllRequiredPermissions && hasNoExcludedPermissions
}

internal fun getNextAnnouncement(): MessageType = when {
internal fun send(message: MessageType, messageAction: (Player) -> 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 -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ class BossBarSender(
announcement: Boss,
) : AnnouncementSender<Boss.BossBarMessage, Boss>(plugin, announcement) {
override fun run() {
val message = getNextAnnouncement()
val message = getNextMessage()
val barConfig = message.barConfig ?: announcement.barConfig

// create the bar
val bar = Bukkit.createBossBar(message.message, barConfig.color, barConfig.style)
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
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String, Chat>(plugin, announcement) {
) : AnnouncementSender<Chat.ChatMessage, Chat>(plugin, announcement) {
override fun run() {
Bukkit.getOnlinePlayers()
.filterNotNull()
.filter(this::shouldSendTo)
.forEach { it.sendMessage(getNextAnnouncement()) }
val message = getNextMessage()
send(message) { it.sendMessage(message.message) }
}
}
Loading