Reorganize command permissions, add license

This commit is contained in:
jmp committed 2021-02-25 15:57:02 -08:00
1 parent e58809eed3
commit 7c91d476a8
42 files changed
+2900 -1916

No files matched your search

+8
View File
@@ -0,0 +1,8 @@
root = true
[*]
charset = utf-8
indent_size = 2
indent_style = space
insert_final_newline = true
max_line_length = off
+58 -61
View File
@@ -2,80 +2,77 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ConfigureShadowRelocatio
import org.apache.commons.io.output.ByteArrayOutputStream
plugins {
kotlin("jvm") version "1.4.21"
id("com.github.johnrengelman.shadow") version "6.1.0"
id("kr.entree.spigradle") version "2.2.3"
kotlin("jvm") version "1.4.30"
id("com.github.johnrengelman.shadow") version "6.1.0"
id("kr.entree.spigradle") version "2.2.3"
id("net.kyori.indra.license-header") version "1.3.1"
}
val projectName = "AnnouncerPlus"
group = "xyz.jpenilla"
version = "1.2.2.2+${getLastCommitHash()}-SNAPSHOT"
version = "1.2.3.1+${getLastCommitHash()}-SNAPSHOT"
description = "Announcement plugin with support for permissions. Supports Hex colors and clickable messages/hover text using MiniMessage."
repositories {
mavenLocal()
mavenCentral()
jcenter()
maven(url = "https://hub.spigotmc.org/nexus/content/repositories/snapshots/")
maven(url = "https://oss.sonatype.org/content/groups/public/")
maven(url = "https://repo.spongepowered.org/maven")
maven(url = "https://repo.jpenilla.xyz/snapshots")
maven(url = "https://ci.ender.zone/plugin/repository/everything/")
maven(url = "https://repo.codemc.org/repository/maven-public")
maven(url = "https://jitpack.io")
mavenLocal()
mavenCentral()
jcenter()
maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/")
maven("https://oss.sonatype.org/content/groups/public/")
maven("https://repo.spongepowered.org/maven")
maven("https://repo.jpenilla.xyz/snapshots")
maven("https://ci.ender.zone/plugin/repository/everything/")
maven("https://repo.codemc.org/repository/maven-public")
maven("https://jitpack.io")
}
dependencies {
compileOnly("org.spigotmc", "spigot-api", "1.13.2-R0.1-SNAPSHOT")
compileOnly("com.github.MilkBowl", "VaultAPI", "1.7")
compileOnly("net.ess3", "EssentialsX", "2.17.2")
compileOnly("org.spigotmc", "spigot-api", "1.13.2-R0.1-SNAPSHOT")
compileOnly("com.github.MilkBowl", "VaultAPI", "1.7")
compileOnly("net.ess3", "EssentialsX", "2.17.2")
implementation("org.koin", "koin-core", "2.1.6")
implementation("xyz.jpenilla", "jmplib", "1.0.1+27-SNAPSHOT")
implementation("org.spongepowered", "configurate-hocon", "4.0.0")
implementation("org.bstats", "bstats-bukkit", "1.8")
implementation("org.koin", "koin-core", "2.1.6")
implementation("net.kyori", "adventure-api", "4.5.1")
implementation("xyz.jpenilla", "jmplib", "1.0.1+30-SNAPSHOT")
implementation("org.spongepowered", "configurate-hocon", "4.1.0-SNAPSHOT")
implementation("org.bstats", "bstats-bukkit", "2.2.1")
val cloudVersion = "1.3.0"
implementation("cloud.commandframework", "cloud-paper", cloudVersion)
implementation("cloud.commandframework", "cloud-kotlin-extensions", cloudVersion)
implementation("cloud.commandframework", "cloud-minecraft-extras", cloudVersion)
}
spigot {
name = projectName
apiVersion = "1.13"
description = "Announcement plugin with support for permissions. Supports Hex colors and clickable messages/hover text using MiniMessage."
website = "https://github.com/jmanpenilla/AnnouncerPlus"
authors("jmp")
depends("Vault")
softDepends("PlaceholderAPI", "Prisma", "Essentials")
}
val autoRelocate by tasks.register("configureShadowRelocation", ConfigureShadowRelocation::class) {
target = tasks.shadowJar.get()
val packageName = "${project.group}.${project.name.toLowerCase()}"
prefix = "$packageName.shaded"
val cloudVersion = "1.4.0"
implementation("cloud.commandframework", "cloud-paper", cloudVersion)
implementation("cloud.commandframework", "cloud-kotlin-extensions", cloudVersion)
implementation("cloud.commandframework", "cloud-minecraft-extras", cloudVersion)
}
tasks {
build {
dependsOn(shadowJar)
}
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
shadowJar {
minimize()
dependsOn(autoRelocate)
archiveClassifier.set("")
archiveFileName.set("$projectName-${project.version}.jar")
}
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
register<ConfigureShadowRelocation>("autoRelocate") {
target = shadowJar.get()
val packageName = "${project.group}.${project.name.toLowerCase()}"
prefix = "$packageName.lib"
}
shadowJar {
dependsOn(withType<ConfigureShadowRelocation>())
minimize()
archiveClassifier.set("")
archiveFileName.set("${project.name}-${project.version}.jar")
}
build {
dependsOn(shadowJar)
}
}
fun getLastCommitHash(): String {
val byteOut = ByteArrayOutputStream()
exec {
commandLine = listOf("git", "rev-parse", "--short", "HEAD")
standardOutput = byteOut
}
return byteOut.toString(Charsets.UTF_8).trim()
spigot {
apiVersion = "1.13"
website = "https://github.com/jmanpenilla/AnnouncerPlus"
authors("jmp")
depends("Vault")
softDepends("PlaceholderAPI", "Prisma", "Essentials")
}
fun getLastCommitHash(): String = ByteArrayOutputStream().apply {
exec {
commandLine = listOf("git", "rev-parse", "--short", "HEAD")
standardOutput = this@apply
}
}.toString(Charsets.UTF_8).trim()
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020-2021 Jason Penilla
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+21
View File
@@ -0,0 +1,21 @@
This file is part of AnnouncerPlus, licensed under the MIT License.
Copyright (c) 2020-2021 Jason Penilla
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+1
View File
@@ -0,0 +1 @@
rootProject.name = "AnnouncerPlus"
@@ -1,3 +1,26 @@
/*
* This file is part of AnnouncerPlus, licensed under the MIT License.
*
* Copyright (c) 2020-2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package xyz.jpenilla.announcerplus
import com.google.gson.Gson
@@ -7,92 +30,94 @@ import kr.entree.spigradle.annotations.PluginMain
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer
import net.milkbowl.vault.permission.Permission
import org.bstats.bukkit.Metrics
import org.bstats.charts.SimplePie
import org.koin.core.KoinComponent
import org.koin.core.context.startKoin
import org.koin.core.inject
import org.koin.dsl.module
import xyz.jpenilla.announcerplus.command.CommandManager
import xyz.jpenilla.announcerplus.compatability.EssentialsHook
import xyz.jpenilla.announcerplus.compatibility.EssentialsHook
import xyz.jpenilla.announcerplus.config.ConfigManager
import xyz.jpenilla.announcerplus.config.message.MessageConfig
import xyz.jpenilla.announcerplus.task.ToastTask
import xyz.jpenilla.announcerplus.util.UpdateChecker
import xyz.jpenilla.jmplib.BasePlugin
import xyz.jpenilla.jmplib.Environment
@PluginMain
class AnnouncerPlus : BasePlugin(), KoinComponent {
val gson: Gson = GsonBuilder().create()
val jsonParser = JsonParser()
val gsonComponentSerializer = GsonComponentSerializer.gson()
val downsamplingGsonComponentSerializer = GsonComponentSerializer.colorDownsamplingGson()
val configManager: ConfigManager by inject()
val gson: Gson = GsonBuilder().create()
val jsonParser = JsonParser()
val gsonComponentSerializer = GsonComponentSerializer.gson()
val downsamplingGsonComponentSerializer = GsonComponentSerializer.colorDownsamplingGson()
val configManager: ConfigManager by inject()
var perms: Permission? = null
var essentials: EssentialsHook? = null
var toastTask: ToastTask? = null
private lateinit var commandManager: CommandManager
var perms: Permission? = null
var essentials: EssentialsHook? = null
var toastTask: ToastTask? = null
private lateinit var commandManager: CommandManager
override fun onPluginEnable() {
if (!setupPermissions()) {
logger.warning("Permissions plugin not found. AnnouncerPlus will not work.")
isEnabled = false
return
}
if (server.pluginManager.isPluginEnabled("Essentials")) {
essentials = EssentialsHook()
}
startKoin {
modules(module {
single { this@AnnouncerPlus }
single { audience }
single { miniMessage }
single { chat }
single { ConfigManager(get()) }
single { gson }
single { jsonParser }
})
}
if (majorMinecraftVersion > 11) {
toastTask = ToastTask()
} else {
logger.info("Sorry, but Toast/Achievement style messages do not work on this version. Update to 1.12 or newer to use this feature.")
}
commandManager = CommandManager(this)
server.pluginManager.registerEvents(JoinQuitListener(), this)
broadcast()
UpdateChecker(this, "jmanpenilla/AnnouncerPlus").updateCheck()
val metrics = Metrics(this, 8067)
metrics.addCustomChart(Metrics.SimplePie("join_quit_configs", configManager.joinQuitConfigs.size::toString))
metrics.addCustomChart(Metrics.SimplePie("message_configs", configManager.messageConfigs.size::toString))
override fun onPluginEnable() {
if (!setupPermissions()) {
logger.warning("Permissions plugin not found. AnnouncerPlus will not work.")
isEnabled = false
return
}
if (server.pluginManager.isPluginEnabled("Essentials")) {
essentials = EssentialsHook()
}
private fun broadcast() {
if (configManager.mainConfig.enableBroadcasts) configManager.messageConfigs.values.forEach(MessageConfig::broadcast)
startKoin {
modules(module {
single { this@AnnouncerPlus }
single { audiences() }
single { miniMessage() }
single { chat() }
single { ConfigManager(get()) }
single { gson }
single { jsonParser }
})
}
fun reload() {
if (toastTask != null) {
toastTask?.cancel()
toastTask = ToastTask()
}
configManager.reload()
broadcast()
if (Environment.majorMinecraftVersion() > 11) {
toastTask = ToastTask()
} else {
logger.info("Sorry, but Toast/Achievement style messages do not work on this version. Update to 1.12 or newer to use this feature.")
}
commandManager = CommandManager(this)
override fun onDisable() {
toastTask?.cancel()
}
server.pluginManager.registerEvents(JoinQuitListener(), this)
broadcast()
private fun setupPermissions(): Boolean {
val rsp = server.servicesManager.getRegistration(Permission::class.java)
if (rsp != null) {
perms = rsp.provider
}
return perms != null
UpdateChecker(this, "jmanpenilla/AnnouncerPlus").updateCheck()
val metrics = Metrics(this, 8067)
metrics.addCustomChart(SimplePie("join_quit_configs", configManager.joinQuitConfigs.size::toString))
metrics.addCustomChart(SimplePie("message_configs", configManager.messageConfigs.size::toString))
}
private fun broadcast() {
if (configManager.mainConfig.enableBroadcasts) configManager.messageConfigs.values.forEach(MessageConfig::broadcast)
}
fun reload() {
if (toastTask != null) {
toastTask?.cancel()
toastTask = ToastTask()
}
configManager.reload()
broadcast()
}
override fun onDisable() {
toastTask?.cancel()
}
private fun setupPermissions(): Boolean {
val rsp = server.servicesManager.getRegistration(Permission::class.java)
if (rsp != null) {
perms = rsp.provider
}
return perms != null
}
}
@@ -1,3 +1,26 @@
/*
* This file is part of AnnouncerPlus, licensed under the MIT License.
*
* Copyright (c) 2020-2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package xyz.jpenilla.announcerplus
import org.bukkit.event.EventHandler
@@ -11,47 +34,47 @@ import xyz.jpenilla.announcerplus.config.ConfigManager
import xyz.jpenilla.jmplib.RandomCollection
class JoinQuitListener : Listener, KoinComponent {
private val configManager: ConfigManager by inject()
private val configManager: ConfigManager by inject()
@EventHandler(priority = EventPriority.LOWEST)
fun onJoin(event: PlayerJoinEvent) {
if (configManager.mainConfig.joinFeatures) {
event.joinMessage = ""
if (configManager.mainConfig.firstJoinConfigEnabled && !event.player.hasPlayedBefore()) {
configManager.firstJoinConfig.onJoin(event.player)
return
}
for (entry in configManager.mainConfig.randomJoinConfigs.entries) {
if (entry.key != "demo" && event.player.hasPermission("announcerplus.randomjoin.${entry.key}")) {
val weights = RandomCollection<String>()
for (pair in entry.value) {
weights.add(pair.weight, pair.configName)
}
configManager.joinQuitConfigs[weights.next()]?.onJoin(event.player)
}
}
for (config in configManager.joinQuitConfigs.values) {
config.onJoin(event.player)
}
@EventHandler(priority = EventPriority.LOWEST)
fun onJoin(event: PlayerJoinEvent) {
if (configManager.mainConfig.joinFeatures) {
event.joinMessage = ""
if (configManager.mainConfig.firstJoinConfigEnabled && !event.player.hasPlayedBefore()) {
configManager.firstJoinConfig.onJoin(event.player)
return
}
for (entry in configManager.mainConfig.randomJoinConfigs.entries) {
if (entry.key != "demo" && event.player.hasPermission("announcerplus.randomjoin.${entry.key}")) {
val weights = RandomCollection<String>()
for (pair in entry.value) {
weights.add(pair.weight, pair.configName)
}
configManager.joinQuitConfigs[weights.next()]?.onJoin(event.player)
}
}
for (config in configManager.joinQuitConfigs.values) {
config.onJoin(event.player)
}
}
}
@EventHandler(priority = EventPriority.LOWEST)
fun onQuit(event: PlayerQuitEvent) {
if (configManager.mainConfig.quitFeatures) {
event.quitMessage = ""
for (entry in configManager.mainConfig.randomQuitConfigs.entries) {
if (entry.key != "demo" && event.player.hasPermission("announcerplus.randomquit.${entry.key}")) {
val weights = RandomCollection<String>()
for (pair in entry.value) {
weights.add(pair.weight, pair.configName)
}
configManager.joinQuitConfigs[weights.next()]?.onQuit(event.player)
}
}
for (config in configManager.joinQuitConfigs.values) {
config.onQuit(event.player)
}
@EventHandler(priority = EventPriority.LOWEST)
fun onQuit(event: PlayerQuitEvent) {
if (configManager.mainConfig.quitFeatures) {
event.quitMessage = ""
for (entry in configManager.mainConfig.randomQuitConfigs.entries) {
if (entry.key != "demo" && event.player.hasPermission("announcerplus.randomquit.${entry.key}")) {
val weights = RandomCollection<String>()
for (pair in entry.value) {
weights.add(pair.weight, pair.configName)
}
configManager.joinQuitConfigs[weights.next()]?.onQuit(event.player)
}
}
for (config in configManager.joinQuitConfigs.values) {
config.onQuit(event.player)
}
}
}
}
@@ -1,3 +1,26 @@
/*
* This file is part of AnnouncerPlus, licensed under the MIT License.
*
* Copyright (c) 2020-2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package xyz.jpenilla.announcerplus.command
import cloud.commandframework.CommandHelpHandler
@@ -15,69 +38,74 @@ import xyz.jpenilla.announcerplus.config.ConfigManager
import xyz.jpenilla.announcerplus.config.message.MessageConfig
class ArgumentFactory : KoinComponent {
private val commandManager: CommandManager by inject()
private val configManager: ConfigManager by inject()
private val commandManager: CommandManager by inject()
private val configManager: ConfigManager by inject()
fun helpQuery(name: String): CommandArgument<CommandSender, String> {
return StringArgument.newBuilder<CommandSender>(name)
.greedy()
.asOptional()
.withSuggestionsProvider { context, _ ->
(commandManager.commandHelpHandler.queryHelp(context.sender, "") as CommandHelpHandler.IndexHelpTopic<CommandSender>)
.entries.map { it.syntaxString }
}
.build()
}
fun helpQuery(name: String): CommandArgument<CommandSender, String> {
return StringArgument.newBuilder<CommandSender>(name)
.greedy()
.asOptional()
.withSuggestionsProvider { context, _ ->
(commandManager.commandHelpHandler.queryHelp(
context.sender,
""
) as CommandHelpHandler.IndexHelpTopic<CommandSender>)
.entries.map { it.syntaxString }
}
.build()
}
fun positiveInteger(name: String): IntegerArgument.Builder<CommandSender> {
return IntegerArgument.newBuilder<CommandSender>(name)
.withMin(1)
}
fun positiveInteger(name: String): IntegerArgument.Builder<CommandSender> {
return IntegerArgument.newBuilder<CommandSender>(name)
.withMin(1)
}
fun messageConfig(name: String): CommandArgument<CommandSender, MessageConfig> {
return commandManager.argumentBuilder(MessageConfig::class.java, name)
.withSuggestionsProvider { _, _ -> configManager.messageConfigs.keys.toList() }
.withParser { _, inputQueue ->
val input = inputQueue.peek()
val config = configManager.messageConfigs[input]
?: return@withParser ArgumentParseResult.failure(IllegalArgumentException(
"No message config for name '$input'. Must be one of: ${configManager.messageConfigs.keys.joinToString(", ")}"
))
inputQueue.remove()
ArgumentParseResult.success(config)
}
.build()
}
fun messageConfig(name: String): CommandArgument<CommandSender, MessageConfig> {
return commandManager.argumentBuilder(MessageConfig::class.java, name)
.withSuggestionsProvider { _, _ -> configManager.messageConfigs.keys.toList() }
.withParser { _, inputQueue ->
val input = inputQueue.peek()
val config = configManager.messageConfigs[input]
?: return@withParser ArgumentParseResult.failure(
IllegalArgumentException(
"No message config for name '$input'. Must be one of: ${configManager.messageConfigs.keys.joinToString(", ")}"
)
)
inputQueue.remove()
ArgumentParseResult.success(config)
}
.build()
}
fun worldPlayers(name: String): CommandArgument<CommandSender, WorldPlayers> {
return commandManager.argumentBuilder(WorldPlayers::class.java, name)
.withSuggestionsProvider { _, _ ->
val suggestions = Bukkit.getWorlds().map { it.name }.toMutableList()
suggestions.add("all")
suggestions
}
.withParser { _, inputQueue ->
val input = inputQueue.peek()
if (input == null || input.isEmpty()) {
return@withParser ArgumentParseResult.failure(
IllegalArgumentException("No input provided.")
)
}
if (input == "all") {
inputQueue.remove()
return@withParser ArgumentParseResult.success(WorldPlayers(
Bukkit.getWorlds().flatMap { it.players }
))
}
val world = Bukkit.getWorld(input)
?: return@withParser ArgumentParseResult.failure(
IllegalArgumentException("No such world: $input")
)
inputQueue.remove()
ArgumentParseResult.success(WorldPlayers(ImmutableList.copyOf(world.players)))
}
.build()
}
fun worldPlayers(name: String): CommandArgument<CommandSender, WorldPlayers> {
return commandManager.argumentBuilder(WorldPlayers::class.java, name)
.withSuggestionsProvider { _, _ ->
val suggestions = Bukkit.getWorlds().map { it.name }.toMutableList()
suggestions.add("all")
suggestions
}
.withParser { _, inputQueue ->
val input = inputQueue.peek()
if (input.isEmpty()) {
return@withParser ArgumentParseResult.failure(
IllegalArgumentException("No input provided.")
)
}
if (input == "all") {
inputQueue.remove()
return@withParser ArgumentParseResult.success(WorldPlayers(
Bukkit.getWorlds().flatMap { it.players }
))
}
val world = Bukkit.getWorld(input)
?: return@withParser ArgumentParseResult.failure(
IllegalArgumentException("No such world: $input")
)
inputQueue.remove()
ArgumentParseResult.success(WorldPlayers(ImmutableList.copyOf(world.players)))
}
.build()
}
data class WorldPlayers(val players: Collection<Player>)
data class WorldPlayers(val players: Collection<Player>)
}
@@ -1,7 +1,30 @@
/*
* This file is part of AnnouncerPlus, licensed under the MIT License.
*
* Copyright (c) 2020-2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package xyz.jpenilla.announcerplus.command
import org.koin.core.KoinComponent
interface BaseCommand : KoinComponent {
fun register()
fun register()
}
@@ -1,8 +1,29 @@
/*
* This file is part of AnnouncerPlus, licensed under the MIT License.
*
* Copyright (c) 2020-2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package xyz.jpenilla.announcerplus.command
import cloud.commandframework.context.CommandContext
import cloud.commandframework.kotlin.extension.commandBuilder
import cloud.commandframework.kotlin.extension.description
import cloud.commandframework.minecraft.extras.MinecraftHelp
import com.google.common.collect.ImmutableSet
import net.kyori.adventure.text.Component
@@ -19,161 +40,174 @@ import org.koin.core.inject
import xyz.jpenilla.announcerplus.AnnouncerPlus
import xyz.jpenilla.announcerplus.config.ConfigManager
import xyz.jpenilla.announcerplus.config.message.MessageConfig
import xyz.jpenilla.announcerplus.util.description
import xyz.jpenilla.jmplib.Chat
import java.util.Collections
import kotlin.math.roundToInt
class CommandAnnouncerPlus : BaseCommand {
private val announcerPlus: AnnouncerPlus by inject()
private val commandManager: CommandManager by inject()
private val configManager: ConfigManager by inject()
private val minecraftHelp: MinecraftHelp<CommandSender> by inject()
private val argumentFactory: ArgumentFactory by inject()
private val chat: Chat by inject()
private val announcerPlus: AnnouncerPlus by inject()
private val commandManager: CommandManager by inject()
private val configManager: ConfigManager by inject()
private val minecraftHelp: MinecraftHelp<CommandSender> by inject()
private val argumentFactory: ArgumentFactory by inject()
private val chat: Chat by inject()
override fun register() {
commandManager.commandBuilder("ap", aliases = arrayOf("announcerplus", "announcer")) {
registerCopy {
commandDescription("Shows help for AnnouncerPlus commands.")
literal("help")
argument(description("Help Query")) {
argumentFactory.helpQuery("query")
}
handler(::executeHelp)
}
registerCopy {
commandDescription("Prints some information about AnnouncerPlus.")
literal("about")
handler(::executeAbout)
}
registerCopy {
permission = "announcerplus.reload"
commandDescription("Reloads AnnouncerPlus configs.")
literal("reload")
handler(::executeReload)
}
registerCopy {
permission = "announcerplus.list"
commandDescription("Displays the chat messages of a message config to the command sender.")
literal("list")
argument(argumentFactory.messageConfig("config"))
argument(argumentFactory.positiveInteger("page").asOptional())
handler(::executeList)
}
override fun register() {
commandManager.registerSubcommand("help") {
permission = "announcerplus.command.help"
commandDescription("Shows help for AnnouncerPlus commands.")
argument(description("Help Query")) {
argumentFactory.helpQuery("query")
}
handler(::executeHelp)
}
commandManager.registerSubcommand("about") {
permission = "announcerplus.command.about"
commandDescription("Prints some information about AnnouncerPlus.")
handler(::executeAbout)
}
commandManager.registerSubcommand("reload") {
permission = "announcerplus.command.reload"
commandDescription("Reloads AnnouncerPlus configs.")
handler(::executeReload)
}
commandManager.registerSubcommand("list") {
permission = "announcerplus.command.list"
commandDescription("Displays the chat messages of a message config to the command sender.")
argument(argumentFactory.messageConfig("config"))
argument(argumentFactory.positiveInteger("page").asOptional())
handler(::executeList)
}
}
private fun executeHelp(ctx: CommandContext<CommandSender>) {
minecraftHelp.queryCommands(ctx.getOrDefault("query", "")!!, ctx.sender)
}
private fun executeAbout(ctx: CommandContext<CommandSender>) {
val color = randomColor()
val text =
"<hover:show_text:'<rainbow>click me!'><click:open_url:${announcerPlus.description.website}>${announcerPlus.name} <$color>${announcerPlus.description.version}"
val header = "<gradient:white:$color:white><strikethrough>${
"-".repeat(
(announcerPlus.miniMessage().stripTokens(text).length / 1.2).roundToInt() + 4
)
}"
listOf(
header,
text,
"By <$color>jmp",
header
).forEach { chat.send(ctx.sender, chat.getCenteredMessage(it)) }
}
private fun executeReload(ctx: CommandContext<CommandSender>) {
chat.send(
ctx.sender,
chat.getCenteredMessage("<italic><gradient:${randomColor()}:${randomColor()}>Reloading ${announcerPlus.name} config...")
)
try {
announcerPlus.reload()
chat.send(ctx.sender, chat.getCenteredMessage("<green>Done."))
} catch (e: Exception) {
chat.send(
ctx.sender,
"<gradient:red:gold>I'm sorry, but there was an error reloading the plugin. This is most likely due to misconfiguration. Check console for more info."
)
e.printStackTrace()
}
}
private fun executeList(ctx: CommandContext<CommandSender>) {
val color = randomColor()
val config = ctx.get<MessageConfig>("config")
val page = ctx.getOrDefault("page", 1) ?: 1
val pagination = Pagination.builder().apply {
resultsPerPage(17)
width(53)
line { line ->
line.character('-')
line.style(Style.style { builder ->
builder.color(TextColor.fromHexString(color))
builder.decorate(TextDecoration.STRIKETHROUGH)
})
}
renderer(object : Pagination.Renderer {
override fun renderNextPageButton(character: Char, style: Style, clickEvent: ClickEvent): Component {
LinearComponents.linear(
)
return Component.text()
.append(Component.space())
.append(Component.text("[", NamedTextColor.WHITE))
.append(Component.text(character, style.clickEvent(clickEvent)))
.append(Component.text("]", NamedTextColor.WHITE))
.append(Component.space())
.build()
}
}
private fun executeHelp(ctx: CommandContext<CommandSender>) {
minecraftHelp.queryCommands(ctx.getOrDefault("query", "")!!, ctx.sender)
}
private fun executeAbout(ctx: CommandContext<CommandSender>) {
val color = randomColor()
val text = "<hover:show_text:'<rainbow>click me!'><click:open_url:${announcerPlus.description.website}>${announcerPlus.name} <$color>${announcerPlus.description.version}"
val header = "<gradient:white:$color:white><strikethrough>${"-".repeat((announcerPlus.miniMessage.stripTokens(text).length / 1.2).roundToInt() + 4)}"
listOf(
header,
text,
"By <$color>jmp",
header
).forEach { chat.send(ctx.sender, chat.getCenteredMessage(it)) }
}
private fun executeReload(ctx: CommandContext<CommandSender>) {
chat.send(ctx.sender, chat.getCenteredMessage("<italic><gradient:${randomColor()}:${randomColor()}>Reloading ${announcerPlus.name} config..."))
try {
announcerPlus.reload()
chat.send(ctx.sender, chat.getCenteredMessage("<green>Done."))
} catch (e: Exception) {
chat.send(ctx.sender, "<gradient:red:gold>I'm sorry, but there was an error reloading the plugin. This is most likely due to misconfiguration. Check console for more info.")
e.printStackTrace()
override fun renderPreviousPageButton(
character: Char,
style: Style,
clickEvent: ClickEvent
): Component {
return Component.text()
.append(Component.space())
.append(Component.text("[", NamedTextColor.WHITE))
.append(Component.text(character, style.clickEvent(clickEvent)))
.append(Component.text("]", NamedTextColor.WHITE))
.append(Component.space())
.build()
}
}
})
nextButton { nextButton ->
nextButton.style(Style.style { builder ->
builder.decorate(TextDecoration.BOLD)
builder.color(TextColor.fromHexString(color))
builder.hoverEvent(HoverEvent.showText(Component.text("Next Page", NamedTextColor.GREEN)))
})
}
previousButton { prevButton ->
prevButton.style(Style.style { builder ->
builder.decorate(TextDecoration.BOLD)
builder.color(TextColor.fromHexString(color))
builder.hoverEvent(HoverEvent.showText(Component.text("Previous Page", NamedTextColor.RED)))
})
}
}.build<String>(
Component.text("Messages"),
{ value, _ -> Collections.singleton(value?.let { announcerPlus.miniMessage().parse(it) }) },
{ "/announcerplus list ${config.name} $it" }
)
private fun executeList(ctx: CommandContext<CommandSender>) {
val color = randomColor()
val config = ctx.get<MessageConfig>("config")
val page = ctx.getOrDefault("page", 1) ?: 1
val pagination = Pagination.builder().apply {
resultsPerPage(17)
width(53)
line { line ->
line.character('-')
line.style(Style.style { builder ->
builder.color(TextColor.fromHexString(color))
builder.decorate(TextDecoration.STRIKETHROUGH)
})
}
renderer(object : Pagination.Renderer {
override fun renderNextPageButton(character: Char, style: Style, clickEvent: ClickEvent): Component {
LinearComponents.linear(
)
return Component.text()
.append(Component.space())
.append(Component.text("[", NamedTextColor.WHITE))
.append(Component.text(character, style.clickEvent(clickEvent)))
.append(Component.text("]", NamedTextColor.WHITE))
.append(Component.space())
.build()
}
override fun renderPreviousPageButton(
character: Char,
style: Style,
clickEvent: ClickEvent
): Component {
return Component.text()
.append(Component.space())
.append(Component.text("[", NamedTextColor.WHITE))
.append(Component.text(character, style.clickEvent(clickEvent)))
.append(Component.text("]", NamedTextColor.WHITE))
.append(Component.space())
.build()
}
})
nextButton { nextButton ->
nextButton.style(Style.style { builder ->
builder.decorate(TextDecoration.BOLD)
builder.color(TextColor.fromHexString(color))
builder.hoverEvent(HoverEvent.showText(Component.text("Next Page", NamedTextColor.GREEN)))
})
}
previousButton { prevButton ->
prevButton.style(Style.style { builder ->
builder.decorate(TextDecoration.BOLD)
builder.color(TextColor.fromHexString(color))
builder.hoverEvent(HoverEvent.showText(Component.text("Previous Page", NamedTextColor.RED)))
})
}
}.build<String>(
Component.text("Messages"),
{ value, _ -> Collections.singleton(value?.let { announcerPlus.miniMessage.parse(it) }) },
{ "/announcerplus list ${config.name} $it" }
)
val messages = arrayListOf<String>()
for (msg in config.messages) {
for (line in msg.messageText) {
val b = StringBuilder()
if (msg.messageText.indexOf(line) == 0) {
b.append(" <color:$color>-</color:$color> ")
} else {
b.append(" <color:$color>-</color:$color> ")
}
b.append("<white>\"</white>$line<reset><white>\"")
messages.add(configManager.parse(ctx.sender, b.toString()))
}
val messages = arrayListOf<String>()
for (msg in config.messages) {
for (line in msg.messageText) {
val b = StringBuilder()
if (msg.messageText.indexOf(line) == 0) {
b.append(" <color:$color>-</color:$color> ")
} else {
b.append(" <color:$color>-</color:$color> ")
}
val l = arrayListOf<Component>(announcerPlus.miniMessage.parse("Config<gray>:</gray> <color:$color>${config.name}</color:$color> <gray><italic><hover:show_text:'<italic>Click to copy'><click:copy_to_clipboard:announcerplus.messages.${config.name}><white>(</white>announcerplus.messages.${config.name}<white>)</white>"))
l.addAll(pagination.render(messages, page))
chat.send(ctx.sender, l)
b.append("<white>\"</white>$line<reset><white>\"")
messages.add(configManager.parse(ctx.sender, b.toString()))
}
}
val l = arrayListOf<Component>(
announcerPlus.miniMessage()
.parse("Config<gray>:</gray> <color:$color>${config.name}</color:$color> <gray><italic><hover:show_text:'<italic>Click to copy'><click:copy_to_clipboard:announcerplus.messages.${config.name}><white>(</white>announcerplus.messages.${config.name}<white>)</white>")
)
l.addAll(pagination.render(messages, page))
chat.send(ctx.sender, l)
}
companion object {
private val colors = ImmutableSet.of(
"#f44336", "#e91e63", "#9c27b0", "#673ab7", "#3f51b5", "#2196f3", "#03a9f4", "#00bcd4", "#009688", "#4caf50",
"#8bc34a", "#cddc39", "#ffeb3b", "#ffc107", "#ff9800", "#ff5722", "#795548", "#9e9e9e", "#607d8b", "#333333")
fun randomColor(): String = colors.random()
}
companion object {
private val colors = ImmutableSet.of(
"#f44336", "#e91e63", "#9c27b0", "#673ab7", "#3f51b5", "#2196f3", "#03a9f4", "#00bcd4", "#009688", "#4caf50",
"#8bc34a", "#cddc39", "#ffeb3b", "#ffc107", "#ff9800", "#ff5722", "#795548", "#9e9e9e", "#607d8b", "#333333"
)
fun randomColor(): String = colors.random()
}
}
@@ -1,11 +1,32 @@
/*
* This file is part of AnnouncerPlus, licensed under the MIT License.
*
* Copyright (c) 2020-2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package xyz.jpenilla.announcerplus.command
import cloud.commandframework.arguments.standard.EnumArgument
import cloud.commandframework.arguments.standard.StringArgument
import cloud.commandframework.bukkit.parsers.MaterialArgument
import cloud.commandframework.context.CommandContext
import cloud.commandframework.kotlin.extension.commandBuilder
import cloud.commandframework.kotlin.extension.description
import net.kyori.adventure.bossbar.BossBar
import org.bukkit.command.CommandSender
import org.koin.core.inject
@@ -15,102 +36,99 @@ import xyz.jpenilla.announcerplus.config.message.ToastSettings
import xyz.jpenilla.announcerplus.task.ActionBarUpdateTask
import xyz.jpenilla.announcerplus.task.BossBarUpdateTask
import xyz.jpenilla.announcerplus.task.TitleUpdateTask
import xyz.jpenilla.announcerplus.util.description
import xyz.jpenilla.jmplib.Chat
class CommandBroadcast : BaseCommand {
private val commandManager: CommandManager by inject()
private val configManager: ConfigManager by inject()
private val argumentFactory: ArgumentFactory by inject()
private val announcerPlus: AnnouncerPlus by inject()
private val chat: Chat by inject()
private val commandManager: CommandManager by inject()
private val configManager: ConfigManager by inject()
private val argumentFactory: ArgumentFactory by inject()
private val announcerPlus: AnnouncerPlus by inject()
private val chat: Chat by inject()
override fun register() {
commandManager.commandBuilder("ap") {
registerCopy("broadcast") {
permission = "announcerplus.broadcast"
commandDescription("Parses and broadcasts a message to chat in the specified world or all worlds.")
argument(argumentFactory.worldPlayers("world"))
argument(StringArgument.greedy("message"))
handler(::executeBroadcast)
}
if (announcerPlus.toastTask != null) {
registerCopy("broadcasttoast") {
permission = "announcerplus.broadcasttoast"
commandDescription("Parses and broadcasts a Toast style message to the specified world or all worlds.")
argument(argumentFactory.worldPlayers("world"))
argument(MaterialArgument.of("icon"))
argument(EnumArgument.of(ToastSettings.FrameType::class.java, "frame"))
argument(StringArgument.quoted("header"), description("Quoted String"))
argument(StringArgument.quoted("body"), description("Quoted String"))
handler(::executeBroadcastToast)
}
}
registerCopy("broadcasttitle") {
permission = "announcerplus.broadcasttitle"
commandDescription("Parses and broadcasts a Title and Subtitle style message to the specified world or all worlds.")
argument(argumentFactory.worldPlayers("world"))
argument(argumentFactory.positiveInteger("seconds"))
argument(StringArgument.quoted("title"), description("Quoted String"))
argument(StringArgument.quoted("subtitle"), description("Quoted String"))
handler(::executeBroadcastTitle)
}
registerCopy("broadcastactionbar") {
permission = "announcerplus.broadcastactionbar"
commandDescription("Parses and broadcasts an Action Bar style message to the specified world or all worlds.")
argument(argumentFactory.worldPlayers("world"))
argument(argumentFactory.positiveInteger("seconds"))
argument(StringArgument.greedy("text"))
handler(::executeBroadcastActionBar)
}
registerCopy("broadcastbossbar") {
permission = "announcerplus.broadcastbossbar"
commandDescription("Parses and broadcasts a Boss Bar style message to the specified world or all worlds.")
argument(argumentFactory.worldPlayers("world"))
argument(argumentFactory.positiveInteger("seconds"))
argument(EnumArgument.of(BossBar.Overlay::class.java, "overlay"))
argument(EnumArgument.of(BossBarUpdateTask.FillMode::class.java, "fillmode"))
argument(EnumArgument.of(BossBar.Color::class.java, "color"))
argument(StringArgument.greedy("text"))
handler(::executeBroadcastBossBar)
}
}
override fun register() {
commandManager.registerSubcommand("broadcast") {
permission = "announcerplus.command.broadcast.chat"
commandDescription("Parses and broadcasts a message to chat in the specified world or all worlds.")
argument(argumentFactory.worldPlayers("world"))
argument(StringArgument.greedy("message"))
handler(::executeBroadcast)
}
commandManager.registerSubcommand("broadcasttoast", announcerPlus.toastTask != null) {
permission = "announcerplus.command.broadcast.toast"
commandDescription("Parses and broadcasts a Toast style message to the specified world or all worlds.")
argument(argumentFactory.worldPlayers("world"))
argument(MaterialArgument.of("icon"))
argument(EnumArgument.of(ToastSettings.FrameType::class.java, "frame"))
argument(StringArgument.quoted("header"), description("Quoted String"))
argument(StringArgument.quoted("body"), description("Quoted String"))
handler(::executeBroadcastToast)
}
commandManager.registerSubcommand("broadcasttitle") {
permission = "announcerplus.command.broadcast.title"
commandDescription("Parses and broadcasts a Title and Subtitle style message to the specified world or all worlds.")
argument(argumentFactory.worldPlayers("world"))
argument(argumentFactory.positiveInteger("seconds"))
argument(StringArgument.quoted("title"), description("Quoted String"))
argument(StringArgument.quoted("subtitle"), description("Quoted String"))
handler(::executeBroadcastTitle)
}
commandManager.registerSubcommand("broadcastactionbar") {
permission = "announcerplus.command.broadcast.actionbar"
commandDescription("Parses and broadcasts an Action Bar style message to the specified world or all worlds.")
argument(argumentFactory.worldPlayers("world"))
argument(argumentFactory.positiveInteger("seconds"))
argument(StringArgument.greedy("text"))
handler(::executeBroadcastActionBar)
}
commandManager.registerSubcommand("broadcastbossbar") {
permission = "announcerplus.command.broadcast.bossbar"
commandDescription("Parses and broadcasts a Boss Bar style message to the specified world or all worlds.")
argument(argumentFactory.worldPlayers("world"))
argument(argumentFactory.positiveInteger("seconds"))
argument(EnumArgument.of(BossBar.Overlay::class.java, "overlay"))
argument(EnumArgument.of(BossBarUpdateTask.FillMode::class.java, "fillmode"))
argument(EnumArgument.of(BossBar.Color::class.java, "color"))
argument(StringArgument.greedy("text"))
handler(::executeBroadcastBossBar)
}
}
private fun executeBroadcast(ctx: CommandContext<CommandSender>) {
for (player in ctx.get<ArgumentFactory.WorldPlayers>("world").players) {
chat.send(player, configManager.parse(player, ctx.get<String>("message")))
}
private fun executeBroadcast(ctx: CommandContext<CommandSender>) {
for (player in ctx.get<ArgumentFactory.WorldPlayers>("world").players) {
chat.send(player, configManager.parse(player, ctx.get<String>("message")))
}
}
private fun executeBroadcastToast(ctx: CommandContext<CommandSender>) {
val toast = ToastSettings(ctx.get("icon"), ctx.get("frame"), ctx.get("header"), ctx.get("body"))
for (player in ctx.get<ArgumentFactory.WorldPlayers>("world").players) {
toast.displayIfEnabled(player)
}
private fun executeBroadcastToast(ctx: CommandContext<CommandSender>) {
val toast = ToastSettings(ctx.get("icon"), ctx.get("frame"), ctx.get("header"), ctx.get("body"))
for (player in ctx.get<ArgumentFactory.WorldPlayers>("world").players) {
toast.displayIfEnabled(player)
}
}
private fun executeBroadcastTitle(ctx: CommandContext<CommandSender>) {
for (player in ctx.get<ArgumentFactory.WorldPlayers>("world").players) {
TitleUpdateTask(player, 0, ctx.get("seconds"), 1, ctx.get("title"), ctx.get("subtitle")).start()
}
private fun executeBroadcastTitle(ctx: CommandContext<CommandSender>) {
for (player in ctx.get<ArgumentFactory.WorldPlayers>("world").players) {
TitleUpdateTask(player, 0, ctx.get("seconds"), 1, ctx.get("title"), ctx.get("subtitle")).start()
}
}
private fun executeBroadcastActionBar(ctx: CommandContext<CommandSender>) {
for (player in ctx.get<ArgumentFactory.WorldPlayers>("world").players) {
ActionBarUpdateTask(player, ctx.get<Int>("seconds") * 20L, true, ctx.get("text")).start()
}
private fun executeBroadcastActionBar(ctx: CommandContext<CommandSender>) {
for (player in ctx.get<ArgumentFactory.WorldPlayers>("world").players) {
ActionBarUpdateTask(player, ctx.get<Int>("seconds") * 20L, true, ctx.get("text")).start()
}
}
private fun executeBroadcastBossBar(ctx: CommandContext<CommandSender>) {
for (player in ctx.get<ArgumentFactory.WorldPlayers>("world").players) {
BossBarUpdateTask(
player,
ctx.get("seconds"),
ctx.get("overlay"),
ctx.get("fillmode"),
ctx.get<BossBar.Color>("color").toString(),
ctx.get("text")
).start()
}
private fun executeBroadcastBossBar(ctx: CommandContext<CommandSender>) {
for (player in ctx.get<ArgumentFactory.WorldPlayers>("world").players) {
BossBarUpdateTask(
player,
ctx.get("seconds"),
ctx.get("overlay"),
ctx.get("fillmode"),
ctx.get<BossBar.Color>("color").toString(),
ctx.get("text")
).start()
}
}
}
@@ -1,6 +1,32 @@
/*
* This file is part of AnnouncerPlus, licensed under the MIT License.
*
* Copyright (c) 2020-2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package xyz.jpenilla.announcerplus.command
import cloud.commandframework.bukkit.CloudBukkitCapabilities
import cloud.commandframework.execution.AsynchronousCommandExecutionCoordinator
import cloud.commandframework.kotlin.MutableCommandBuilder
import cloud.commandframework.kotlin.extension.commandBuilder
import cloud.commandframework.minecraft.extras.MinecraftExceptionHandler
import cloud.commandframework.minecraft.extras.MinecraftHelp
import cloud.commandframework.paper.PaperCommandManager
@@ -15,63 +41,80 @@ import xyz.jpenilla.announcerplus.util.Constants
import java.util.function.Function
class CommandManager(announcerPlus: AnnouncerPlus) : PaperCommandManager<CommandSender>(
announcerPlus,
AsynchronousCommandExecutionCoordinator.newBuilder<CommandSender>().build(),
Function.identity(),
Function.identity()
announcerPlus,
AsynchronousCommandExecutionCoordinator.newBuilder<CommandSender>().build(),
Function.identity(),
Function.identity()
) {
private val minecraftHelp = MinecraftHelp(
"/announcerplus help",
announcerPlus.audience::sender,
this
).apply {
helpColors = MinecraftHelp.HelpColors.of(
TextColor.color(0x00a3ff),
NamedTextColor.WHITE,
TextColor.color(0x284fff),
NamedTextColor.GRAY,
NamedTextColor.DARK_GRAY
)
setMessage(MinecraftHelp.MESSAGE_HELP_TITLE, "AnnouncerPlus Help")
private val minecraftHelp = MinecraftHelp(
"/announcerplus help",
announcerPlus.audiences()::sender,
this
).apply {
helpColors = MinecraftHelp.HelpColors.of(
TextColor.color(0x00a3ff),
NamedTextColor.WHITE,
TextColor.color(0x284fff),
NamedTextColor.GRAY,
NamedTextColor.DARK_GRAY
)
setMessage(MinecraftHelp.MESSAGE_HELP_TITLE, "AnnouncerPlus Help")
}
init {
MinecraftExceptionHandler<CommandSender>()
.withDefaultHandlers()
.withDecorator {
Component.text()
.append(Constants.CHAT_PREFIX)
.append(it)
.build()
}
.apply(this, announcerPlus.audiences()::sender)
if (queryCapability(CloudBukkitCapabilities.NATIVE_BRIGADIER)) {
this.registerBrigadier()
this.brigadierManager()?.setNativeNumberSuggestions(false)
announcerPlus.logger.info("Successfully registered Mojang Brigadier support for commands.")
}
init {
MinecraftExceptionHandler<CommandSender>()
.withDefaultHandlers()
.withDecorator {
Component.text()
.append(Constants.CHAT_PREFIX)
.append(it)
.build()
}
.apply(this, announcerPlus.audience::sender)
try {
this.registerBrigadier()
this.brigadierManager()?.setNativeNumberSuggestions(false)
announcerPlus.logger.info("Successfully registered Mojang Brigadier support for commands.")
} catch (ignored: Exception) {
}
try {
this.registerAsynchronousCompletions()
announcerPlus.logger.info("Successfully registered asynchronous command completion listener.")
} catch (ignored: Exception) {
}
loadKoinModules(module {
single { this@CommandManager }
single { minecraftHelp }
single { ArgumentFactory() }
})
listOf(
CommandAnnouncerPlus(),
CommandBroadcast(),
CommandSend(),
CommandParse()
).forEach(BaseCommand::register)
if (queryCapability(CloudBukkitCapabilities.ASYNCHRONOUS_COMPLETION)) {
this.registerAsynchronousCompletions()
announcerPlus.logger.info("Successfully registered asynchronous command completion listener.")
}
loadKoinModules(module {
single { this@CommandManager }
single { minecraftHelp }
single { ArgumentFactory() }
})
listOf(
CommandAnnouncerPlus(),
CommandBroadcast(),
CommandSend(),
CommandParse()
).forEach(BaseCommand::register)
}
fun rootBuilder(
lambda: MutableCommandBuilder<CommandSender>.() -> Unit = {}
) = this.commandBuilder("ap", aliases = arrayOf("announcerplus", "announcer"), lambda = lambda)
fun registerSubcommand(
literal: String,
lambda: MutableCommandBuilder<CommandSender>.() -> Unit
) = this.rootBuilder().literal(literal).apply(lambda).register()
fun registerSubcommand(
literal: String,
registrationPredicate: Boolean,
lambda: MutableCommandBuilder<CommandSender>.() -> Unit
) {
if (registrationPredicate) {
this.registerSubcommand(literal, lambda)
}
}
}
@@ -1,11 +1,32 @@
/*
* This file is part of AnnouncerPlus, licensed under the MIT License.
*
* Copyright (c) 2020-2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package xyz.jpenilla.announcerplus.command
import cloud.commandframework.arguments.standard.EnumArgument
import cloud.commandframework.arguments.standard.StringArgument
import cloud.commandframework.bukkit.parsers.MaterialArgument
import cloud.commandframework.context.CommandContext
import cloud.commandframework.kotlin.extension.commandBuilder
import cloud.commandframework.kotlin.extension.description
import net.kyori.adventure.bossbar.BossBar
import org.bukkit.command.CommandSender
import org.bukkit.entity.Player
@@ -16,107 +37,104 @@ import xyz.jpenilla.announcerplus.config.message.ToastSettings
import xyz.jpenilla.announcerplus.task.ActionBarUpdateTask
import xyz.jpenilla.announcerplus.task.BossBarUpdateTask
import xyz.jpenilla.announcerplus.task.TitleUpdateTask
import xyz.jpenilla.announcerplus.util.description
import xyz.jpenilla.jmplib.Chat
class CommandParse : BaseCommand {
private val commandManager: CommandManager by inject()
private val configManager: ConfigManager by inject()
private val argumentFactory: ArgumentFactory by inject()
private val announcerPlus: AnnouncerPlus by inject()
private val chat: Chat by inject()
private val commandManager: CommandManager by inject()
private val configManager: ConfigManager by inject()
private val argumentFactory: ArgumentFactory by inject()
private val announcerPlus: AnnouncerPlus by inject()
private val chat: Chat by inject()
override fun register() {
commandManager.commandBuilder("ap") {
registerCopy("parse") {
permission = "announcerplus.parse"
commandDescription("Parses a message and echoes it back.")
argument(StringArgument.greedy("message"))
handler(::executeParse)
}
if (announcerPlus.toastTask != null) {
registerCopy("parsetoast") {
permission = "announcerplus.parsetoast"
senderType<Player>()
commandDescription("Parses a Toast style message and displays it to the command sender.")
argument(MaterialArgument.of("icon"))
argument(EnumArgument.of(ToastSettings.FrameType::class.java, "frame"))
argument(StringArgument.quoted("header"), description("Quoted String"))
argument(StringArgument.quoted("body"), description("Quoted String"))
handler(::executeParseToast)
}
}
registerCopy("parsetitle") {
permission = "announcerplus.parsetitle"
senderType<Player>()
commandDescription("Parses a Title and Subtitle style message and displays it to the command sender.")
argument(argumentFactory.positiveInteger("seconds"))
argument(StringArgument.quoted("title"), description("Quoted String"))
argument(StringArgument.quoted("subtitle"), description("Quoted String"))
handler(::executeParseTitle)
}
registerCopy("parseactionbar") {
permission = "announcerplus.parseactionbar"
senderType<Player>()
commandDescription("Parses an Action Bar style message and displays it to the command sender.")
argument(argumentFactory.positiveInteger("seconds"))
argument(StringArgument.greedy("text"))
handler(::executeParseActionBar)
}
registerCopy("parsebossbar") {
permission = "announcerplus.parsebossbar"
senderType<Player>()
commandDescription("Parses a Boss Bar style message and displays it to the command sender.")
argument(argumentFactory.positiveInteger("seconds"))
argument(EnumArgument.of(BossBar.Overlay::class.java, "overlay"))
argument(EnumArgument.of(BossBarUpdateTask.FillMode::class.java, "fillmode"))
argument(EnumArgument.of(BossBar.Color::class.java, "color"))
argument(StringArgument.greedy("text"))
handler(::executeParseBossBar)
}
registerCopy("parseanimation") {
permission = "announcerplus.parseanimation"
senderType<Player>()
commandDescription("Parses a message with an animation and displays it to the command sender.")
argument(argumentFactory.positiveInteger("seconds"))
argument(StringArgument.greedy("message"))
handler(::executeParseAnimation)
}
}
override fun register() {
commandManager.registerSubcommand("parse") {
permission = "announcerplus.command.parse.chat"
commandDescription("Parses a message and echoes it back.")
argument(StringArgument.greedy("message"))
handler(::executeParse)
}
commandManager.registerSubcommand("parsetoast", announcerPlus.toastTask != null) {
permission = "announcerplus.command.parse.toast"
senderType<Player>()
commandDescription("Parses a Toast style message and displays it to the command sender.")
argument(MaterialArgument.of("icon"))
argument(EnumArgument.of(ToastSettings.FrameType::class.java, "frame"))
argument(StringArgument.quoted("header"), description("Quoted String"))
argument(StringArgument.quoted("body"), description("Quoted String"))
handler(::executeParseToast)
}
commandManager.registerSubcommand("parsetitle") {
permission = "announcerplus.command.parse.title"
senderType<Player>()
commandDescription("Parses a Title and Subtitle style message and displays it to the command sender.")
argument(argumentFactory.positiveInteger("seconds"))
argument(StringArgument.quoted("title"), description("Quoted String"))
argument(StringArgument.quoted("subtitle"), description("Quoted String"))
handler(::executeParseTitle)
}
commandManager.registerSubcommand("parseactionbar") {
permission = "announcerplus.command.parse.actionbar"
senderType<Player>()
commandDescription("Parses an Action Bar style message and displays it to the command sender.")
argument(argumentFactory.positiveInteger("seconds"))
argument(StringArgument.greedy("text"))
handler(::executeParseActionBar)
}
commandManager.registerSubcommand("parsebossbar") {
permission = "announcerplus.command.parse.bossbar"
senderType<Player>()
commandDescription("Parses a Boss Bar style message and displays it to the command sender.")
argument(argumentFactory.positiveInteger("seconds"))
argument(EnumArgument.of(BossBar.Overlay::class.java, "overlay"))
argument(EnumArgument.of(BossBarUpdateTask.FillMode::class.java, "fillmode"))
argument(EnumArgument.of(BossBar.Color::class.java, "color"))
argument(StringArgument.greedy("text"))
handler(::executeParseBossBar)
}
commandManager.registerSubcommand("parseanimation") {
permission = "announcerplus.command.parse.animation"
senderType<Player>()
commandDescription("Parses a message with an animation and displays it to the command sender.")
argument(argumentFactory.positiveInteger("seconds"))
argument(StringArgument.greedy("message"))
handler(::executeParseAnimation)
}
}
private fun executeParse(ctx: CommandContext<CommandSender>) {
chat.send(ctx.sender, configManager.parse(ctx.sender, ctx.get<String>("message")))
}
private fun executeParse(ctx: CommandContext<CommandSender>) {
chat.send(ctx.sender, configManager.parse(ctx.sender, ctx.get<String>("message")))
}
private fun executeParseToast(ctx: CommandContext<CommandSender>) {
val toast = ToastSettings(ctx.get("icon"), ctx.get("frame"), ctx.get("header"), ctx.get("body"))
toast.displayIfEnabled(ctx.sender as Player)
}
private fun executeParseToast(ctx: CommandContext<CommandSender>) {
val toast = ToastSettings(ctx.get("icon"), ctx.get("frame"), ctx.get("header"), ctx.get("body"))
toast.displayIfEnabled(ctx.sender as Player)
}
private fun executeParseTitle(ctx: CommandContext<CommandSender>) {
TitleUpdateTask(ctx.sender as Player, 0, ctx.get("seconds"), 1, ctx.get("title"), ctx.get("subtitle")).start()
}
private fun executeParseTitle(ctx: CommandContext<CommandSender>) {
TitleUpdateTask(ctx.sender as Player, 0, ctx.get("seconds"), 1, ctx.get("title"), ctx.get("subtitle")).start()
}
private fun executeParseActionBar(ctx: CommandContext<CommandSender>) {
ActionBarUpdateTask(ctx.sender as Player, ctx.get<Int>("seconds") * 20L, true, ctx.get("text")).start()
}
private fun executeParseActionBar(ctx: CommandContext<CommandSender>) {
ActionBarUpdateTask(ctx.sender as Player, ctx.get<Int>("seconds") * 20L, true, ctx.get("text")).start()
}
private fun executeParseBossBar(ctx: CommandContext<CommandSender>) {
BossBarUpdateTask(
ctx.sender as Player,
ctx.get("seconds"),
ctx.get("overlay"),
ctx.get("fillmode"),
ctx.get<BossBar.Color>("color").toString(),
ctx.get("text")
).start()
}
private fun executeParseBossBar(ctx: CommandContext<CommandSender>) {
BossBarUpdateTask(
ctx.sender as Player,
ctx.get("seconds"),
ctx.get("overlay"),
ctx.get("fillmode"),
ctx.get<BossBar.Color>("color").toString(),
ctx.get("text")
).start()
}
private fun executeParseAnimation(ctx: CommandContext<CommandSender>) {
val player = ctx.sender as Player
val seconds = ctx.get<Int>("seconds")
val message = ctx.get<String>("message")
TitleUpdateTask(player, 0, seconds, 0, message, message).start()
ActionBarUpdateTask(player, seconds * 20L, false, message).start()
}
private fun executeParseAnimation(ctx: CommandContext<CommandSender>) {
val player = ctx.sender as Player
val seconds = ctx.get<Int>("seconds")
val message = ctx.get<String>("message")
TitleUpdateTask(player, 0, seconds, 0, message, message).start()
ActionBarUpdateTask(player, seconds * 20L, false, message).start()
}
}
@@ -1,3 +1,26 @@
/*
* This file is part of AnnouncerPlus, licensed under the MIT License.
*
* Copyright (c) 2020-2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package xyz.jpenilla.announcerplus.command
import cloud.commandframework.arguments.standard.EnumArgument
@@ -6,8 +29,6 @@ import cloud.commandframework.bukkit.arguments.selector.MultiplePlayerSelector
import cloud.commandframework.bukkit.parsers.MaterialArgument
import cloud.commandframework.bukkit.parsers.selector.MultiplePlayerSelectorArgument
import cloud.commandframework.context.CommandContext
import cloud.commandframework.kotlin.extension.commandBuilder
import cloud.commandframework.kotlin.extension.description
import net.kyori.adventure.bossbar.BossBar
import org.bukkit.command.CommandSender
import org.koin.core.inject
@@ -17,102 +38,99 @@ import xyz.jpenilla.announcerplus.config.message.ToastSettings
import xyz.jpenilla.announcerplus.task.ActionBarUpdateTask
import xyz.jpenilla.announcerplus.task.BossBarUpdateTask
import xyz.jpenilla.announcerplus.task.TitleUpdateTask
import xyz.jpenilla.announcerplus.util.description
import xyz.jpenilla.jmplib.Chat
class CommandSend : BaseCommand {
private val commandManager: CommandManager by inject()
private val configManager: ConfigManager by inject()
private val argumentFactory: ArgumentFactory by inject()
private val announcerPlus: AnnouncerPlus by inject()
private val chat: Chat by inject()
private val commandManager: CommandManager by inject()
private val configManager: ConfigManager by inject()
private val argumentFactory: ArgumentFactory by inject()
private val announcerPlus: AnnouncerPlus by inject()
private val chat: Chat by inject()
override fun register() {
commandManager.commandBuilder("ap") {
registerCopy("send") {
permission = "announcerplus.send"
commandDescription("Parses a message and sends it to the specified players.")
argument(MultiplePlayerSelectorArgument.of("players"))
argument(StringArgument.greedy("message"))
handler(::executeSend)
}
if (announcerPlus.toastTask != null) {
registerCopy("sendtoast") {
permission = "announcerplus.sendtoast"
commandDescription("Parses and sends a Toast style message to the specified players.")
argument(MultiplePlayerSelectorArgument.of("players"))
argument(MaterialArgument.of("icon"))
argument(EnumArgument.of(ToastSettings.FrameType::class.java, "frame"))
argument(StringArgument.quoted("header"), description("Quoted String"))
argument(StringArgument.quoted("body"), description("Quoted String"))
handler(::executeSendToast)
}
}
registerCopy("sendtitle") {
permission = "announcerplus.sendtitle"
commandDescription("Parses and sends a Title and Subtitle style message to the specified players.")
argument(MultiplePlayerSelectorArgument.of("players"))
argument(argumentFactory.positiveInteger("seconds"))
argument(StringArgument.quoted("title"), description("Quoted String"))
argument(StringArgument.quoted("subtitle"), description("Quoted String"))
handler(::executeSendTitle)
}
registerCopy("sendactionbar") {
permission = "announcerplus.sendactionbar"
commandDescription("Parses and sends an Action Bar style message to the specified players.")
argument(MultiplePlayerSelectorArgument.of("players"))
argument(argumentFactory.positiveInteger("seconds"))
argument(StringArgument.greedy("text"))
handler(::executeSendActionBar)
}
registerCopy("sendbossbar") {
permission = "announcerplus.sendbossbar"
commandDescription("Parses and sends a Boss Bar style message to the specified players.")
argument(MultiplePlayerSelectorArgument.of("players"))
argument(argumentFactory.positiveInteger("seconds"))
argument(EnumArgument.of(BossBar.Overlay::class.java, "overlay"))
argument(EnumArgument.of(BossBarUpdateTask.FillMode::class.java, "fillmode"))
argument(EnumArgument.of(BossBar.Color::class.java, "color"))
argument(StringArgument.greedy("text"))
handler(::executeSendBossBar)
}
}
override fun register() {
commandManager.registerSubcommand("send") {
permission = "announcerplus.command.send.chat"
commandDescription("Parses a message and sends it to the specified players.")
argument(MultiplePlayerSelectorArgument.of("players"))
argument(StringArgument.greedy("message"))
handler(::executeSend)
}
commandManager.registerSubcommand("sendtoast", announcerPlus.toastTask != null) {
permission = "announcerplus.command.send.toast"
commandDescription("Parses and sends a Toast style message to the specified players.")
argument(MultiplePlayerSelectorArgument.of("players"))
argument(MaterialArgument.of("icon"))
argument(EnumArgument.of(ToastSettings.FrameType::class.java, "frame"))
argument(StringArgument.quoted("header"), description("Quoted String"))
argument(StringArgument.quoted("body"), description("Quoted String"))
handler(::executeSendToast)
}
commandManager.registerSubcommand("sendtitle") {
permission = "announcerplus.command.send.title"
commandDescription("Parses and sends a Title and Subtitle style message to the specified players.")
argument(MultiplePlayerSelectorArgument.of("players"))
argument(argumentFactory.positiveInteger("seconds"))
argument(StringArgument.quoted("title"), description("Quoted String"))
argument(StringArgument.quoted("subtitle"), description("Quoted String"))
handler(::executeSendTitle)
}
commandManager.registerSubcommand("sendactionbar") {
permission = "announcerplus.command.send.actionbar"
commandDescription("Parses and sends an Action Bar style message to the specified players.")
argument(MultiplePlayerSelectorArgument.of("players"))
argument(argumentFactory.positiveInteger("seconds"))
argument(StringArgument.greedy("text"))
handler(::executeSendActionBar)
}
commandManager.registerSubcommand("sendbossbar") {
permission = "announcerplus.command.send.bossbar"
commandDescription("Parses and sends a Boss Bar style message to the specified players.")
argument(MultiplePlayerSelectorArgument.of("players"))
argument(argumentFactory.positiveInteger("seconds"))
argument(EnumArgument.of(BossBar.Overlay::class.java, "overlay"))
argument(EnumArgument.of(BossBarUpdateTask.FillMode::class.java, "fillmode"))
argument(EnumArgument.of(BossBar.Color::class.java, "color"))
argument(StringArgument.greedy("text"))
handler(::executeSendBossBar)
}
}
private fun executeSend(ctx: CommandContext<CommandSender>) {
for (player in ctx.get<MultiplePlayerSelector>("players").players) {
chat.send(player, configManager.parse(player, ctx.get<String>("message")))
}
private fun executeSend(ctx: CommandContext<CommandSender>) {
for (player in ctx.get<MultiplePlayerSelector>("players").players) {
chat.send(player, configManager.parse(player, ctx.get<String>("message")))
}
}
private fun executeSendToast(ctx: CommandContext<CommandSender>) {
val toast = ToastSettings(ctx.get("icon"), ctx.get("frame"), ctx.get("header"), ctx.get("body"))
for (player in ctx.get<MultiplePlayerSelector>("players").players) {
toast.displayIfEnabled(player)
}
private fun executeSendToast(ctx: CommandContext<CommandSender>) {
val toast = ToastSettings(ctx.get("icon"), ctx.get("frame"), ctx.get("header"), ctx.get("body"))
for (player in ctx.get<MultiplePlayerSelector>("players").players) {
toast.displayIfEnabled(player)
}
}
private fun executeSendTitle(ctx: CommandContext<CommandSender>) {
for (player in ctx.get<MultiplePlayerSelector>("players").players) {
TitleUpdateTask(player, 0, ctx.get("seconds"), 1, ctx.get("title"), ctx.get("subtitle")).start()
}
private fun executeSendTitle(ctx: CommandContext<CommandSender>) {
for (player in ctx.get<MultiplePlayerSelector>("players").players) {
TitleUpdateTask(player, 0, ctx.get("seconds"), 1, ctx.get("title"), ctx.get("subtitle")).start()
}
}
private fun executeSendActionBar(ctx: CommandContext<CommandSender>) {
for (player in ctx.get<MultiplePlayerSelector>("players").players) {
ActionBarUpdateTask(player, ctx.get<Int>("seconds") * 20L, true, ctx.get("text")).start()
}
private fun executeSendActionBar(ctx: CommandContext<CommandSender>) {
for (player in ctx.get<MultiplePlayerSelector>("players").players) {
ActionBarUpdateTask(player, ctx.get<Int>("seconds") * 20L, true, ctx.get("text")).start()
}
}
private fun executeSendBossBar(ctx: CommandContext<CommandSender>) {
for (player in ctx.get<MultiplePlayerSelector>("players").players) {
BossBarUpdateTask(
player,
ctx.get("seconds"),
ctx.get("overlay"),
ctx.get("fillmode"),
ctx.get<BossBar.Color>("color").toString(),
ctx.get("text")
).start()
}
private fun executeSendBossBar(ctx: CommandContext<CommandSender>) {
for (player in ctx.get<MultiplePlayerSelector>("players").players) {
BossBarUpdateTask(
player,
ctx.get("seconds"),
ctx.get("overlay"),
ctx.get("fillmode"),
ctx.get<BossBar.Color>("color").toString(),
ctx.get("text")
).start()
}
}
}
@@ -1,22 +0,0 @@
package xyz.jpenilla.announcerplus.compatability
import net.ess3.api.IEssentials
import net.ess3.api.IUser
import org.bukkit.Bukkit
import org.bukkit.entity.Player
class EssentialsHook {
val essentials = Bukkit.getServer().pluginManager.getPlugin("Essentials") as IEssentials
fun isAfk(player: Player): Boolean {
return user(player).isAfk
}
fun isVanished(player: Player): Boolean {
return user(player).isVanished
}
fun user(player: Player): IUser {
return essentials.getUser(player)
}
}
@@ -0,0 +1,45 @@
/*
* This file is part of AnnouncerPlus, licensed under the MIT License.
*
* Copyright (c) 2020-2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package xyz.jpenilla.announcerplus.compatibility
import net.ess3.api.IEssentials
import net.ess3.api.IUser
import org.bukkit.Bukkit
import org.bukkit.entity.Player
class EssentialsHook {
val essentials = Bukkit.getServer().pluginManager.getPlugin("Essentials") as IEssentials
fun isAfk(player: Player): Boolean {
return user(player).isAfk
}
fun isVanished(player: Player): Boolean {
return user(player).isVanished
}
fun user(player: Player): IUser {
return essentials.getUser(player)
}
}
@@ -1,3 +1,26 @@
/*
* This file is part of AnnouncerPlus, licensed under the MIT License.
*
* Copyright (c) 2020-2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package xyz.jpenilla.announcerplus.config
import org.bukkit.command.CommandSender
@@ -14,71 +37,73 @@ import java.nio.file.Files
import kotlin.streams.toList
class ConfigManager(private val announcerPlus: AnnouncerPlus) {
private val serializers: TypeSerializerCollection
private val configOptions: ConfigurationOptions
private val serializers: TypeSerializerCollection
private val configOptions: ConfigurationOptions
private val mainConfigPath = announcerPlus.dataFolder.toPath().resolve("main.conf")
private val mainConfigLoader: HoconConfigurationLoader
lateinit var mainConfig: MainConfig
private val mainConfigPath = announcerPlus.dataFolder.toPath().resolve("main.conf")
private val mainConfigLoader: HoconConfigurationLoader
lateinit var mainConfig: MainConfig
private val firstJoinConfigPath = announcerPlus.dataFolder.toPath().resolve("first-join.conf")
private val firstJoinConfigLoader: HoconConfigurationLoader
lateinit var firstJoinConfig: JoinQuitConfig
private val firstJoinConfigPath = announcerPlus.dataFolder.toPath().resolve("first-join.conf")
private val firstJoinConfigLoader: HoconConfigurationLoader
lateinit var firstJoinConfig: JoinQuitConfig
private val mapperFactory = ObjectMapper.factoryBuilder()
.addNodeResolver { name, _ ->
/* We don't want to attempt serializing delegated properties, and they can't be @Transient */
if (name.endsWith("delegate")) {
NodeResolver.SKIP_FIELD
} else {
null
}
}.build()
private val mapperFactory = ObjectMapper.factoryBuilder()
.addNodeResolver { name, _ ->
/* We don't want to attempt serializing delegated properties, and they can't be @Transient */
if (name.endsWith("delegate")) {
NodeResolver.SKIP_FIELD
} else {
null
}
}.build()
val messageConfigs = hashMapOf<String, MessageConfig>()
val joinQuitConfigs = hashMapOf<String, JoinQuitConfig>()
val messageConfigs = hashMapOf<String, MessageConfig>()
val joinQuitConfigs = hashMapOf<String, JoinQuitConfig>()
init {
serializers = TypeSerializerCollection.defaults().childBuilder()
.registerAnnotatedObjects(mapperFactory)
.build()
init {
serializers = TypeSerializerCollection.defaults().childBuilder()
.registerAnnotatedObjects(mapperFactory)
.build()
configOptions = ConfigurationOptions.defaults().serializers(serializers)
configOptions = ConfigurationOptions.defaults().serializers(serializers)
mainConfigLoader = HoconConfigurationLoader.builder().path(mainConfigPath).build()
firstJoinConfigLoader = HoconConfigurationLoader.builder().path(firstJoinConfigPath).build()
mainConfigLoader = HoconConfigurationLoader.builder().path(mainConfigPath).build()
firstJoinConfigLoader = HoconConfigurationLoader.builder().path(firstJoinConfigPath).build()
reload()
reload()
}
fun reload() {
load()
save()
}
private fun load() {
Files.createDirectories(announcerPlus.dataFolder.toPath())
val mainConfigRoot = mainConfigLoader.load(configOptions)
try {
mainConfig = MainConfig.loadFrom(mainConfigRoot)
} catch (e: Exception) {
throw InvalidConfigurationException("Failed to load the main.conf config file. This is due to misconfiguration", e)
}
fun reload() {
load()
save()
val firstJoinConfigRoot = firstJoinConfigLoader.load(configOptions)
try {
firstJoinConfig = JoinQuitConfig.loadFrom(firstJoinConfigRoot, null)
} catch (e: Exception) {
throw InvalidConfigurationException("Failed to load the main.conf config file. This is due to misconfiguration", e)
}
private fun load() {
Files.createDirectories(announcerPlus.dataFolder.toPath())
loadMessageConfigs()
loadJoinQuitConfigs()
}
val mainConfigRoot = mainConfigLoader.load(configOptions)
try {
mainConfig = MainConfig.loadFrom(mainConfigRoot)
} catch (e: Exception) {
throw InvalidConfigurationException("Failed to load the main.conf config file. This is due to misconfiguration", e)
}
val firstJoinConfigRoot = firstJoinConfigLoader.load(configOptions)
try {
firstJoinConfig = JoinQuitConfig.loadFrom(firstJoinConfigRoot, null)
} catch (e: Exception) {
throw InvalidConfigurationException("Failed to load the main.conf config file. This is due to misconfiguration", e)
}
loadMessageConfigs()
loadJoinQuitConfigs()
}
private fun save() {
val mainConfigRoot = mainConfigLoader.createNode(configOptions.header("""
private fun save() {
val mainConfigRoot = mainConfigLoader.createNode(
configOptions.header(
"""
| ___ ____ __ __
| / | ____ ____ ____ __ ______ ________ _____/ __ \/ /_ _______ __/ /_
| / /| | / __ \/ __ \/ __ \/ / / / __ \/ ___/ _ \/ ___/ /_/ / / / / / ___/ /_ __/
@@ -86,98 +111,108 @@ class ConfigManager(private val announcerPlus: AnnouncerPlus) {
| /_/ |_/_/ /_/_/ /_/\____/\__,_/_/ /_/\___/\___/_/ /_/ /_/\__,_/____/
|
| v${announcerPlus.description.version}
""".trimMargin()))
mainConfig.saveTo(mainConfigRoot)
mainConfigLoader.save(mainConfigRoot)
""".trimMargin()
)
)
mainConfig.saveTo(mainConfigRoot)
mainConfigLoader.save(mainConfigRoot)
val firstJoinConfigRoot = firstJoinConfigLoader.createNode(configOptions.header(
"If enabled in main.conf, this join config will be used when players join the server for the first time.\n" +
"All other join configs will be skipped for first-join if this is enabled."
))
firstJoinConfig.saveTo(firstJoinConfigRoot)
firstJoinConfigRoot.removeChild("quit-section")
firstJoinConfigLoader.save(firstJoinConfigRoot)
val firstJoinConfigRoot = firstJoinConfigLoader.createNode(
configOptions.header(
"If enabled in main.conf, this join config will be used when players join the server for the first time.\n" +
"All other join configs will be skipped for first-join if this is enabled."
)
)
firstJoinConfig.saveTo(firstJoinConfigRoot)
firstJoinConfigRoot.removeChild("quit-section")
firstJoinConfigLoader.save(firstJoinConfigRoot)
}
private fun loadJoinQuitConfigs() {
joinQuitConfigs.clear()
val path = announcerPlus.dataFolder.toPath().resolve("join-quit-configs")
if (!Files.exists(path)) {
announcerPlus.logger.info("Creating join quit config folder")
Files.createDirectories(path)
}
if (Files.list(path).toList().isEmpty()) {
announcerPlus.logger.info("No join/quit configs found, creating default.conf")
private fun loadJoinQuitConfigs() {
joinQuitConfigs.clear()
val path = announcerPlus.dataFolder.toPath().resolve("join-quit-configs")
if (!Files.exists(path)) {
announcerPlus.logger.info("Creating join quit config folder")
Files.createDirectories(path)
}
if (Files.list(path).toList().isEmpty()) {
announcerPlus.logger.info("No join/quit configs found, creating default.conf")
val defaultConfig = path.resolve("default.conf")
val defaultConfigLoader = HoconConfigurationLoader.builder().path(defaultConfig).build()
val defaultConfigRoot = defaultConfigLoader.createNode(configOptions.header(
"To give a player these join/quit messages give them the announcerplus.join.default\n" +
" and announcerplus.quit.default permissions"))
JoinQuitConfig().saveTo(defaultConfigRoot)
defaultConfigLoader.save(defaultConfigRoot)
}
Files.list(path).forEach {
val configLoader = HoconConfigurationLoader.builder().path(it).build()
val name = it.toFile().nameWithoutExtension
try {
val root = configLoader.load(configOptions)
joinQuitConfigs[name] = JoinQuitConfig.loadFrom(root, name)
joinQuitConfigs[name]?.saveTo(root)
configLoader.save(root)
} catch (e: Exception) {
throw InvalidConfigurationException("Failed to load message config: ${it.fileName}. This is due to an invalid config file.", e)
}
}
val defaultConfig = path.resolve("default.conf")
val defaultConfigLoader = HoconConfigurationLoader.builder().path(defaultConfig).build()
val defaultConfigRoot = defaultConfigLoader.createNode(
configOptions.header(
"To give a player these join/quit messages give them the announcerplus.join.default\n" +
" and announcerplus.quit.default permissions"
)
)
JoinQuitConfig().saveTo(defaultConfigRoot)
defaultConfigLoader.save(defaultConfigRoot)
}
Files.list(path).forEach {
val configLoader = HoconConfigurationLoader.builder().path(it).build()
val name = it.toFile().nameWithoutExtension
try {
val root = configLoader.load(configOptions)
joinQuitConfigs[name] = JoinQuitConfig.loadFrom(root, name)
private fun loadMessageConfigs() {
messageConfigs.values.forEach(MessageConfig::stop)
messageConfigs.clear()
val path = announcerPlus.dataFolder.toPath().resolve("message-configs")
if (!Files.exists(path)) {
announcerPlus.logger.info("Creating message config folder")
Files.createDirectories(path)
}
if (Files.list(path).toList().isEmpty()) {
announcerPlus.logger.info("No message configs found, creating demo.conf")
joinQuitConfigs[name]?.saveTo(root)
configLoader.save(root)
} catch (e: Exception) {
throw InvalidConfigurationException("Failed to load message config: ${it.fileName}. This is due to an invalid config file.", e)
}
}
}
val defaultConfig = path.resolve("demo.conf")
val defaultConfigLoader = HoconConfigurationLoader.builder().path(defaultConfig).build()
val defaultConfigRoot = defaultConfigLoader.createNode(configOptions.header(
"""For a player to get these messages give them the announcerplus.messages.demo permission
private fun loadMessageConfigs() {
messageConfigs.values.forEach(MessageConfig::stop)
messageConfigs.clear()
val path = announcerPlus.dataFolder.toPath().resolve("message-configs")
if (!Files.exists(path)) {
announcerPlus.logger.info("Creating message config folder")
Files.createDirectories(path)
}
if (Files.list(path).toList().isEmpty()) {
announcerPlus.logger.info("No message configs found, creating demo.conf")
val defaultConfig = path.resolve("demo.conf")
val defaultConfigLoader = HoconConfigurationLoader.builder().path(defaultConfig).build()
val defaultConfigRoot = defaultConfigLoader.createNode(
configOptions.header(
"""For a player to get these messages give them the announcerplus.messages.demo permission
| If EssentialsX is installed, then giving a player the announcerplus.messages.demo.afk permission
| will stop them from receiving these messages while afk""".trimMargin()))
MessageConfig().saveTo(defaultConfigRoot)
defaultConfigLoader.save(defaultConfigRoot)
}
Files.list(path).forEach {
val configLoader = HoconConfigurationLoader.builder().path(it).build()
val name = it.toFile().nameWithoutExtension
try {
val root = configLoader.load(configOptions)
messageConfigs[name] = MessageConfig.loadFrom(root, name)
| will stop them from receiving these messages while afk""".trimMargin()
)
)
MessageConfig().saveTo(defaultConfigRoot)
defaultConfigLoader.save(defaultConfigRoot)
}
Files.list(path).forEach {
val configLoader = HoconConfigurationLoader.builder().path(it).build()
val name = it.toFile().nameWithoutExtension
try {
val root = configLoader.load(configOptions)
messageConfigs[name] = MessageConfig.loadFrom(root, name)
messageConfigs[name]?.saveTo(root)
configLoader.save(root)
} catch (e: Exception) {
throw InvalidConfigurationException("Failed to load join/quit config: ${it.fileName}. This is due to an invalid config file.", e)
}
}
messageConfigs[name]?.saveTo(root)
configLoader.save(root)
} catch (e: Exception) {
throw InvalidConfigurationException("Failed to load join/quit config: ${it.fileName}. This is due to an invalid config file.", e)
}
}
}
fun parse(player: CommandSender?, message: String): String {
val p = player as? Player
val msg = announcerPlus.chat().parse(p, message, mainConfig.customPlaceholders)
if (msg.startsWith("<center>")) {
return announcerPlus.chat().getCenteredMessage(msg.replace("<center>", ""))
}
fun parse(player: CommandSender?, message: String): String {
val p = player as? Player
return msg
}
val msg = announcerPlus.chat.parse(p, message, mainConfig.customPlaceholders)
if (msg.startsWith("<center>")) {
return announcerPlus.chat.getCenteredMessage(msg.replace("<center>", ""))
}
return msg
}
fun parse(player: CommandSender?, messages: List<String>): List<String> =
messages.map { parse(player, it) }
fun parse(player: CommandSender?, messages: List<String>): List<String> =
messages.map { parse(player, it) }
}
@@ -1,3 +1,26 @@
/*
* This file is part of AnnouncerPlus, licensed under the MIT License.
*
* Copyright (c) 2020-2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package xyz.jpenilla.announcerplus.config
import com.google.common.collect.ImmutableList
@@ -24,189 +47,190 @@ import xyz.jpenilla.announcerplus.util.dispatchCommandAsConsole
import xyz.jpenilla.announcerplus.util.runAsync
import xyz.jpenilla.announcerplus.util.runSync
import xyz.jpenilla.jmplib.Chat
import xyz.jpenilla.jmplib.Environment
@ConfigSerializable
class JoinQuitConfig : KoinComponent {
@Setting("visible-permission")
@Comment("If set to something other than \"\", this setting's value will be the permission required to see these join/quit messages when they are broadcasted for a player")
var permission = ""
@Setting("visible-permission")
@Comment("If set to something other than \"\", this setting's value will be the permission required to see these join/quit messages when they are broadcasted for a player")
var permission = ""
@Setting("join-section")
@Comment("Player Join related settings")
var join = JoinSection()
@Setting("join-section")
@Comment("Player Join related settings")
var join = JoinSection()
@Setting("quit-section")
@Comment("Player Quit related settings")
var quit = QuitSection()
@Setting("quit-section")
@Comment("Player Quit related settings")
var quit = QuitSection()
@ConfigSerializable
class JoinSection {
@Setting("randomize-join-sounds")
@Comment(Constants.CONFIG_COMMENT_SOUNDS_RANDOM)
var randomSound = true
@ConfigSerializable
class JoinSection {
@Setting("randomize-join-sounds")
@Comment(Constants.CONFIG_COMMENT_SOUNDS_RANDOM)
var randomSound = true
@Setting("randomize-join-broadcast-sounds")
@Comment(Constants.CONFIG_COMMENT_SOUNDS_RANDOM)
var randomBroadcastSound = true
@Setting("randomize-join-broadcast-sounds")
@Comment(Constants.CONFIG_COMMENT_SOUNDS_RANDOM)
var randomBroadcastSound = true
@Setting("join-sounds")
@Comment("These sound(s) will be played to the joining player.\n ${Constants.CONFIG_COMMENT_SOUNDS_LINE2}")
var sounds =
"minecraft:entity.strider.happy,minecraft:entity.villager.ambient,minecraft:block.note_block.cow_bell"
@Setting("join-sounds")
@Comment("These sound(s) will be played to the joining player.\n ${Constants.CONFIG_COMMENT_SOUNDS_LINE2}")
var sounds =
"minecraft:entity.strider.happy,minecraft:entity.villager.ambient,minecraft:block.note_block.cow_bell"
@Setting("join-broadcast-sounds")
@Comment("These sound(s) will be played to the joining player.\n ${Constants.CONFIG_COMMENT_SOUNDS_LINE2}")
var broadcastSounds = "minecraft:entity.enderman.teleport"
@Setting("join-broadcast-sounds")
@Comment("These sound(s) will be played to the joining player.\n ${Constants.CONFIG_COMMENT_SOUNDS_LINE2}")
var broadcastSounds = "minecraft:entity.enderman.teleport"
@Setting("join-messages")
@Comment("These messages will be sent to the joining Player. These messages are sometimes called a \"Message of the Day\" or a \"MotD\"")
val messages = arrayListOf(
"<hover:show_text:'<yellow>Username</yellow><gray>:</gray> {user}'>{nick}</hover> <yellow>joined the game",
"<center><rainbow><italic>Welcome,</rainbow> {user}<yellow>!",
"<center><gradient:black:white:black>------------------------------------</gradient>",
"This server is using <blue>Announcer<italic>Plus<reset>!",
"<gradient:green:white>Configure these messages by editing the config files!"
)
@Setting("join-messages")
@Comment("These messages will be sent to the joining Player. These messages are sometimes called a \"Message of the Day\" or a \"MotD\"")
val messages = arrayListOf(
"<hover:show_text:'<yellow>Username</yellow><gray>:</gray> {user}'>{nick}</hover> <yellow>joined the game",
"<center><rainbow><italic>Welcome,</rainbow> {user}<yellow>!",
"<center><gradient:black:white:black>------------------------------------</gradient>",
"This server is using <blue>Announcer<italic>Plus<reset>!",
"<gradient:green:white>Configure these messages by editing the config files!"
)
@Setting("join-broadcasts")
@Comment("These messages will be sent to every Player online except the joining Player. Also known as join messages.")
val broadcasts =
arrayListOf("<hover:show_text:'<yellow>Username</yellow><gray>:</gray> {user}'>{nick}</hover> <yellow>joined the game")
@Setting("join-broadcasts")
@Comment("These messages will be sent to every Player online except the joining Player. Also known as join messages.")
val broadcasts =
arrayListOf("<hover:show_text:'<yellow>Username</yellow><gray>:</gray> {user}'>{nick}</hover> <yellow>joined the game")
@Setting("join-commands")
@Comment("These commands will be run by the console on Player join.\n Example: \"minecraft:give %player_name% dirt\"")
val commands = arrayListOf<String>()
@Setting("join-commands")
@Comment("These commands will be run by the console on Player join.\n Example: \"minecraft:give %player_name% dirt\"")
val commands = arrayListOf<String>()
@Setting("as-player-join-commands")
@Comment("These commands will be run as the Player on Player join.\n Example: \"ap about\"")
val asPlayerCommands = arrayListOf<String>()
@Setting("as-player-join-commands")
@Comment("These commands will be run as the Player on Player join.\n Example: \"ap about\"")
val asPlayerCommands = arrayListOf<String>()
@Setting("title-settings")
@Comment("Settings relating to showing a title to the joining Player")
var title = TitleSettings(
1, 7, 1,
"<bold><italic><gradient:green:blue:green:{animate:scroll:0.1}>Welcome</gradient><yellow>{animate:flash:!:!!:!!!:10}",
"<{animate:pulse:red:blue:yellow:green:10}>{user}"
)
@Setting("title-settings")
@Comment("Settings relating to showing a title to the joining Player")
var title = TitleSettings(
1, 7, 1,
"<bold><italic><gradient:green:blue:green:{animate:scroll:0.1}>Welcome</gradient><yellow>{animate:flash:!:!!:!!!:10}",
"<{animate:pulse:red:blue:yellow:green:10}>{user}"
)
@Setting("action-bar-settings")
@Comment("Settings relating to showing an Action Bar to the joining Player")
var actionBar = ActionBarSettings(
false, 8,
"<gradient:green:blue:green:{animate:scroll:0.1}>|||||||||||||||||||||||||||||||||||||||</gradient>"
)
@Setting("action-bar-settings")
@Comment("Settings relating to showing an Action Bar to the joining Player")
var actionBar = ActionBarSettings(
false, 8,
"<gradient:green:blue:green:{animate:scroll:0.1}>|||||||||||||||||||||||||||||||||||||||</gradient>"
)
@Setting("boss-bar-settings")
@Comment("Settings relating to showing a Boss Bar to the joining Player")
var bossBar = BossBarSettings()
@Setting("boss-bar-settings")
@Comment("Settings relating to showing a Boss Bar to the joining Player")
var bossBar = BossBarSettings()
@Setting("toast-settings")
@Comment("Configure the Toast that will be showed to the joining player")
var toast = ToastSettings(
Material.DIAMOND, ToastSettings.FrameType.CHALLENGE,
"<gradient:green:blue><bold><italic>AnnouncerPlus", "<rainbow>Welcome to the server!"
)
@Setting("toast-settings")
@Comment("Configure the Toast that will be showed to the joining player")
var toast = ToastSettings(
Material.DIAMOND, ToastSettings.FrameType.CHALLENGE,
"<gradient:green:blue><bold><italic>AnnouncerPlus", "<rainbow>Welcome to the server!"
)
fun messageElements(): Collection<MessageElement> = ImmutableSet.of(
actionBar,
bossBar,
title,
toast
)
}
fun messageElements(): Collection<MessageElement> = ImmutableSet.of(
actionBar,
bossBar,
title,
toast
)
}
@ConfigSerializable
class QuitSection {
@Setting("randomize-quit-sounds")
@Comment(Constants.CONFIG_COMMENT_SOUNDS_RANDOM)
var randomSound = true
@ConfigSerializable
class QuitSection {
@Setting("randomize-quit-sounds")
@Comment(Constants.CONFIG_COMMENT_SOUNDS_RANDOM)
var randomSound = true
@Setting("quit-sounds")
@Comment("These sound(s) will be played to online players on player quit\n ${Constants.CONFIG_COMMENT_SOUNDS_LINE2}")
var sounds = "minecraft:entity.enderman.teleport"
@Setting("quit-sounds")
@Comment("These sound(s) will be played to online players on player quit\n ${Constants.CONFIG_COMMENT_SOUNDS_LINE2}")
var sounds = "minecraft:entity.enderman.teleport"
@Setting("quit-broadcasts")
@Comment("These messages will be sent to online players on player quit. Also known as quit messages")
val broadcasts =
arrayListOf("<hover:show_text:'<yellow>Username</yellow><gray>:</gray> {user}'>{nick}</hover> <yellow>left the game")
@Setting("quit-broadcasts")
@Comment("These messages will be sent to online players on player quit. Also known as quit messages")
val broadcasts =
arrayListOf("<hover:show_text:'<yellow>Username</yellow><gray>:</gray> {user}'>{nick}</hover> <yellow>left the game")
@Setting("quit-commands")
@Comment("These commands will be run by the console on Player quit.\n Example: \"broadcast %player_name% left\"")
val commands = arrayListOf<String>()
}
@Setting("quit-commands")
@Comment("These commands will be run by the console on Player quit.\n Example: \"broadcast %player_name% left\"")
val commands = arrayListOf<String>()
}
companion object {
private val MAPPER = ObjectMapper.factoryBuilder().addNodeResolver(NodeResolver.onlyWithSetting()).build()
.get(JoinQuitConfig::class.java)
companion object {
private val MAPPER = ObjectMapper.factoryBuilder().addNodeResolver(NodeResolver.onlyWithSetting()).build()
.get(JoinQuitConfig::class.java)
fun loadFrom(node: CommentedConfigurationNode, name: String?): JoinQuitConfig =
MAPPER.load(node).populate(name)
}
fun loadFrom(node: CommentedConfigurationNode, name: String?): JoinQuitConfig =
MAPPER.load(node).populate(name)
}
fun saveTo(node: CommentedConfigurationNode) =
MAPPER.save(this, node)
fun saveTo(node: CommentedConfigurationNode) =
MAPPER.save(this, node)
fun populate(name: String?): JoinQuitConfig =
this.apply { this.name = name }
fun populate(name: String?): JoinQuitConfig =
this.apply { this.name = name }
private val announcerPlus: AnnouncerPlus by inject()
private val chat: Chat by inject()
private var name: String? = null
private val announcerPlus: AnnouncerPlus by inject()
private val chat: Chat by inject()
private var name: String? = null
fun onJoin(player: Player) {
/* name is null for first-join config */
if (name != null && !player.hasPermission("announcerplus.join.$name")) return
chat.send(player, announcerPlus.configManager.parse(player, join.messages))
announcerPlus.runSync(3L) {
if (!isVanished(player)) {
val onlinePlayers = ImmutableList.copyOf(Bukkit.getOnlinePlayers())
announcerPlus.runAsync {
onlinePlayers.forEach { onlinePlayer ->
if (onlinePlayer.name != player.name) {
if (announcerPlus.perms!!.playerHas(onlinePlayer, permission) || permission.isEmpty()) {
chat.send(onlinePlayer, announcerPlus.configManager.parse(player, join.broadcasts))
if (join.broadcastSounds != "") {
chat.playSounds(onlinePlayer, join.randomBroadcastSound, join.broadcastSounds)
}
}
}
}
}
join.commands.forEach { dispatchCommandAsConsole(announcerPlus.configManager.parse(player, it)) }
join.asPlayerCommands.forEach { player.performCommand(announcerPlus.configManager.parse(player, it)) }
}
}
announcerPlus.runAsync(if (announcerPlus.majorMinecraftVersion <= 12) 5L else 0L) {
join.messageElements().forEach { it.displayIfEnabled(player) }
if (join.sounds != "") {
chat.playSounds(player, join.randomSound, join.sounds)
}
}
}
fun onQuit(player: Player) {
if (name == null || !player.hasPermission("announcerplus.quit.$name") || isVanished(player)) return
ImmutableList.copyOf(Bukkit.getOnlinePlayers()).forEach { onlinePlayer ->
fun onJoin(player: Player) {
/* name is null for first-join config */
if (name != null && !player.hasPermission("announcerplus.join.$name")) return
chat.send(player, announcerPlus.configManager.parse(player, join.messages))
announcerPlus.runSync(3L) {
if (!isVanished(player)) {
val onlinePlayers = ImmutableList.copyOf(Bukkit.getOnlinePlayers())
announcerPlus.runAsync {
onlinePlayers.forEach { onlinePlayer ->
if (onlinePlayer.name != player.name) {
if (announcerPlus.perms!!.playerHas(onlinePlayer, permission) || permission.isEmpty()) {
chat.send(onlinePlayer, announcerPlus.configManager.parse(player, quit.broadcasts))
if (quit.sounds.isNotEmpty()) {
chat.playSounds(onlinePlayer, quit.randomSound, quit.sounds)
}
if (announcerPlus.perms!!.playerHas(onlinePlayer, permission) || permission.isEmpty()) {
chat.send(onlinePlayer, announcerPlus.configManager.parse(player, join.broadcasts))
if (join.broadcastSounds != "") {
chat.playSounds(onlinePlayer, join.randomBroadcastSound, join.broadcastSounds)
}
}
}
}
}
quit.commands.forEach { dispatchCommandAsConsole(announcerPlus.configManager.parse(player, it)) }
join.commands.forEach { dispatchCommandAsConsole(announcerPlus.configManager.parse(player, it)) }
join.asPlayerCommands.forEach { player.performCommand(announcerPlus.configManager.parse(player, it)) }
}
}
announcerPlus.runAsync(if (Environment.majorMinecraftVersion() <= 12) 5L else 0L) {
join.messageElements().forEach { it.displayIfEnabled(player) }
if (join.sounds != "") {
chat.playSounds(player, join.randomSound, join.sounds)
}
}
}
private fun isVanished(player: Player): Boolean {
for (meta in player.getMetadata("vanished")) {
if (meta.asBoolean()) return true
fun onQuit(player: Player) {
if (name == null || !player.hasPermission("announcerplus.quit.$name") || isVanished(player)) return
ImmutableList.copyOf(Bukkit.getOnlinePlayers()).forEach { onlinePlayer ->
if (onlinePlayer.name != player.name) {
if (announcerPlus.perms!!.playerHas(onlinePlayer, permission) || permission.isEmpty()) {
chat.send(onlinePlayer, announcerPlus.configManager.parse(player, quit.broadcasts))
if (quit.sounds.isNotEmpty()) {
chat.playSounds(onlinePlayer, quit.randomSound, quit.sounds)
}
}
if (announcerPlus.essentials != null) {
return announcerPlus.essentials!!.isVanished(player)
}
return false
}
}
quit.commands.forEach { dispatchCommandAsConsole(announcerPlus.configManager.parse(player, it)) }
}
private fun isVanished(player: Player): Boolean {
for (meta in player.getMetadata("vanished")) {
if (meta.asBoolean()) return true
}
if (announcerPlus.essentials != null) {
return announcerPlus.essentials!!.isVanished(player)
}
return false
}
}
@@ -1,3 +1,26 @@
/*
* This file is part of AnnouncerPlus, licensed under the MIT License.
*
* Copyright (c) 2020-2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package xyz.jpenilla.announcerplus.config
import org.spongepowered.configurate.CommentedConfigurationNode
@@ -8,73 +31,80 @@ import org.spongepowered.configurate.objectmapping.meta.Comment
@ConfigSerializable
class MainConfig {
@Comment("Here you can define custom placeholders for use in plugin messages\n These placeholders can be used like \"{placeholder}\", i.e. \"{nick}\" or \"{r}rainbow text{rc}\"")
val customPlaceholders = hashMapOf(
"nick" to "%essentials_nickname%",
"user" to "%player_name%",
"prefix1" to "<bold><blue>[<green>!</green>]</blue></bold>",
"r" to "<rainbow>",
"rc" to "</rainbow>"
@Comment("Here you can define custom placeholders for use in plugin messages\n These placeholders can be used like \"{placeholder}\", i.e. \"{nick}\" or \"{r}rainbow text{rc}\"")
val customPlaceholders = hashMapOf(
"nick" to "%essentials_nickname%",
"user" to "%player_name%",
"prefix1" to "<bold><blue>[<green>!</green>]</blue></bold>",
"r" to "<rainbow>",
"rc" to "</rainbow>"
)
@Comment("This setting enables or disables all timed broadcasts")
var enableBroadcasts = true
@Comment("This setting enables or disables all Join event features")
var joinFeatures = true
@Comment("This setting enables or disables all Quit event features")
var quitFeatures = true
@Comment(
"This setting enables or disables the first-join.conf\n" +
"If enabled, on a player's first join the first-join.conf will be used instead of any other join configs."
)
var firstJoinConfigEnabled = false
@Comment(
"Here you can define randomized join configs.\n" +
" To assign randomized join configs, give the announcerplus.randomjoin.demo permission, replacing demo with your randomized config\n" +
" NOTE: The randomized config named 'demo' will be ignored by the plugin. You must choose a new name to use this feature.\n" +
" WARNING: If you are OP make sure to negate the appropriate permissions so that you do not get duplicate join/quit messages"
)
val randomJoinConfigs = hashMapOf(
"demo" to arrayListOf(
JoinQuitPair("default", 0.1),
JoinQuitPair("default", 0.2)
)
)
@Comment("This setting enables or disables all timed broadcasts")
var enableBroadcasts = true
@Comment("This setting enables or disables all Join event features")
var joinFeatures = true
@Comment("This setting enables or disables all Quit event features")
var quitFeatures = true
@Comment("This setting enables or disables the first-join.conf\n" +
"If enabled, on a player's first join the first-join.conf will be used instead of any other join configs.")
var firstJoinConfigEnabled = false
@Comment("Here you can define randomized join configs.\n" +
" To assign randomized join configs, give the announcerplus.randomjoin.demo permission, replacing demo with your randomized config\n" +
" WARNING: If you are OP make sure to negate the appropriate permissions so that you do not get duplicate join/quit messages")
val randomJoinConfigs = hashMapOf(
"demo" to arrayListOf(
JoinQuitPair("default", 0.1),
JoinQuitPair("default", 0.2)
)
@Comment(
"Here you can define randomized quit configs.\n" +
" To assign randomized quit configs, give the announcerplus.randomquit.demo permission, replacing demo with your randomized config\n" +
" NOTE: The randomized config named 'demo' will be ignored by the plugin. You must choose a new name to use this feature.\n" +
" WARNING: If you are OP make sure to negate the appropriate permissions so that you do not get duplicate join/quit messages"
)
val randomQuitConfigs = hashMapOf(
"demo" to arrayListOf(
JoinQuitPair("default", 0.2),
JoinQuitPair("default", 0.1)
)
)
@Comment("Here you can define randomized join configs.\n" +
" To assign randomized quit configs, give the announcerplus.randomquit.demo permission, replacing demo with your randomized config\n" +
" NOTE: The randomized config named 'demo' will be ignored by the plugin. You must choose a new name to use this feature.\n" +
" WARNING: If you are OP make sure to negate the appropriate permissions so that you do not get duplicate join/quit messages")
val randomQuitConfigs = hashMapOf(
"demo" to arrayListOf(
JoinQuitPair("default", 0.2),
JoinQuitPair("default", 0.1)
)
)
@ConfigSerializable
class JoinQuitPair {
constructor()
constructor(configName: String, weight: Double) {
this.configName = configName
this.weight = weight
}
@Comment("The name of the config (the text before .conf in the file name)")
var configName = "default"
@Comment("The weight of this config for random selection, 0.0-1.0")
var weight = 0.1
@ConfigSerializable
class JoinQuitPair {
constructor()
constructor(configName: String, weight: Double) {
this.configName = configName
this.weight = weight
}
companion object {
private val MAPPER = ObjectMapper.factory().get(MainConfig::class.java)
@Comment("The name of the config (the text before .conf in the file name)")
var configName = "default"
fun loadFrom(node: CommentedConfigurationNode): MainConfig {
return MAPPER.load(node)
}
}
@Comment("The weight of this config for random selection, 0.0-1.0")
var weight = 0.1
}
fun saveTo(node: CommentedConfigurationNode) {
MAPPER.save(this, node)
companion object {
private val MAPPER = ObjectMapper.factory().get(MainConfig::class.java)
fun loadFrom(node: CommentedConfigurationNode): MainConfig {
return MAPPER.load(node)
}
}
fun saveTo(node: CommentedConfigurationNode) {
MAPPER.save(this, node)
}
}
@@ -1,3 +1,26 @@
/*
* This file is part of AnnouncerPlus, licensed under the MIT License.
*
* Copyright (c) 2020-2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package xyz.jpenilla.announcerplus.config.message
import org.bukkit.entity.Player
@@ -8,27 +31,27 @@ import xyz.jpenilla.announcerplus.task.ActionBarUpdateTask
@ConfigSerializable
class ActionBarSettings : MessageElement {
@Comment("Seconds of duration for the Action Bar to stay on screen")
var durationSeconds = 6
@Comment("Seconds of duration for the Action Bar to stay on screen")
var durationSeconds = 6
@Comment("Should the fade out animation of the Action Bar be enabled?")
var enableFadeOut = false
@Comment("Should the fade out animation of the Action Bar be enabled?")
var enableFadeOut = false
@Comment("The text for the Action Bar. Set to \"\" (empty string) to disable. Accepts animations")
var text = ""
@Comment("The text for the Action Bar. Set to \"\" (empty string) to disable. Accepts animations")
var text = ""
constructor()
constructor(fadeEnabled: Boolean, durationSeconds: Int, text: String) {
this.enableFadeOut = fadeEnabled
this.durationSeconds = durationSeconds
this.text = text
}
constructor()
constructor(fadeEnabled: Boolean, durationSeconds: Int, text: String) {
this.enableFadeOut = fadeEnabled
this.durationSeconds = durationSeconds
this.text = text
}
override fun isEnabled(): Boolean {
return text != ""
}
override fun isEnabled(): Boolean {
return text != ""
}
override fun display(player: Player) {
ActionBarUpdateTask(player, durationSeconds * 20L, enableFadeOut, text).start()
}
override fun display(player: Player) {
ActionBarUpdateTask(player, durationSeconds * 20L, enableFadeOut, text).start()
}
}
@@ -1,3 +1,26 @@
/*
* This file is part of AnnouncerPlus, licensed under the MIT License.
*
* Copyright (c) 2020-2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package xyz.jpenilla.announcerplus.config.message
import net.kyori.adventure.bossbar.BossBar
@@ -9,34 +32,36 @@ import xyz.jpenilla.announcerplus.task.BossBarUpdateTask
@ConfigSerializable
class BossBarSettings : MessageElement {
@Comment("Seconds of duration for the Boss Bar to stay on screen")
var durationSeconds = 12
@Comment("Seconds of duration for the Boss Bar to stay on screen")
var durationSeconds = 12
@Comment("The text for the Boss Bar. Set to \"\" (empty string) to disable. Accepts animations")
var text = ""
@Comment("The text for the Boss Bar. Set to \"\" (empty string) to disable. Accepts animations")
var text = ""
@Comment("The color for the Boss Bar. For a list of colors, visit: https://papermc.io/javadocs/paper/1.16/org/bukkit/boss/BarColor.html\n" +
" This field technically accepts animations, although only the \"Flashing Text\" animation used with valid Boss Bar colors will actually work.")
var color = "YELLOW"
@Comment(
"The color for the Boss Bar. For a list of colors, visit: https://papermc.io/javadocs/paper/1.16/org/bukkit/boss/BarColor.html\n" +
" This field technically accepts animations, although only the \"Flashing Text\" animation used with valid Boss Bar colors will actually work."
)
var color = "YELLOW"
@Comment("The overlay for the Boss Bar. Possible values: PROGRESS, NOTCHED_6, NOTCHED_10, NOTCHED_12, NOTCHED_20")
var overlay = BossBar.Overlay.PROGRESS
@Comment("The overlay for the Boss Bar. Possible values: PROGRESS, NOTCHED_6, NOTCHED_10, NOTCHED_12, NOTCHED_20")
var overlay = BossBar.Overlay.PROGRESS
@Comment("The fill mode for the Boss Bar. Possible modes: FILL, DRAIN, FULL, EMPTY")
var fillMode = BossBarUpdateTask.FillMode.DRAIN
@Comment("The fill mode for the Boss Bar. Possible modes: FILL, DRAIN, FULL, EMPTY")
var fillMode = BossBarUpdateTask.FillMode.DRAIN
constructor()
constructor(durationSeconds: Int, color: String, text: String) {
this.durationSeconds = durationSeconds
this.color = color
this.text = text
}
constructor()
constructor(durationSeconds: Int, color: String, text: String) {
this.durationSeconds = durationSeconds
this.color = color
this.text = text
}
override fun isEnabled(): Boolean {
return text != ""
}
override fun isEnabled(): Boolean {
return text != ""
}
override fun display(player: Player) {
BossBarUpdateTask(player, durationSeconds, overlay, fillMode, color, text).start()
}
override fun display(player: Player) {
BossBarUpdateTask(player, durationSeconds, overlay, fillMode, color, text).start()
}
}
@@ -1,3 +1,26 @@
/*
* This file is part of AnnouncerPlus, licensed under the MIT License.
*
* Copyright (c) 2020-2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package xyz.jpenilla.announcerplus.config.message
import com.google.common.collect.ImmutableSet
@@ -8,56 +31,56 @@ import xyz.jpenilla.announcerplus.util.Constants
@ConfigSerializable
class Message {
@Comment("The lines of text for this message. Can be empty for no chat messages.")
val messageText = arrayListOf<String>()
@Comment("The lines of text for this message. Can be empty for no chat messages.")
val messageText = arrayListOf<String>()
@Comment("Configure the Action Bar for this message")
var actionBar = ActionBarSettings()
@Comment("Configure the Action Bar for this message")
var actionBar = ActionBarSettings()
@Comment("Configure the Boss Bar for this message")
var bossBar = BossBarSettings()
@Comment("Configure the Boss Bar for this message")
var bossBar = BossBarSettings()
@Comment("Configure the Title for this message")
var title = TitleSettings()
@Comment("Configure the Title for this message")
var title = TitleSettings()
@Comment("Configure the Toast/Achievement/Advancement for this message")
var toast = ToastSettings()
@Comment("Configure the Toast/Achievement/Advancement for this message")
var toast = ToastSettings()
@Comment("The sounds to play when this message is sent\n ${Constants.CONFIG_COMMENT_SOUNDS_LINE2}")
var sounds = ""
@Comment("The sounds to play when this message is sent\n ${Constants.CONFIG_COMMENT_SOUNDS_LINE2}")
var sounds = ""
@Comment(Constants.CONFIG_COMMENT_SOUNDS_RANDOM)
var soundsRandomized = true
@Comment(Constants.CONFIG_COMMENT_SOUNDS_RANDOM)
var soundsRandomized = true
@Comment("These commands will run as console on broadcast. Example: \"broadcast This is a test\"")
val commands = arrayListOf<String>()
@Comment("These commands will run as console on broadcast. Example: \"broadcast This is a test\"")
val commands = arrayListOf<String>()
@Comment("These commands will run as console once per player on broadcast. Example: \"minecraft:give %player_name% dirt\"")
val perPlayerCommands = arrayListOf<String>()
@Comment("These commands will run as console once per player on broadcast. Example: \"minecraft:give %player_name% dirt\"")
val perPlayerCommands = arrayListOf<String>()
@Comment("These commands will run once per player, as the player on broadcast. Example: \"ap about\"")
val asPlayerCommands = arrayListOf<String>()
@Comment("These commands will run once per player, as the player on broadcast. Example: \"ap about\"")
val asPlayerCommands = arrayListOf<String>()
constructor()
constructor(text: List<String>) {
this.messageText.addAll(text)
}
constructor()
constructor(text: List<String>) {
this.messageText.addAll(text)
}
fun messageElements(): Collection<MessageElement> = ImmutableSet.of(
actionBar,
bossBar,
title,
toast
)
fun messageElements(): Collection<MessageElement> = ImmutableSet.of(
actionBar,
bossBar,
title,
toast
)
fun sounds(sounds: String): Message = apply { this.sounds = sounds }
fun sounds(sounds: String): Message = apply { this.sounds = sounds }
fun actionBar(actionBar: ActionBarSettings): Message = apply { this.actionBar = actionBar }
fun actionBar(actionBar: ActionBarSettings): Message = apply { this.actionBar = actionBar }
fun bossBar(bossBar: BossBarSettings): Message = apply { this.bossBar = bossBar }
fun bossBar(bossBar: BossBarSettings): Message = apply { this.bossBar = bossBar }
fun title(title: TitleSettings): Message = apply { this.title = title }
fun title(title: TitleSettings): Message = apply { this.title = title }
fun toast(toast: ToastSettings): Message = apply { this.toast = toast }
fun toast(toast: ToastSettings): Message = apply { this.toast = toast }
}
@@ -1,3 +1,26 @@
/*
* This file is part of AnnouncerPlus, licensed under the MIT License.
*
* Copyright (c) 2020-2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package xyz.jpenilla.announcerplus.config.message
import com.google.common.collect.ImmutableList
@@ -23,134 +46,164 @@ import xyz.jpenilla.jmplib.Chat
@ConfigSerializable
class MessageConfig : KoinComponent {
@Setting
@Comment("The list of messages for a config")
val messages = arrayListOf(
Message(arrayListOf("<center><rainbow>Test AnnouncerPlus broadcast!")),
Message().bossBar(BossBarSettings(25, "{animate:flash:YELLOW:PURPLE:40}",
"<green>-| <white>{animate:scrolltext:Hello this is an example Boss Bar announcement:20:3}</white> |-")),
Message(arrayListOf(
"{prefix1} 1. <gradient:blue:green:blue>Multi-line test AnnouncerPlus broadcast",
"{prefix1} 2. <gradient:red:gold:red>Line number two of three",
"{prefix1} 3. <bold><rainbow>this is the last line (line 3)"))
.toast(ToastSettings(Material.DIAMOND, ToastSettings.FrameType.CHALLENGE,
"<gradient:green:blue><bold><italic>AnnouncerPlus", "<rainbow>This is a Toast message!")),
Message(arrayListOf("{prefix1} Test <gradient:blue:aqua>AnnouncerPlus</gradient> broadcast with sound<green>!"))
.sounds("minecraft:entity.strider.happy,minecraft:entity.villager.ambient,minecraft:block.note_block.cow_bell"),
Message(arrayListOf("{prefix1} Use <click:run_command:/ap about><hover:show_text:'<rainbow>Click to run!'><rainbow>/ap about</rainbow></hover></click> to check the plugin version"))
.actionBar(ActionBarSettings(true, 15, "<{animate:randomcolor:pulse:25}>-| <white>{animate:scrolltext:Hello there this is some very long text being displayed in a scrolling window!! =):20:3}</white> |-")),
Message(arrayListOf("<bold><italic>Hello, </bold></italic> {nick} {prefix1} {r}!!!!!!!!!{rc}"))
.title(TitleSettings(1, 13, 2, "<gradient:green:blue:green:{animate:scroll:0.1}>||||||||||||||||||||||||||||||||||||||||||||", "<{animate:pulse:red:blue:10}>{animate:type:This is a test... typing...:6}")),
Message(arrayListOf("<center><gradient:red:blue>Centered text Example"))
.bossBar(BossBarSettings(25, "PINK",
"<bold>This is an example <italic><gradient:blue:light_purple>Boss Bar"))
@Setting
@Comment("The list of messages for a config")
val messages = arrayListOf(
Message(arrayListOf("<center><rainbow>Test AnnouncerPlus broadcast!")),
Message().bossBar(
BossBarSettings(
25, "{animate:flash:YELLOW:PURPLE:40}",
"<green>-| <white>{animate:scrolltext:Hello this is an example Boss Bar announcement:20:3}</white> |-"
)
),
Message(
arrayListOf(
"{prefix1} 1. <gradient:blue:green:blue>Multi-line test AnnouncerPlus broadcast",
"{prefix1} 2. <gradient:red:gold:red>Line number two of three",
"{prefix1} 3. <bold><rainbow>this is the last line (line 3)"
)
)
.toast(
ToastSettings(
Material.DIAMOND, ToastSettings.FrameType.CHALLENGE,
"<gradient:green:blue><bold><italic>AnnouncerPlus", "<rainbow>This is a Toast message!"
)
),
Message(arrayListOf("{prefix1} Test <gradient:blue:aqua>AnnouncerPlus</gradient> broadcast with sound<green>!"))
.sounds("minecraft:entity.strider.happy,minecraft:entity.villager.ambient,minecraft:block.note_block.cow_bell"),
Message(arrayListOf("{prefix1} Use <click:run_command:/ap about><hover:show_text:'<rainbow>Click to run!'><rainbow>/ap about</rainbow></hover></click> to check the plugin version"))
.actionBar(
ActionBarSettings(
true,
15,
"<{animate:randomcolor:pulse:25}>-| <white>{animate:scrolltext:Hello there this is some very long text being displayed in a scrolling window!! =):20:3}</white> |-"
)
),
Message(arrayListOf("<bold><italic>Hello, </bold></italic> {nick} {prefix1} {r}!!!!!!!!!{rc}"))
.title(
TitleSettings(
1,
13,
2,
"<gradient:green:blue:green:{animate:scroll:0.1}>||||||||||||||||||||||||||||||||||||||||||||",
"<{animate:pulse:red:blue:10}>{animate:type:This is a test... typing...:6}"
)
),
Message(arrayListOf("<center><gradient:red:blue>Centered text Example"))
.bossBar(
BossBarSettings(
25, "PINK",
"<bold>This is an example <italic><gradient:blue:light_purple>Boss Bar"
)
)
)
@Setting("every-broadcast-commands")
@Comment("These commands will run as console once each interval\n Example: \"broadcast This is a test\"")
val commands = arrayListOf<String>()
@Setting("every-broadcast-commands")
@Comment("These commands will run as console once each interval\n Example: \"broadcast This is a test\"")
val commands = arrayListOf<String>()
@Setting("every-broadcast-per-player-commands")
@Comment("These commands will run as console once per player each interval\n Example: \"minecraft:give %player_name% dirt\"")
val perPlayerCommands = arrayListOf<String>()
@Setting("every-broadcast-per-player-commands")
@Comment("These commands will run as console once per player each interval\n Example: \"minecraft:give %player_name% dirt\"")
val perPlayerCommands = arrayListOf<String>()
@Setting("every-broadcast-as-player-commands")
@Comment("These commands will run once per player each interval, as the player\n Example: \"ap about\"")
val asPlayerCommands = arrayListOf<String>()
@Setting("every-broadcast-as-player-commands")
@Comment("These commands will run once per player each interval, as the player\n Example: \"ap about\"")
val asPlayerCommands = arrayListOf<String>()
@Setting("interval-time-unit")
@Comment("The unit of time used for the interval\n Can be SECONDS, MINUTES, or HOURS")
var timeUnit = TimeUnit.MINUTES
@Setting("interval-time-unit")
@Comment("The unit of time used for the interval\n Can be SECONDS, MINUTES, or HOURS")
var timeUnit = TimeUnit.MINUTES
@Setting("interval-time-amount")
@Comment("The amount of time used for the interval")
var interval = 3
@Setting("interval-time-amount")
@Comment("The amount of time used for the interval")
var interval = 3
@Setting("random-message-order")
@Comment("Should the messages be sent in order of the config or in random order")
var randomOrder = false
@Setting("random-message-order")
@Comment("Should the messages be sent in order of the config or in random order")
var randomOrder = false
companion object {
private val MAPPER = ObjectMapper.factoryBuilder().addNodeResolver(NodeResolver.onlyWithSetting()).build()
.get(MessageConfig::class.java)
companion object {
private val MAPPER = ObjectMapper.factoryBuilder().addNodeResolver(NodeResolver.onlyWithSetting()).build()
.get(MessageConfig::class.java)
fun loadFrom(node: CommentedConfigurationNode, name: String): MessageConfig =
MAPPER.load(node).populate(name)
fun loadFrom(node: CommentedConfigurationNode, name: String): MessageConfig =
MAPPER.load(node).populate(name)
}
fun saveTo(node: CommentedConfigurationNode) {
MAPPER.save(this, node)
}
fun populate(name: String): MessageConfig =
this.apply { this.name = name }
private var broadcastTask: BukkitTask? = null
lateinit var name: String
private val announcerPlus: AnnouncerPlus by inject()
private val configManager: ConfigManager by inject()
private val chat: Chat by inject()
private val broadcastQueue = ArrayDeque<Message>()
fun broadcast() {
stop()
broadcastQueue.clear()
broadcastQueue.addAll(shuffledMessages())
broadcastTask = announcerPlus.asyncTimer(0L, timeUnit.getTicks(interval)) {
if (broadcastQueue.isNotEmpty()) {
broadcast(broadcastQueue.removeFirst())
} else {
broadcast()
}
}
}
fun saveTo(node: CommentedConfigurationNode) =
MAPPER.save(this, node)
fun populate(name: String): MessageConfig =
this.apply { this.name = name }
private var broadcastTask: BukkitTask? = null
lateinit var name: String
private val announcerPlus: AnnouncerPlus by inject()
private val configManager: ConfigManager by inject()
private val chat: Chat by inject()
private val broadcastQueue = ArrayDeque<Message>()
fun broadcast() {
stop()
broadcastQueue.clear()
broadcastQueue.addAll(shuffledMessages())
broadcastTask = announcerPlus.asyncTimer(0L, timeUnit.getTicks(interval)) {
if (broadcastQueue.isNotEmpty()) {
broadcast(broadcastQueue.removeFirst())
} else {
broadcast()
}
private fun broadcast(message: Message) {
val onlinePlayers = announcerPlus.getOnMain { ImmutableList.copyOf(Bukkit.getOnlinePlayers()) }
for (onlinePlayer in onlinePlayers) {
if (announcerPlus.essentials != null) {
if (announcerPlus.essentials!!.isAfk(onlinePlayer) && announcerPlus.perms!!.playerHas(onlinePlayer, "${announcerPlus.name}.messages.$name.afk")) {
continue
}
}
private fun broadcast(message: Message) {
val onlinePlayers = announcerPlus.getOnMain { ImmutableList.copyOf(Bukkit.getOnlinePlayers()) }
for (onlinePlayer in onlinePlayers) {
if (announcerPlus.essentials != null) {
if (announcerPlus.essentials!!.isAfk(onlinePlayer) && announcerPlus.perms!!.playerHas(onlinePlayer, "${announcerPlus.name}.messages.$name.afk")) {
continue
}
}
if (announcerPlus.perms!!.playerHas(onlinePlayer, "${announcerPlus.name}.messages.$name")) {
with(message) {
if (messageText.size != 0) {
chat.send(onlinePlayer, configManager.parse(onlinePlayer, messageText))
}
chat.playSounds(onlinePlayer, soundsRandomized, sounds)
messageElements().forEach { it.displayIfEnabled(onlinePlayer) }
}
announcerPlus.runSync {
message.perPlayerCommands.forEach { dispatchCommandAsConsole(configManager.parse(onlinePlayer, it)) }
message.asPlayerCommands.forEach { onlinePlayer.performCommand(configManager.parse(onlinePlayer, it)) }
perPlayerCommands.forEach { dispatchCommandAsConsole(configManager.parse(onlinePlayer, it)) }
asPlayerCommands.forEach { onlinePlayer.performCommand(configManager.parse(onlinePlayer, it)) }
}
}
}
if (announcerPlus.perms!!.playerHas(onlinePlayer, "${announcerPlus.name}.messages.$name")) {
with(message) {
if (messageText.size != 0) {
chat.send(onlinePlayer, configManager.parse(onlinePlayer, messageText))
}
chat.playSounds(onlinePlayer, soundsRandomized, sounds)
messageElements().forEach { it.displayIfEnabled(onlinePlayer) }
}
announcerPlus.runSync {
message.commands.forEach { dispatchCommandAsConsole(configManager.parse(null, it)) }
commands.forEach { dispatchCommandAsConsole(configManager.parse(null, it)) }
message.perPlayerCommands.forEach { dispatchCommandAsConsole(configManager.parse(onlinePlayer, it)) }
message.asPlayerCommands.forEach { onlinePlayer.performCommand(configManager.parse(onlinePlayer, it)) }
perPlayerCommands.forEach { dispatchCommandAsConsole(configManager.parse(onlinePlayer, it)) }
asPlayerCommands.forEach { onlinePlayer.performCommand(configManager.parse(onlinePlayer, it)) }
}
}
}
private fun shuffledMessages(): List<Message> {
if (randomOrder) {
return messages.shuffled()
}
return messages
announcerPlus.runSync {
message.commands.forEach { dispatchCommandAsConsole(configManager.parse(null, it)) }
commands.forEach { dispatchCommandAsConsole(configManager.parse(null, it)) }
}
}
fun stop() {
broadcastTask?.cancel()
private fun shuffledMessages(): List<Message> {
if (randomOrder) {
return messages.shuffled()
}
return messages
}
enum class TimeUnit(val ticks: Long) {
SECONDS(20L),
MINUTES(1200L),
HOURS(72000L);
fun stop() {
broadcastTask?.cancel()
}
fun getTicks(units: Int): Long =
ticks * units
}
enum class TimeUnit(val ticks: Long) {
SECONDS(20L),
MINUTES(1200L),
HOURS(72000L);
fun getTicks(units: Int): Long =
ticks * units
}
}
@@ -1,15 +1,38 @@
/*
* This file is part of AnnouncerPlus, licensed under the MIT License.
*
* Copyright (c) 2020-2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package xyz.jpenilla.announcerplus.config.message
import org.bukkit.entity.Player
import org.koin.core.KoinComponent
interface MessageElement : KoinComponent {
fun isEnabled(): Boolean
fun display(player: Player)
fun isEnabled(): Boolean
fun display(player: Player)
fun displayIfEnabled(player: Player) {
if (isEnabled()) {
display(player)
}
fun displayIfEnabled(player: Player) {
if (isEnabled()) {
display(player)
}
}
}
@@ -1,3 +1,26 @@
/*
* This file is part of AnnouncerPlus, licensed under the MIT License.
*
* Copyright (c) 2020-2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package xyz.jpenilla.announcerplus.config.message
import org.bukkit.entity.Player
@@ -7,39 +30,39 @@ import xyz.jpenilla.announcerplus.task.TitleUpdateTask
@ConfigSerializable
class TitleSettings : MessageElement {
constructor()
constructor(fadeInSeconds: Int, durationSeconds: Int, fadeOutSeconds: Int, title: String, subTitle: String) {
this.fadeInSeconds = fadeInSeconds
this.fadeOutSeconds = fadeOutSeconds
this.title = title
this.subtitle = subTitle
this.durationSeconds = durationSeconds
constructor()
constructor(fadeInSeconds: Int, durationSeconds: Int, fadeOutSeconds: Int, title: String, subTitle: String) {
this.fadeInSeconds = fadeInSeconds
this.fadeOutSeconds = fadeOutSeconds
this.title = title
this.subtitle = subTitle
this.durationSeconds = durationSeconds
}
@Comment("Seconds of duration for the title fade-in animation")
var fadeInSeconds = 1
@Comment("Seconds of duration for the title to stay on screen")
var durationSeconds = 5
@Comment("Seconds of duration for the title fade-out animation")
var fadeOutSeconds = 1
@Comment("Title text. If the title and subtitle are both set to \"\" (empty string), then this title is disabled")
var title = ""
@Comment("Subtitle text. If the title and subtitle are both set to \"\" (empty string), then this title is disabled")
var subtitle = ""
override fun isEnabled(): Boolean {
return if (fadeInSeconds == 0 && durationSeconds == 0 && fadeOutSeconds == 0) {
false
} else {
title != "" || subtitle != ""
}
}
@Comment("Seconds of duration for the title fade-in animation")
var fadeInSeconds = 1
@Comment("Seconds of duration for the title to stay on screen")
var durationSeconds = 5
@Comment("Seconds of duration for the title fade-out animation")
var fadeOutSeconds = 1
@Comment("Title text. If the title and subtitle are both set to \"\" (empty string), then this title is disabled")
var title = ""
@Comment("Subtitle text. If the title and subtitle are both set to \"\" (empty string), then this title is disabled")
var subtitle = ""
override fun isEnabled(): Boolean {
return if (fadeInSeconds == 0 && durationSeconds == 0 && fadeOutSeconds == 0) {
false
} else {
title != "" || subtitle != ""
}
}
override fun display(player: Player) {
TitleUpdateTask(player, fadeInSeconds, durationSeconds, fadeOutSeconds, title, subtitle).start()
}
override fun display(player: Player) {
TitleUpdateTask(player, fadeInSeconds, durationSeconds, fadeOutSeconds, title, subtitle).start()
}
}
@@ -1,3 +1,26 @@
/*
* This file is part of AnnouncerPlus, licensed under the MIT License.
*
* Copyright (c) 2020-2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package xyz.jpenilla.announcerplus.config.message
import com.google.gson.JsonObject
@@ -10,91 +33,92 @@ import org.koin.core.inject
import org.spongepowered.configurate.objectmapping.ConfigSerializable
import org.spongepowered.configurate.objectmapping.meta.Comment
import xyz.jpenilla.announcerplus.AnnouncerPlus
import xyz.jpenilla.jmplib.Environment
@ConfigSerializable
class ToastSettings : MessageElement {
@Comment("The icon for the Toast/Advancement notification")
var icon = Material.DIAMOND
@Comment("The icon for the Toast/Advancement notification")
var icon = Material.DIAMOND
@Comment("Should the icon item be enchanted?")
var iconEnchanted = false
@Comment("Should the icon item be enchanted?")
var iconEnchanted = false
@Comment("Enter custom model data for the icon item. -1 to disable")
var iconCustomModelData = -1
@Comment("Enter custom model data for the icon item. -1 to disable")
var iconCustomModelData = -1
@Comment("The text for the header of the Toast. If this and the footer are set to \"\" (empty string), the toast is disabled")
var header = ""
@Comment("The text for the header of the Toast. If this and the footer are set to \"\" (empty string), the toast is disabled")
var header = ""
@Comment("The text for the footer of the Toast. If this and the header are set to \"\" (empty string), the toast is disabled")
var footer = ""
@Comment("The text for the footer of the Toast. If this and the header are set to \"\" (empty string), the toast is disabled")
var footer = ""
@Comment("The frame for the Toast. Can be CHALLENGE, GOAL, or TASK")
var frame = FrameType.GOAL
@Comment("The frame for the Toast. Can be CHALLENGE, GOAL, or TASK")
var frame = FrameType.GOAL
constructor()
constructor(icon: Material, frameType: FrameType, header: String, footer: String) {
this.icon = icon
this.frame = frameType
this.header = header
this.footer = footer
constructor()
constructor(icon: Material, frameType: FrameType, header: String, footer: String) {
this.icon = icon
this.frame = frameType
this.header = header
this.footer = footer
}
private val announcerPlus: AnnouncerPlus by inject()
private val miniMessage: MiniMessage by inject()
override fun isEnabled(): Boolean {
return header != "" || footer != ""
}
override fun display(player: Player) {
announcerPlus.toastTask?.queueToast(this, player)
}
fun getJson(player: Player): JsonObject {
val json = JsonObject()
val display = JsonObject()
val icon = JsonObject()
val iconString = if (Environment.majorMinecraftVersion() <= 12) this.icon.name else this.icon.key.toString()
icon.addProperty("item", iconString)
val nbtBuilder = StringBuilder("{CustomModelData:$iconCustomModelData")
if (iconEnchanted) {
nbtBuilder.append(",Enchantments:[{id:\"aqua_affinity\",lvl:1}]}")
} else {
nbtBuilder.append("}")
}
icon.addProperty("nbt", nbtBuilder.toString())
display.add("icon", icon)
val titleComponent = LinearComponents.linear(
miniMessage.parse(announcerPlus.configManager.parse(player, header)),
Component.newline(),
miniMessage.parse(announcerPlus.configManager.parse(player, footer))
)
val title = announcerPlus.jsonParser.parse(
if (Environment.majorMinecraftVersion() >= 16) {
announcerPlus.gsonComponentSerializer.serialize(titleComponent)
} else {
announcerPlus.downsamplingGsonComponentSerializer.serialize(titleComponent)
}
)
display.add("title", title)
display.addProperty("description", "AnnouncerPlus Toast Description")
display.addProperty("frame", frame.value)
display.addProperty("announce_to_chat", false)
display.addProperty("show_toast", true)
display.addProperty("hidden", true)
val trigger = JsonObject()
trigger.addProperty("trigger", "minecraft:impossible")
val criteria = JsonObject()
criteria.add("impossible", trigger)
json.add("criteria", criteria)
json.add("display", display)
return json
}
private val announcerPlus: AnnouncerPlus by inject()
private val miniMessage: MiniMessage by inject()
override fun isEnabled(): Boolean {
return header != "" || footer != ""
}
override fun display(player: Player) {
announcerPlus.toastTask?.queueToast(this, player)
}
fun getJson(player: Player): JsonObject {
val json = JsonObject()
val display = JsonObject()
val icon = JsonObject()
val iconString = if (announcerPlus.majorMinecraftVersion <= 12) this.icon.name else this.icon.key.toString()
icon.addProperty("item", iconString)
val nbtBuilder = StringBuilder("{CustomModelData:$iconCustomModelData")
if (iconEnchanted) {
nbtBuilder.append(",Enchantments:[{id:\"aqua_affinity\",lvl:1}]}")
} else {
nbtBuilder.append("}")
}
icon.addProperty("nbt", nbtBuilder.toString())
display.add("icon", icon)
val titleComponent = LinearComponents.linear(
miniMessage.parse(announcerPlus.configManager.parse(player, header)),
Component.newline(),
miniMessage.parse(announcerPlus.configManager.parse(player, footer))
)
val title = announcerPlus.jsonParser.parse(
if (announcerPlus.majorMinecraftVersion >= 16) {
announcerPlus.gsonComponentSerializer.serialize(titleComponent)
} else {
announcerPlus.downsamplingGsonComponentSerializer.serialize(titleComponent)
}
)
display.add("title", title)
display.addProperty("description", "AnnouncerPlus Toast Description")
display.addProperty("frame", frame.value)
display.addProperty("announce_to_chat", false)
display.addProperty("show_toast", true)
display.addProperty("hidden", true)
val trigger = JsonObject()
trigger.addProperty("trigger", "minecraft:impossible")
val criteria = JsonObject()
criteria.add("impossible", trigger)
json.add("criteria", criteria)
json.add("display", display)
return json
}
enum class FrameType(val value: String) {
CHALLENGE("challenge"),
GOAL("goal"),
TASK("task"),
}
enum class FrameType(val value: String) {
CHALLENGE("challenge"),
GOAL("goal"),
TASK("task"),
}
}
@@ -1,3 +1,26 @@
/*
* This file is part of AnnouncerPlus, licensed under the MIT License.
*
* Copyright (c) 2020-2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package xyz.jpenilla.announcerplus.task
import org.bukkit.entity.Player
@@ -6,28 +29,28 @@ import xyz.jpenilla.announcerplus.textanimation.AnimationHolder
import xyz.jpenilla.jmplib.Chat
class ActionBarUpdateTask(
private val player: Player,
private val lifeTime: Long,
private val shouldFade: Boolean,
private val text: String
private val player: Player,
private val lifeTime: Long,
private val shouldFade: Boolean,
private val text: String
) : UpdateTask() {
private val chat: Chat by inject()
private val animationHolder = AnimationHolder(player, text)
private val chat: Chat by inject()
private val animationHolder = AnimationHolder(player, text)
override fun stop() {
super.stop()
if (!shouldFade) {
chat.sendActionBar(player, "")
}
override fun stop() {
super.stop()
if (!shouldFade) {
chat.sendActionBar(player, "")
}
}
override fun update() {
chat.sendActionBar(player, animationHolder.parseNext(text))
}
override fun update() {
chat.sendActionBar(player, animationHolder.parseNext(text))
}
override fun shouldContinue(): Boolean {
return ticksLived < lifeTime && player.isOnline
}
override fun shouldContinue(): Boolean {
return ticksLived < lifeTime && player.isOnline
}
override fun synchronizationContext() = SynchronizationContext.ASYNC
override fun synchronizationContext() = SynchronizationContext.ASYNC
}
@@ -1,3 +1,26 @@
/*
* This file is part of AnnouncerPlus, licensed under the MIT License.
*
* Copyright (c) 2020-2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package xyz.jpenilla.announcerplus.task
import net.kyori.adventure.bossbar.BossBar
@@ -11,44 +34,44 @@ import xyz.jpenilla.announcerplus.config.ConfigManager
import xyz.jpenilla.announcerplus.textanimation.AnimationHolder
class BossBarUpdateTask(
private val player: Player,
private val lifeTime: Int,
overlay: BossBar.Overlay,
private val fillMode: FillMode,
private val color: String,
private val text: String
private val player: Player,
private val lifeTime: Int,
overlay: BossBar.Overlay,
private val fillMode: FillMode,
private val color: String,
private val text: String
) : UpdateTask() {
private val configManager: ConfigManager by inject()
private val miniMessage: MiniMessage by inject()
private val audience = get<BukkitAudiences>().player(player)
private val textAnimation = AnimationHolder(player, text)
private val colorAnimation = AnimationHolder(player, color)
private val bar = BossBar.bossBar(Component.empty(), 0.5f, BossBar.Color.BLUE, overlay)
private val configManager: ConfigManager by inject()
private val miniMessage: MiniMessage by inject()
private val audience = get<BukkitAudiences>().player(player)
private val textAnimation = AnimationHolder(player, text)
private val colorAnimation = AnimationHolder(player, color)
private val bar = BossBar.bossBar(Component.empty(), 0.5f, BossBar.Color.BLUE, overlay)
override fun stop() {
super.stop()
audience.hideBossBar(bar)
override fun stop() {
super.stop()
audience.hideBossBar(bar)
}
override fun update() {
bar.color(BossBar.Color.NAMES.value(colorAnimation.parseNext(color).toLowerCase()) ?: BossBar.Color.BLUE)
bar.name(miniMessage.parse(configManager.parse(player, textAnimation.parseNext(text))))
when (fillMode) {
FillMode.FILL -> bar.progress(ticksLived / (lifeTime * 20f))
FillMode.DRAIN -> bar.progress(1f - (ticksLived / (lifeTime * 20f)))
FillMode.FULL -> if (ticksLived == 0L) bar.progress(1f)
FillMode.EMPTY -> if (ticksLived == 0L) bar.progress(0f)
}
if (ticksLived == 0L) audience.showBossBar(bar)
}
override fun update() {
bar.color(BossBar.Color.NAMES.value(colorAnimation.parseNext(color).toLowerCase()) ?: BossBar.Color.BLUE)
bar.name(miniMessage.parse(configManager.parse(player, textAnimation.parseNext(text))))
when (fillMode) {
FillMode.FILL -> bar.progress(ticksLived / (lifeTime * 20f))
FillMode.DRAIN -> bar.progress(1f - (ticksLived / (lifeTime * 20f)))
FillMode.FULL -> if (ticksLived == 0L) bar.progress(1f)
FillMode.EMPTY -> if (ticksLived == 0L) bar.progress(0f)
}
if (ticksLived == 0L) audience.showBossBar(bar)
}
override fun shouldContinue(): Boolean {
return ticksLived < lifeTime * 20L && player.isOnline
}
override fun shouldContinue(): Boolean {
return ticksLived < lifeTime * 20L && player.isOnline
}
override fun synchronizationContext() = SynchronizationContext.ASYNC
override fun synchronizationContext() = SynchronizationContext.ASYNC
enum class FillMode {
FILL, DRAIN, FULL, EMPTY
}
enum class FillMode {
FILL, DRAIN, FULL, EMPTY
}
}
@@ -1,3 +1,26 @@
/*
* This file is part of AnnouncerPlus, licensed under the MIT License.
*
* Copyright (c) 2020-2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package xyz.jpenilla.announcerplus.task
import org.bukkit.entity.Player
@@ -8,42 +31,52 @@ import xyz.jpenilla.jmplib.Chat
import java.time.temporal.ChronoUnit
class TitleUpdateTask(
private val player: Player,
private val fadeIn: Int,
private val duration: Int,
private val fadeOut: Int,
private val title: String,
private val subTitle: String
private val player: Player,
private val fadeIn: Int,
private val duration: Int,
private val fadeOut: Int,
private val title: String,
private val subTitle: String
) : UpdateTask() {
private val chat: Chat by inject()
private val configManager: ConfigManager by inject()
private val titleAnimation = AnimationHolder(player, title)
private val subTitleAnimation = AnimationHolder(player, subTitle)
private val chat: Chat by inject()
private val configManager: ConfigManager by inject()
private val titleAnimation = AnimationHolder(player, title)
private val subTitleAnimation = AnimationHolder(player, subTitle)
override fun stop() {
super.stop()
if (fadeOut == 0) {
chat.showTitle(player, chat.getTitleSeconds("", "", 0, 0, 0))
} else {
chat.showTitle(player, chat.getTitleSeconds(configManager.parse(player, titleAnimation.parseNext(title)), subTitleAnimation.parseNext(subTitle), 0, 0, fadeOut))
}
override fun stop() {
super.stop()
if (fadeOut != 0) {
chat.showTitle(
player,
chat.getTitleSeconds(configManager.parse(player, titleAnimation.parseNext(title)), subTitleAnimation.parseNext(subTitle), 0, 0, fadeOut)
)
}
}
override fun update() {
when (fadeIn) {
0 -> chat.showTitle(player, chat.getTitle(configManager.parse(player, titleAnimation.parseNext(title)), subTitleAnimation.parseNext(subTitle), ChronoUnit.SECONDS, 0, ChronoUnit.MILLIS, 200, ChronoUnit.SECONDS, 0))
else -> when (ticksLived) {
0L -> chat.showTitle(player, chat.getTitle(configManager.parse(player, titleAnimation.parseNext(title)), subTitleAnimation.parseNext(subTitle), ChronoUnit.SECONDS, fadeIn, ChronoUnit.MILLIS, 200, ChronoUnit.SECONDS, 0))
else -> if (ticksLived > fadeIn * 20L) {
chat.showTitle(player, chat.getTitle(configManager.parse(player, titleAnimation.parseNext(title)), subTitleAnimation.parseNext(subTitle), ChronoUnit.SECONDS, 0, ChronoUnit.MILLIS, 200, ChronoUnit.SECONDS, 0))
}
}
}
override fun update() {
if (fadeIn == 0) {
chat.showTitle(
player,
chat.getTitle(configManager.parse(player, titleAnimation.parseNext(title)), subTitleAnimation.parseNext(subTitle), ChronoUnit.SECONDS, 0, ChronoUnit.MILLIS, 200, ChronoUnit.SECONDS, 0)
)
} else {
if (ticksLived == 0L) {
chat.showTitle(
player,
chat.getTitle(configManager.parse(player, titleAnimation.parseNext(title)), subTitleAnimation.parseNext(subTitle), ChronoUnit.SECONDS, fadeIn, ChronoUnit.MILLIS, 200, ChronoUnit.SECONDS, 0)
)
} else if (ticksLived > fadeIn * 20L) {
chat.showTitle(
player,
chat.getTitle(configManager.parse(player, titleAnimation.parseNext(title)), subTitleAnimation.parseNext(subTitle), ChronoUnit.SECONDS, 0, ChronoUnit.MILLIS, 200, ChronoUnit.SECONDS, 0)
)
}
}
}
override fun shouldContinue(): Boolean {
return ticksLived < 20L * (fadeIn + duration) && player.isOnline
}
override fun shouldContinue(): Boolean {
return ticksLived < 20L * (fadeIn + duration) && player.isOnline
}
override fun synchronizationContext() = SynchronizationContext.ASYNC
override fun synchronizationContext() = SynchronizationContext.ASYNC
}
@@ -1,3 +1,26 @@
/*
* This file is part of AnnouncerPlus, licensed under the MIT License.
*
* Copyright (c) 2020-2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package xyz.jpenilla.announcerplus.task
import com.google.gson.Gson
@@ -10,165 +33,166 @@ import xyz.jpenilla.announcerplus.config.message.ToastSettings
import xyz.jpenilla.announcerplus.util.asyncTimer
import xyz.jpenilla.announcerplus.util.runSync
import xyz.jpenilla.jmplib.Crafty
import xyz.jpenilla.jmplib.Environment
import java.lang.reflect.Modifier
import java.util.concurrent.ConcurrentLinkedDeque
import kotlin.random.Random.Default.nextInt
class ToastTask : KoinComponent {
private val announcerPlus: AnnouncerPlus by inject()
private val queuedToasts = ConcurrentLinkedDeque<QueuedToast>()
private val announcerPlus: AnnouncerPlus by inject()
private val queuedToasts = ConcurrentLinkedDeque<QueuedToast>()
private val toastTask = announcerPlus.asyncTimer(0L, 1L) {
if (queuedToasts.isNotEmpty()) {
val toast = queuedToasts.removeFirst()
if (toast.player.isOnline) {
displayToastImmediately(toast)
}
private val toastTask = announcerPlus.asyncTimer(0L, 1L) {
if (queuedToasts.isNotEmpty()) {
val toast = queuedToasts.removeFirst()
if (toast.player.isOnline) {
displayToastImmediately(toast)
}
}
}
fun queueToast(toastSettings: ToastSettings, player: Player) {
queuedToasts.add(QueuedToast(player, toastSettings))
}
fun cancel() {
toastTask.cancel()
}
data class QueuedToast(val player: Player, val toast: ToastSettings)
private fun displayToastImmediately(queuedToast: QueuedToast) {
val (player, toast) = queuedToast
announcerPlus.runSync {
val minecraftKey =
MinecraftKey_ctr.newInstance(announcerPlus.name.toLowerCase(), nextInt(1000000).toString())
val serializedAdvancement = if (Environment.majorMinecraftVersion() >= 16) {
SerializedAdvancement_deserialize(
null,
toast.getJson(player),
LootDeserializationContext_ctr.newInstance(
minecraftKey,
MinecraftServer_getLootPredicateManager(MinecraftServer_getServer())
)
)
} else {
ChatDeserializer_deserialize(
null,
AdvancementDataWorld_DESERIALIZER,
announcerPlus.gson.toJson(toast.getJson(player)),
SerializedAdvancement_class
)
}
val advancement = SerializedAdvancement_getAdvancement(serializedAdvancement, minecraftKey)
val nmsPlayer = player.handle
val advancementData = EntityPlayer_getAdvancementData(nmsPlayer)
val advancementProgress = AdvancementDataPlayer_getProgress(advancementData, advancement)
(AdvancementProgress_remainingCriteria(advancementProgress) as Iterable<*>).forEach {
AdvancementDataPlayer_grantCriteria(advancementData, advancement, it)
}
announcerPlus.runSync(20L) {
(AdvancementProgress_awardedCriteria(advancementProgress) as Iterable<*>).forEach {
AdvancementDataPlayer_revokeCriteria(advancementData, advancement, it)
}
}
}
}
private companion object Reflect {
val MinecraftServer_class = Crafty.needNmsClass("MinecraftServer")
val MinecraftServer_getServer =
Crafty.findStaticMethod(MinecraftServer_class, "getServer", MinecraftServer_class)
?: error("Cannot find getServer")
val MinecraftKey_class = Crafty.needNmsClass("MinecraftKey")
val MinecraftKey_ctr = MinecraftKey_class.getDeclaredConstructor(String::class.java, String::class.java)
?: error("Cannot find MinecraftKey constructor")
val Advancement_class = Crafty.needNmsClass("Advancement")
val SerializedAdvancement_class = Crafty.needNmsClass("Advancement\$SerializedAdvancement")
val SerializedAdvancement_getAdvancement = SerializedAdvancement_class.declaredMethods.find { method ->
method.returnType == Advancement_class
&& method.parameterCount == 1
&& method.parameterTypes[0] == MinecraftKey_class
&& !Modifier.isStatic(method.modifiers)
} ?: error("Cannot find SerializedAdvancement#getAdvancement")
val EntityPlayer_class = Crafty.needNmsClass("EntityPlayer")
val CraftPlayer_class = Crafty.needCraftClass("entity.CraftPlayer")
val CraftPlayer_getHandle = CraftPlayer_class.getMethod("getHandle")
?: error("Cannot find CraftPlayer#getHandle")
val Player.handle: Any
get() = CraftPlayer_getHandle(this)
val AdvancementProgress_class = Crafty.needNmsClass("AdvancementProgress")
val AdvancementDataPlayer_class = Crafty.needNmsClass("AdvancementDataPlayer")
val EntityPlayer_getAdvancementData = EntityPlayer_class.methods.find { method ->
method.returnType == AdvancementDataPlayer_class
&& method.parameterCount == 0
} ?: error("Cannot find EntityPlayer#getAdvancementData")
val AdvancementDataPlayer_getProgress = AdvancementDataPlayer_class.methods.find { method ->
method.returnType == AdvancementProgress_class
&& method.parameterCount == 1
&& method.parameterTypes[0] == Advancement_class
} ?: error("Cannot find AdvancementDataPlayer#getProgress")
val AdvancementDataPlayer_grantCriteria =
AdvancementDataPlayer_class.getDeclaredMethod("grantCriteria", Advancement_class, String::class.java)
?: error("Cannot find AdvancementDataPlayer#grantCriteria")
val AdvancementDataPlayer_revokeCriteria = try {
AdvancementDataPlayer_class.getDeclaredMethod("revokeCritera", Advancement_class, String::class.java) // lol
} catch (e: NoSuchMethodException) {
AdvancementDataPlayer_class.getDeclaredMethod("revokeCriteria", Advancement_class, String::class.java)
} ?: error("Cannot find AdvancementDataPlayer#revokeCriteria")
val AdvancementProgress_remainingCriteria = AdvancementProgress_class.getDeclaredMethod("getRemainingCriteria")
?: error("Cannot find AdvancementProgress#getRemainingCriteria")
val AdvancementProgress_awardedCriteria = AdvancementProgress_class.getDeclaredMethod("getAwardedCriteria")
?: error("Cannot find AdvancementProgress#getAwardedCriteria")
// start 1.12 -> 1.15
val ChatDeserializer_class by lazy { Crafty.needNmsClass("ChatDeserializer") }
val AdvancementDataWorld_class by lazy { Crafty.needNmsClass("AdvancementDataWorld") }
val ChatDeserializer_deserialize by lazy {
ChatDeserializer_class.methods.find { method ->
method.parameterCount == 3
&& method.parameterTypes.contains(Gson::class.java)
&& method.parameterTypes.contains(String::class.java)
&& method.parameterTypes.contains(Class::class.java)
} ?: error("Cannot find ChatDeserializer.deserialize")
}
fun queueToast(toastSettings: ToastSettings, player: Player) {
queuedToasts.add(QueuedToast(player, toastSettings))
val AdvancementDataWorld_DESERIALIZER by lazy {
AdvancementDataWorld_class.getField("DESERIALIZER").get(null)
?: error("Cannot find AdvancementDataWorld.DESERIALIZER")
}
// end 1.12 -> 1.15
// start 1.16+
val LootPredicateManager_class by lazy { Crafty.needNmsClass("LootPredicateManager") }
val MinecraftServer_getLootPredicateManager by lazy {
MinecraftServer_class.declaredMethods.find { method ->
method.returnType == LootPredicateManager_class
&& method.parameterCount == 0
} ?: error("Cannot find MinecraftServer#getLootPredicateManager")
}
fun cancel() {
toastTask.cancel()
val LootDeserializationContext_class by lazy { Crafty.needNmsClass("LootDeserializationContext") }
val LootDeserializationContext_ctr by lazy {
LootDeserializationContext_class.getDeclaredConstructor(MinecraftKey_class, LootPredicateManager_class)
?: error("Cannot find LootDeserializationContext constructor")
}
data class QueuedToast(val player: Player, val toast: ToastSettings)
private fun displayToastImmediately(queuedToast: QueuedToast) {
val (player, toast) = queuedToast
announcerPlus.runSync {
val minecraftKey =
MinecraftKey_ctr.newInstance(announcerPlus.name.toLowerCase(), nextInt(1000000).toString())
val serializedAdvancement = if (announcerPlus.majorMinecraftVersion >= 16) {
SerializedAdvancement_deserialize(
null,
toast.getJson(player),
LootDeserializationContext_ctr.newInstance(
minecraftKey,
MinecraftServer_getLootPredicateManager(MinecraftServer_getServer())
)
)
} else {
ChatDeserializer_deserialize(
null,
AdvancementDataWorld_DESERIALIZER,
announcerPlus.gson.toJson(toast.getJson(player)),
SerializedAdvancement_class
)
}
val advancement = SerializedAdvancement_getAdvancement(serializedAdvancement, minecraftKey)
val nmsPlayer = player.handle
val advancementData = EntityPlayer_getAdvancementData(nmsPlayer)
val advancementProgress = AdvancementDataPlayer_getProgress(advancementData, advancement)
(AdvancementProgress_remainingCriteria(advancementProgress) as Iterable<*>).forEach {
AdvancementDataPlayer_grantCriteria(advancementData, advancement, it)
}
announcerPlus.runSync(20L) {
(AdvancementProgress_awardedCriteria(advancementProgress) as Iterable<*>).forEach {
AdvancementDataPlayer_revokeCriteria(advancementData, advancement, it)
}
}
}
}
private companion object Reflect {
val MinecraftServer_class = Crafty.needNmsClass("MinecraftServer")
val MinecraftServer_getServer =
Crafty.findStaticMethod(MinecraftServer_class, "getServer", MinecraftServer_class)
?: error("Cannot find getServer")
val MinecraftKey_class = Crafty.needNmsClass("MinecraftKey")
val MinecraftKey_ctr = MinecraftKey_class.getDeclaredConstructor(String::class.java, String::class.java)
?: error("Cannot find MinecraftKey constructor")
val Advancement_class = Crafty.needNmsClass("Advancement")
val SerializedAdvancement_class = Crafty.needNmsClass("Advancement\$SerializedAdvancement")
val SerializedAdvancement_getAdvancement = SerializedAdvancement_class.declaredMethods.find { method ->
method.returnType == Advancement_class
&& method.parameterCount == 1
&& method.parameterTypes[0] == MinecraftKey_class
&& !Modifier.isStatic(method.modifiers)
} ?: error("Cannot find SerializedAdvancement#getAdvancement")
val EntityPlayer_class = Crafty.needNmsClass("EntityPlayer")
val CraftPlayer_class = Crafty.needCraftClass("entity.CraftPlayer")
val CraftPlayer_getHandle = CraftPlayer_class.getMethod("getHandle")
?: error("Cannot find CraftPlayer#getHandle")
val Player.handle: Any
get() = CraftPlayer_getHandle(this)
val AdvancementProgress_class = Crafty.needNmsClass("AdvancementProgress")
val AdvancementDataPlayer_class = Crafty.needNmsClass("AdvancementDataPlayer")
val EntityPlayer_getAdvancementData = EntityPlayer_class.methods.find { method ->
method.returnType == AdvancementDataPlayer_class
&& method.parameterCount == 0
} ?: error("Cannot find EntityPlayer#getAdvancementData")
val AdvancementDataPlayer_getProgress = AdvancementDataPlayer_class.methods.find { method ->
method.returnType == AdvancementProgress_class
&& method.parameterCount == 1
&& method.parameterTypes[0] == Advancement_class
} ?: error("Cannot find AdvancementDataPlayer#getProgress")
val AdvancementDataPlayer_grantCriteria =
AdvancementDataPlayer_class.getDeclaredMethod("grantCriteria", Advancement_class, String::class.java)
?: error("Cannot find AdvancementDataPlayer#grantCriteria")
val AdvancementDataPlayer_revokeCriteria = try {
AdvancementDataPlayer_class.getDeclaredMethod("revokeCritera", Advancement_class, String::class.java) // lol
} catch (e: NoSuchMethodException) {
AdvancementDataPlayer_class.getDeclaredMethod("revokeCriteria", Advancement_class, String::class.java)
} ?: error("Cannot find AdvancementDataPlayer#revokeCriteria")
val AdvancementProgress_remainingCriteria = AdvancementProgress_class.getDeclaredMethod("getRemainingCriteria")
?: error("Cannot find AdvancementProgress#getRemainingCriteria")
val AdvancementProgress_awardedCriteria = AdvancementProgress_class.getDeclaredMethod("getAwardedCriteria")
?: error("Cannot find AdvancementProgress#getAwardedCriteria")
// start 1.12 -> 1.15
val ChatDeserializer_class by lazy { Crafty.needNmsClass("ChatDeserializer") }
val AdvancementDataWorld_class by lazy { Crafty.needNmsClass("AdvancementDataWorld") }
val ChatDeserializer_deserialize by lazy {
ChatDeserializer_class.methods.find { method ->
method.parameterCount == 3
&& method.parameterTypes.contains(Gson::class.java)
&& method.parameterTypes.contains(String::class.java)
&& method.parameterTypes.contains(Class::class.java)
} ?: error("Cannot find ChatDeserializer.deserialize")
}
val AdvancementDataWorld_DESERIALIZER by lazy {
AdvancementDataWorld_class.getField("DESERIALIZER").get(null)
?: error("Cannot find AdvancementDataWorld.DESERIALIZER")
}
// end 1.12 -> 1.15
// start 1.16+
val LootPredicateManager_class by lazy { Crafty.needNmsClass("LootPredicateManager") }
val MinecraftServer_getLootPredicateManager by lazy {
MinecraftServer_class.declaredMethods.find { method ->
method.returnType == LootPredicateManager_class
&& method.parameterCount == 0
} ?: error("Cannot find MinecraftServer#getLootPredicateManager")
}
val LootDeserializationContext_class by lazy { Crafty.needNmsClass("LootDeserializationContext") }
val LootDeserializationContext_ctr by lazy {
LootDeserializationContext_class.getDeclaredConstructor(MinecraftKey_class, LootPredicateManager_class)
?: error("Cannot find LootDeserializationContext constructor")
}
val SerializedAdvancement_deserialize by lazy {
SerializedAdvancement_class.declaredMethods.find { method ->
method.returnType == SerializedAdvancement_class
&& method.parameterCount == 2
&& method.parameterTypes.contains(JsonObject::class.java)
&& method.parameterTypes.contains(LootDeserializationContext_class)
} ?: error("Cannot find SerializedAdvancement.deserialize")
}
// end 1.16+
val SerializedAdvancement_deserialize by lazy {
SerializedAdvancement_class.declaredMethods.find { method ->
method.returnType == SerializedAdvancement_class
&& method.parameterCount == 2
&& method.parameterTypes.contains(JsonObject::class.java)
&& method.parameterTypes.contains(LootDeserializationContext_class)
} ?: error("Cannot find SerializedAdvancement.deserialize")
}
// end 1.16+
}
}
@@ -1,3 +1,26 @@
/*
* This file is part of AnnouncerPlus, licensed under the MIT License.
*
* Copyright (c) 2020-2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package xyz.jpenilla.announcerplus.task
import org.bukkit.scheduler.BukkitTask
@@ -8,38 +31,38 @@ import xyz.jpenilla.announcerplus.util.asyncTimer
import xyz.jpenilla.announcerplus.util.syncTimer
abstract class UpdateTask : KoinComponent {
private val announcerPlus: AnnouncerPlus by inject()
private var updateTask: BukkitTask? = null
var ticksLived = 0L
private val announcerPlus: AnnouncerPlus by inject()
private var updateTask: BukkitTask? = null
var ticksLived = 0L
open fun start(): UpdateTask = apply {
open fun start(): UpdateTask = apply {
stop()
val runnable = Runnable {
if (!shouldContinue()) {
stop()
val runnable = Runnable {
if (!shouldContinue()) {
stop()
}
update()
ticksLived++
}
updateTask = when (synchronizationContext()) {
SynchronizationContext.SYNC -> announcerPlus.syncTimer(0L, 1L, runnable)
SynchronizationContext.ASYNC -> announcerPlus.asyncTimer(0L, 1L, runnable)
}
}
update()
ticksLived++
}
open fun stop() {
updateTask?.cancel()
updateTask = null
updateTask = when (synchronizationContext()) {
SynchronizationContext.SYNC -> announcerPlus.syncTimer(0L, 1L, runnable)
SynchronizationContext.ASYNC -> announcerPlus.asyncTimer(0L, 1L, runnable)
}
}
abstract fun update()
open fun stop() {
updateTask?.cancel()
updateTask = null
}
abstract fun shouldContinue(): Boolean
abstract fun update()
abstract fun synchronizationContext(): SynchronizationContext
abstract fun shouldContinue(): Boolean
enum class SynchronizationContext {
SYNC,
ASYNC
}
abstract fun synchronizationContext(): SynchronizationContext
enum class SynchronizationContext {
SYNC,
ASYNC
}
}
@@ -1,3 +1,26 @@
/*
* This file is part of AnnouncerPlus, licensed under the MIT License.
*
* Copyright (c) 2020-2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package xyz.jpenilla.announcerplus.textanimation
import net.kyori.adventure.text.format.NamedTextColor
@@ -14,112 +37,112 @@ import xyz.jpenilla.announcerplus.textanimation.animation.ScrollingText
import xyz.jpenilla.announcerplus.textanimation.animation.Typewriter
class AnimationHolder(private val player: Player?, private val message: String) : KoinComponent {
companion object {
private val pattern = "\\{animate:/?([a-z][^}]*)/?}?".toPattern()
}
companion object {
private val pattern = "\\{animate:/?([a-z][^}]*)/?}?".toPattern()
}
private val configManager: ConfigManager by inject()
private val matcher = pattern.matcher(message)
private val animations = HashMap<String, TextAnimation>()
private val configManager: ConfigManager by inject()
private val matcher = pattern.matcher(message)
private val animations = HashMap<String, TextAnimation>()
init {
while (matcher.find()) {
val tokens = matcher.group(1).split(":")
init {
while (matcher.find()) {
val tokens = matcher.group(1).split(":")
when (tokens[0]) {
"scroll" -> {
val speed = try {
tokens[1].toFloat()
} catch (e: Exception) {
0.1f
}
animations[matcher.group()] = ScrollingGradient(speed)
}
"flash" -> {
val colors = ArrayList(tokens.subList(1, tokens.size))
var ticks: Int
try {
ticks = colors.last().toInt()
colors.removeAt(colors.lastIndex)
} catch (e: Exception) {
ticks = 10
}
animations[matcher.group()] = FlashingText(colors, ticks)
}
"pulse" -> {
val colors = ArrayList(tokens.subList(1, tokens.size))
var ticks: Int
try {
ticks = colors.last().toInt()
colors.removeAt(colors.lastIndex)
} catch (e: Exception) {
ticks = 10
}
val textColors = colors.map { color ->
NamedTextColor.NAMES.value(color)
?: TextColor.fromHexString(color)
?: NamedTextColor.WHITE
}
animations[matcher.group()] = PulsingColor(textColors, ticks)
}
"type" -> {
val text = tokens[1]
val ticks = try {
tokens[2].toInt()
} catch (e: Exception) {
6
}
animations[matcher.group()] = Typewriter(player, text, ticks)
}
"randomcolor" -> {
val type = try {
RandomColor.Type.of(tokens[1])
} catch (e: Exception) {
RandomColor.Type.PULSE
}
val ticks = try {
tokens[2].toInt()
} catch (e: Exception) {
10
}
animations[matcher.group()] = RandomColor(type, ticks)
}
"scrolltext" -> {
val text = tokens[1]
val window = try {
tokens[2].toInt()
} catch (e: Exception) {
10
}
val ticks = try {
tokens[3].toInt()
} catch (e: Exception) {
4
}
animations[matcher.group()] = ScrollingText(player, text, window, ticks)
}
}
when (tokens[0]) {
"scroll" -> {
val speed = try {
tokens[1].toFloat()
} catch (e: Exception) {
0.1f
}
animations[matcher.group()] = ScrollingGradient(speed)
}
}
fun parseNext(text: String = message): String {
var msg = text
animations.forEach { animation ->
msg = msg.replace(animation.key, animation.value.nextValue())
"flash" -> {
val colors = ArrayList(tokens.subList(1, tokens.size))
var ticks: Int
try {
ticks = colors.last().toInt()
colors.removeAt(colors.lastIndex)
} catch (e: Exception) {
ticks = 10
}
animations[matcher.group()] = FlashingText(colors, ticks)
}
return configManager.parse(player, msg)
}
fun parseCurrent(text: String = message): String {
var msg = text
animations.forEach { animation ->
msg = msg.replace(animation.key, animation.value.getValue())
"pulse" -> {
val colors = ArrayList(tokens.subList(1, tokens.size))
var ticks: Int
try {
ticks = colors.last().toInt()
colors.removeAt(colors.lastIndex)
} catch (e: Exception) {
ticks = 10
}
val textColors = colors.map { color ->
NamedTextColor.NAMES.value(color)
?: TextColor.fromHexString(color)
?: NamedTextColor.WHITE
}
animations[matcher.group()] = PulsingColor(textColors, ticks)
}
return configManager.parse(player, msg)
"type" -> {
val text = tokens[1]
val ticks = try {
tokens[2].toInt()
} catch (e: Exception) {
6
}
animations[matcher.group()] = Typewriter(player, text, ticks)
}
"randomcolor" -> {
val type = try {
RandomColor.Type.of(tokens[1])
} catch (e: Exception) {
RandomColor.Type.PULSE
}
val ticks = try {
tokens[2].toInt()
} catch (e: Exception) {
10
}
animations[matcher.group()] = RandomColor(type, ticks)
}
"scrolltext" -> {
val text = tokens[1]
val window = try {
tokens[2].toInt()
} catch (e: Exception) {
10
}
val ticks = try {
tokens[3].toInt()
} catch (e: Exception) {
4
}
animations[matcher.group()] = ScrollingText(player, text, window, ticks)
}
}
}
}
fun parseNext(text: String = message): String {
var msg = text
animations.forEach { animation ->
msg = msg.replace(animation.key, animation.value.nextValue())
}
return configManager.parse(player, msg)
}
fun parseCurrent(text: String = message): String {
var msg = text
animations.forEach { animation ->
msg = msg.replace(animation.key, animation.value.getValue())
}
return configManager.parse(player, msg)
}
}
@@ -1,8 +1,31 @@
/*
* This file is part of AnnouncerPlus, licensed under the MIT License.
*
* Copyright (c) 2020-2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package xyz.jpenilla.announcerplus.textanimation
import org.koin.core.KoinComponent
interface TextAnimation : KoinComponent {
fun getValue(): String
fun nextValue(): String
fun getValue(): String
fun nextValue(): String
}
@@ -1,25 +1,48 @@
/*
* This file is part of AnnouncerPlus, licensed under the MIT License.
*
* Copyright (c) 2020-2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package xyz.jpenilla.announcerplus.textanimation.animation
import xyz.jpenilla.announcerplus.textanimation.TextAnimation
class FlashingText(private val colors: List<String>, private val ticks: Int) : TextAnimation {
private var index = 0
private var ticksLived = 0
private var color = colors[0]
private var index = 0
private var ticksLived = 0
private var color = colors[0]
override fun getValue(): String {
return color
}
override fun getValue(): String {
return color
}
override fun nextValue(): String {
ticksLived++
if (ticksLived % ticks == 0) {
index++
if (index > colors.lastIndex) {
index = 0
}
color = colors[index]
}
return getValue()
override fun nextValue(): String {
ticksLived++
if (ticksLived % ticks == 0) {
index++
if (index > colors.lastIndex) {
index = 0
}
color = colors[index]
}
return getValue()
}
}
@@ -1,3 +1,26 @@
/*
* This file is part of AnnouncerPlus, licensed under the MIT License.
*
* Copyright (c) 2020-2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package xyz.jpenilla.announcerplus.textanimation.animation
import net.kyori.adventure.text.format.TextColor
@@ -5,46 +28,46 @@ import xyz.jpenilla.announcerplus.textanimation.TextAnimation
import kotlin.math.roundToInt
class PulsingColor(colors: List<TextColor>, ticks: Int) : TextAnimation {
private var index = 0
private var colorIndex = 0
private val colors = ArrayList(colors)
private var index = 0
private var colorIndex = 0
private val colors = ArrayList(colors)
init {
this.colors.add(this.colors[0])
init {
this.colors.add(this.colors[0])
}
private var color = this.colors[0].asHexString()
private val factorStep = 1.0f / ticks
override fun getValue(): String {
return color
}
override fun nextValue(): String {
color = nextColor().asHexString()
return getValue()
}
private fun interpolate(color1: TextColor, color2: TextColor, factor: Float): TextColor {
return TextColor.color(
(color1.red() + factor * (color2.red() - color1.red())).roundToInt(),
(color1.green() + factor * (color2.green() - color1.green())).roundToInt(),
(color1.blue() + factor * (color2.blue() - color1.blue())).roundToInt()
)
}
private fun nextColor(): TextColor {
if (factorStep * index > 1) {
colorIndex++
index = 0
}
private var color = this.colors[0].asHexString()
private val factorStep = 1.0f / ticks
val factor = factorStep * index++
override fun getValue(): String {
return color
if (colorIndex + 1 > colors.lastIndex) {
colorIndex = 0
}
override fun nextValue(): String {
color = nextColor().asHexString()
return getValue()
}
private fun interpolate(color1: TextColor, color2: TextColor, factor: Float): TextColor {
return TextColor.color(
(color1.red() + factor * (color2.red() - color1.red())).roundToInt(),
(color1.green() + factor * (color2.green() - color1.green())).roundToInt(),
(color1.blue() + factor * (color2.blue() - color1.blue())).roundToInt()
)
}
private fun nextColor(): TextColor {
if (factorStep * index > 1) {
colorIndex++
index = 0
}
val factor = factorStep * index++
if (colorIndex + 1 > colors.lastIndex) {
colorIndex = 0
}
return interpolate(colors[colorIndex], colors[colorIndex + 1], factor)
}
return interpolate(colors[colorIndex], colors[colorIndex + 1], factor)
}
}
@@ -1,3 +1,26 @@
/*
* This file is part of AnnouncerPlus, licensed under the MIT License.
*
* Copyright (c) 2020-2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package xyz.jpenilla.announcerplus.textanimation.animation
import net.kyori.adventure.text.format.TextColor
@@ -5,42 +28,42 @@ import xyz.jpenilla.announcerplus.textanimation.TextAnimation
import kotlin.random.Random
class RandomColor(type: Type, ticks: Int) : TextAnimation {
private var animation = when (type) {
Type.FLASH -> {
FlashingText(getRandomColors(64).map { color -> color.asHexString() }, ticks)
}
Type.PULSE -> {
PulsingColor(getRandomColors(64), ticks)
}
private var animation = when (type) {
Type.FLASH -> {
FlashingText(getRandomColors(64).map { color -> color.asHexString() }, ticks)
}
override fun getValue(): String {
return animation.getValue()
Type.PULSE -> {
PulsingColor(getRandomColors(64), ticks)
}
}
override fun nextValue(): String {
return animation.nextValue()
override fun getValue(): String {
return animation.getValue()
}
override fun nextValue(): String {
return animation.nextValue()
}
private fun getRandomColor(): TextColor {
return TextColor.color(Random.nextInt(256), Random.nextInt(256), Random.nextInt(256))
}
private fun getRandomColors(amount: Int): List<TextColor> {
val randomColors = arrayListOf<TextColor>()
for (i in 0 until amount) {
randomColors.add(getRandomColor())
}
return randomColors
}
private fun getRandomColor(): TextColor {
return TextColor.color(Random.nextInt(256), Random.nextInt(256), Random.nextInt(256))
}
private fun getRandomColors(amount: Int): List<TextColor> {
val randomColors = arrayListOf<TextColor>()
for (i in 0 until amount) {
randomColors.add(getRandomColor())
}
return randomColors
}
enum class Type {
FLASH, PULSE;
companion object {
fun of(text: String): Type {
return valueOf(text.toUpperCase())
}
}
enum class Type {
FLASH, PULSE;
companion object {
fun of(text: String): Type {
return valueOf(text.toUpperCase())
}
}
}
}
@@ -1,23 +1,46 @@
/*
* This file is part of AnnouncerPlus, licensed under the MIT License.
*
* Copyright (c) 2020-2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package xyz.jpenilla.announcerplus.textanimation.animation
import xyz.jpenilla.announcerplus.textanimation.TextAnimation
class ScrollingGradient(private val increment: Float) : TextAnimation {
companion object {
private val GRADIENT_PHASE_RANGE = -1.0f..1.0f
}
companion object {
private val GRADIENT_PHASE_RANGE = -1.0f..1.0f
}
private var phase = -1f
private var phase = -1f
override fun getValue(): String {
return phase.coerceIn(GRADIENT_PHASE_RANGE).toString()
}
override fun getValue(): String {
return phase.coerceIn(GRADIENT_PHASE_RANGE).toString()
}
override fun nextValue(): String {
if (phase >= 1f) {
phase = -1f
}
phase += increment
return getValue()
override fun nextValue(): String {
if (phase >= 1f) {
phase = -1f
}
phase += increment
return getValue()
}
}
@@ -1,3 +1,26 @@
/*
* This file is part of AnnouncerPlus, licensed under the MIT License.
*
* Copyright (c) 2020-2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package xyz.jpenilla.announcerplus.textanimation.animation
import org.bukkit.entity.Player
@@ -6,43 +29,43 @@ import xyz.jpenilla.announcerplus.config.ConfigManager
import xyz.jpenilla.announcerplus.textanimation.TextAnimation
class ScrollingText(
private val player: Player?,
text: String,
private val windowSize: Int,
private val ticks: Int
private val player: Player?,
text: String,
private val windowSize: Int,
private val ticks: Int
) : TextAnimation {
private val configManager: ConfigManager by inject()
private val spaces = getSpaces(windowSize)
private val text = "$spaces$text$spaces"
private var index = 0
private var ticksLived = 0
private val configManager: ConfigManager by inject()
private val spaces = getSpaces(windowSize)
private val text = "$spaces$text$spaces"
private var index = 0
private var ticksLived = 0
private fun getSpaces(amount: Int): String {
val sb = StringBuilder()
for (i in 1..amount) {
sb.append(" ")
}
return sb.toString()
private fun getSpaces(amount: Int): String {
val sb = StringBuilder()
for (i in 1..amount) {
sb.append(" ")
}
return sb.toString()
}
override fun getValue(): String {
return try {
configManager.parse(player, text).substring(index, index + windowSize)
} catch (e: Exception) {
//if the placeholders changed in a way that causes us to out of bounds
index = 0
configManager.parse(player, text).substring(index, index + windowSize)
}
override fun getValue(): String {
return try {
configManager.parse(player, text).substring(index, index + windowSize)
} catch (e: Exception) {
//if the placeholders changed in a way that causes us to out of bounds
index = 0
configManager.parse(player, text).substring(index, index + windowSize)
}
}
override fun nextValue(): String {
ticksLived++
if (ticksLived % ticks == 0) {
index++
if (index > configManager.parse(player, text).length - windowSize) {
index = 0
}
}
return getValue()
override fun nextValue(): String {
ticksLived++
if (ticksLived % ticks == 0) {
index++
if (index > configManager.parse(player, text).length - windowSize) {
index = 0
}
}
return getValue()
}
}
@@ -1,3 +1,26 @@
/*
* This file is part of AnnouncerPlus, licensed under the MIT License.
*
* Copyright (c) 2020-2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package xyz.jpenilla.announcerplus.textanimation.animation
import org.bukkit.entity.Player
@@ -6,33 +29,33 @@ import xyz.jpenilla.announcerplus.config.ConfigManager
import xyz.jpenilla.announcerplus.textanimation.TextAnimation
class Typewriter(private val player: Player?, private val text: String, private val ticks: Int) : TextAnimation {
private val configManager: ConfigManager by inject()
private var index = 0
private var ticksLived = 0
private var showUnderscore = true
private val configManager: ConfigManager by inject()
private var index = 0
private var ticksLived = 0
private var showUnderscore = true
override fun getValue(): String {
val s = try {
configManager.parse(player, text).substring(0, index)
} catch (e: Exception) {
//if the placeholders changed in a way that causes us to out of bounds
index = 0
configManager.parse(player, text).substring(0, index)
}
return "$s${if (showUnderscore) "_" else " "}"
override fun getValue(): String {
val s = try {
configManager.parse(player, text).substring(0, index)
} catch (e: Exception) {
//if the placeholders changed in a way that causes us to out of bounds
index = 0
configManager.parse(player, text).substring(0, index)
}
return "$s${if (showUnderscore) "_" else " "}"
}
override fun nextValue(): String {
ticksLived++
if (ticksLived % 5 == 0) {
showUnderscore = !showUnderscore
}
if (ticksLived % ticks == 0) {
index++
if (index > configManager.parse(player, text).length) {
index = 0
}
}
return getValue()
override fun nextValue(): String {
ticksLived++
if (ticksLived % 5 == 0) {
showUnderscore = !showUnderscore
}
if (ticksLived % ticks == 0) {
index++
if (index > configManager.parse(player, text).length) {
index = 0
}
}
return getValue()
}
}
@@ -1,3 +1,26 @@
/*
* This file is part of AnnouncerPlus, licensed under the MIT License.
*
* Copyright (c) 2020-2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package xyz.jpenilla.announcerplus.util
import net.kyori.adventure.text.Component
@@ -10,32 +33,32 @@ import net.kyori.adventure.text.format.TextColor.color
import net.kyori.adventure.text.format.TextDecoration
object Constants {
const val CONFIG_COMMENT_SOUNDS_LINE2: String =
"Separate multiple sounds by commas, i.e. \"minecraft:entity.strider.happy,minecraft:entity.villager.ambient,minecraft:block.note_block.cow_bell\""
const val CONFIG_COMMENT_SOUNDS_LINE2: String =
"Separate multiple sounds by commas, i.e. \"minecraft:entity.strider.happy,minecraft:entity.villager.ambient,minecraft:block.note_block.cow_bell\""
const val CONFIG_COMMENT_SOUNDS_RANDOM: String =
"Should a random join sound be chosen(true) or should all of them play(false)"
const val CONFIG_COMMENT_SOUNDS_RANDOM: String =
"Should a random join sound be chosen(true) or should all of them play(false)"
val CHAT_PREFIX: Component = text()
.append(text("[", NamedTextColor.WHITE))
.append(text("A", color(0x47EB46)))
.append(text("P", color(0x2CF58B)))
.append(text("]", NamedTextColor.WHITE))
.append(space())
.hoverEvent(
text()
.append(text("Announcer", color(0x47EB46)))
.append(text("Plus", color(0x2CF58B), TextDecoration.ITALIC))
.append(newline())
.append(
text(
" Click for help",
NamedTextColor.GRAY,
TextDecoration.ITALIC
)
)
.build()
val CHAT_PREFIX: Component = text()
.append(text("[", NamedTextColor.WHITE))
.append(text("A", color(0x47EB46)))
.append(text("P", color(0x2CF58B)))
.append(text("]", NamedTextColor.WHITE))
.append(space())
.hoverEvent(
text()
.append(text("Announcer", color(0x47EB46)))
.append(text("Plus", color(0x2CF58B), TextDecoration.ITALIC))
.append(newline())
.append(
text(
" Click for help",
NamedTextColor.GRAY,
TextDecoration.ITALIC
)
)
.clickEvent(ClickEvent.runCommand("/announcerplus help"))
.build()
)
.clickEvent(ClickEvent.runCommand("/announcerplus help"))
.build()
}
@@ -1,5 +1,29 @@
/*
* This file is part of AnnouncerPlus, licensed under the MIT License.
*
* Copyright (c) 2020-2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package xyz.jpenilla.announcerplus.util
import cloud.commandframework.ArgumentDescription
import org.bukkit.Bukkit
import org.bukkit.plugin.Plugin
import org.bukkit.scheduler.BukkitTask
@@ -20,10 +44,18 @@ fun Plugin.runAsync(
): BukkitTask =
Bukkit.getScheduler().runTaskLaterAsynchronously(this, runnable, delay)
fun Plugin.asyncTimer(delay: Long, interval: Long, runnable: Runnable): BukkitTask =
fun Plugin.asyncTimer(
delay: Long,
interval: Long,
runnable: Runnable
): BukkitTask =
Bukkit.getScheduler().runTaskTimerAsynchronously(this, runnable, delay, interval)
fun Plugin.syncTimer(delay: Long, interval: Long, runnable: Runnable): BukkitTask =
fun Plugin.syncTimer(
delay: Long,
interval: Long,
runnable: Runnable
): BukkitTask =
Bukkit.getScheduler().runTaskTimer(this, runnable, delay, interval)
fun <T> Plugin.getOnMain(supplier: () -> T): T {
@@ -31,3 +63,15 @@ fun <T> Plugin.getOnMain(supplier: () -> T): T {
runSync { future.complete(supplier()) }
return future.join()
}
/**
* Get a [ArgumentDescription], defaulting to [ArgumentDescription.empty]
*
* @param description description string
* @return the description
* @since 1.4.0
*/
fun description(
description: String = ""
): ArgumentDescription =
if (description.isEmpty()) ArgumentDescription.empty() else ArgumentDescription.of(description)
@@ -1,3 +1,26 @@
/*
* This file is part of AnnouncerPlus, licensed under the MIT License.
*
* Copyright (c) 2020-2021 Jason Penilla
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package xyz.jpenilla.announcerplus.util
import com.google.common.base.Charsets
@@ -9,34 +32,34 @@ import java.net.URL
import java.util.LinkedList
class UpdateChecker(private val plugin: AnnouncerPlus, private val githubRepo: String) {
private val parser = JsonParser()
private val parser = JsonParser()
fun updateCheck() {
plugin.runAsync {
val result = try {
parser.parse(InputStreamReader(URL("https://api.github.com/repos/$githubRepo/releases").openStream(), Charsets.UTF_8)).asJsonArray
} catch (exception: IOException) {
plugin.logger.info("Cannot look for updates: " + exception.message)
return@runAsync
}
val versionMap = LinkedHashMap<String, String>()
result.forEach { versionMap[it.asJsonObject["tag_name"].asString] = it.asJsonObject["html_url"].asString }
val versionList = LinkedList(versionMap.keys)
val currentVersion = "v" + plugin.description.version
if (versionList[0] == currentVersion) {
plugin.logger.info("You are running the latest version of ${plugin.name}! :)")
return@runAsync
}
if (currentVersion.contains("SNAPSHOT")) {
plugin.logger.info("You are running a development build of ${plugin.name}! ($currentVersion)")
plugin.logger.info("The latest official release is " + versionList[0])
return@runAsync
}
val versionsBehind = versionList.indexOf(currentVersion)
plugin.logger.info("There is an update available for ${plugin.name}!")
plugin.logger.info("You are running version $currentVersion, which is ${if (versionsBehind == -1) "many" else versionsBehind} versions outdated.")
plugin.logger.info("Download the latest version, ${versionList[0]} from GitHub at the link below:")
plugin.logger.info(versionMap[versionList[0]])
}
fun updateCheck() {
plugin.runAsync {
val result = try {
parser.parse(InputStreamReader(URL("https://api.github.com/repos/$githubRepo/releases").openStream(), Charsets.UTF_8)).asJsonArray
} catch (exception: IOException) {
plugin.logger.info("Cannot look for updates: " + exception.message)
return@runAsync
}
val versionMap = LinkedHashMap<String, String>()
result.forEach { versionMap[it.asJsonObject["tag_name"].asString] = it.asJsonObject["html_url"].asString }
val versionList = LinkedList(versionMap.keys)
val currentVersion = "v" + plugin.description.version
if (versionList[0] == currentVersion) {
// Up to date, do nothing
return@runAsync
}
if (currentVersion.contains("SNAPSHOT")) {
plugin.logger.info("You are running a development build of ${plugin.name}! ($currentVersion)")
plugin.logger.info("The latest official release is " + versionList[0])
return@runAsync
}
val versionsBehind = versionList.indexOf(currentVersion)
plugin.logger.info("There is an update available for ${plugin.name}!")
plugin.logger.info("You are running version $currentVersion, which is ${if (versionsBehind == -1) "many" else versionsBehind} versions outdated.")
plugin.logger.info("Download the latest version, ${versionList[0]} from GitHub at the link below:")
plugin.logger.info(versionMap[versionList[0]])
}
}
}