├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── 3-feature_request.yml │ └── 2-webui_bug_report.yml ├── FUNDING.yml ├── workflows │ ├── stale.yml │ ├── gradle.yml │ └── publish.yml └── dependabot.yml ├── icon.png ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── crowdin.yml ├── settings.gradle.kts ├── src └── main │ ├── resources │ ├── extension.yml │ └── langs │ │ ├── zh_CN.json │ │ ├── ja_JP.json │ │ ├── ko_KR.json │ │ ├── zh_TW.json │ │ ├── ar_SA.json │ │ ├── hu_HU.json │ │ ├── he_IL.json │ │ ├── af_ZA.json │ │ ├── ca_ES.json │ │ ├── en_US.json │ │ ├── sr_SP.json │ │ ├── cs_CZ.json │ │ ├── da_DK.json │ │ ├── vi_VN.json │ │ ├── no_NO.json │ │ └── sv_SE.json │ └── java │ └── me │ └── spartacus04 │ └── jext │ ├── config │ ├── legacy │ │ ├── ConfigMigrator.kt │ │ ├── V2Config.kt │ │ ├── V1Config.kt │ │ ├── V3Config.kt │ │ ├── V4Config.kt │ │ ├── V5Config.kt │ │ ├── V6Config.kt │ │ ├── V7Config.kt │ │ └── V8Config.kt │ ├── ConfigField.kt │ ├── fields │ │ ├── FieldJukeboxBehaviour.kt │ │ ├── FieldGuiStyle.kt │ │ └── FieldLanguageMode.kt │ └── ConfigFactory.kt │ ├── geyser │ ├── plugin │ │ ├── GeyserMode.kt │ │ ├── GeyserStandalone.kt │ │ ├── GeyserManager.kt │ │ └── GeyserSpigot.kt │ ├── IPC │ │ └── GeyserIPC.kt │ └── extension │ │ └── GeyserExtension.kt │ ├── utils │ ├── IsRecordFragment.kt │ ├── sha1.kt │ ├── JextMetrics.kt │ ├── WrapperPlayServerStopSoundCategory.kt │ ├── BaseUrl.kt │ └── JextFileBind.kt │ ├── discs │ ├── sources │ │ ├── DiscSource.kt │ │ ├── file │ │ │ ├── FileSource.kt │ │ │ └── FileDisc.kt │ │ └── nbs │ │ │ ├── NbsSource.kt │ │ │ └── NbsDisc.kt │ ├── discplaying │ │ ├── DefaultDiscPlayingMethod.kt │ │ ├── DiscPlayingMethod.kt │ │ └── NbsDiscPlayingMethod.kt │ ├── discstopping │ │ ├── DiscStoppingMethod.kt │ │ ├── NbsDiscStoppingMethod.kt │ │ └── DefaultDiscStoppingMethod.kt │ ├── DiscPersistentDataContainer.kt │ └── DiscUtils.kt │ ├── commands │ ├── JukeboxGuiCommand.kt │ ├── AdminGuiCommand.kt │ ├── ReloadCommand.kt │ ├── DiscCommand.kt │ ├── customArgs │ │ └── ArgumentDisc.kt │ ├── ExportCommand.kt │ ├── WebUICommand.kt │ ├── FragmentCommand.kt │ ├── PlayAtCommand.kt │ ├── StopMusicCommand.kt │ ├── DiscGiveCommand.kt │ ├── PlayMusicCommand.kt │ └── FragmentGiveCommand.kt │ ├── webapi │ ├── auth │ │ ├── DisconnectHandler.kt │ │ ├── HealthHandler.kt │ │ └── ConnectHandler.kt │ ├── discs │ │ ├── DiscsApplyGeyserHandler.kt │ │ ├── DiscsApplyHandler.kt │ │ └── DiscsReadHandler.kt │ ├── ResourcePackHandler.kt │ ├── config │ │ ├── ConfigApplyHandler.kt │ │ └── ConfigReadHandler.kt │ └── utils │ │ └── JextHttpHandler.kt │ ├── JextNamespace.kt │ ├── gui │ ├── items │ │ ├── NextPageItem.kt │ │ ├── PreviousPageItem.kt │ │ ├── ScrollUpItem.kt │ │ └── ScrollDownItem.kt │ ├── AdminGui.kt │ ├── JukeboxEntry.kt │ └── BaseGui.kt │ ├── integrations │ ├── GriefPreventionPermissionIntegration.kt │ ├── WorldGuardPermissionIntegration.kt │ ├── PermissionIntegration.kt │ └── PermissionsIntegrationManager.kt │ ├── listeners │ ├── ListenerRegistrant.kt │ ├── CreeperDeathEvent.kt │ ├── ResourceStatusEvent.kt │ ├── PrepareCraftingEvent.kt │ ├── CrafterCraftDiscEvent.kt │ ├── PlayerJoinEvent.kt │ ├── RecordPacketEvent.kt │ ├── InventoryMoveItemEvent.kt │ ├── BlockBrushEvent.kt │ ├── VaultDispenseEvent.kt │ ├── TrialSpawnerDispenseEvent.kt │ └── JukeboxClickEvent.kt │ └── language │ └── DefaultMessages.kt ├── docsAssets └── logo-styles.css ├── LICENSE ├── proguard-rules.pro ├── gradle.properties ├── .gitignore └── gradlew.bat /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spartacus04/jext-reborn/HEAD/icon.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spartacus04/jext-reborn/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /crowdin.yml: -------------------------------------------------------------------------------- 1 | files: 2 | - source: /src/main/resources/langs/en_US.json 3 | translation: /src/main/resources/langs/%locale_with_underscore%.json 4 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | rootProject.name = "JEXT-Reborn" 3 | 4 | plugins { 5 | id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" 6 | } -------------------------------------------------------------------------------- /src/main/resources/extension.yml: -------------------------------------------------------------------------------- 1 | id: jext 2 | name: JukeboxExtendedReborn 3 | main: me.spartacus04.jext.geyser.extension.GeyserExtension 4 | api: 1.0.0 5 | version: ${version} 6 | authors: [ Spartacus04 ] -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/config/legacy/ConfigMigrator.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.config.legacy 2 | 3 | import me.spartacus04.colosseum.ColosseumPlugin 4 | 5 | internal interface ConfigMigrator { 6 | fun migrateToNext(plugin: ColosseumPlugin) : String 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/geyser/plugin/GeyserMode.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.geyser.plugin 2 | 3 | import java.util.UUID 4 | 5 | internal interface GeyserMode { 6 | fun isBedrockPlayer(player: UUID): Boolean 7 | 8 | fun applyResourcePack(buffer: ByteArray) 9 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.0-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/config/ConfigField.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.config 2 | 3 | @MustBeDocumented 4 | @Retention(AnnotationRetention.RUNTIME) 5 | @Target(AnnotationTarget.FIELD) 6 | internal annotation class ConfigField( 7 | val name: String, 8 | val description: String, 9 | val defaultValue: String, 10 | val enumValues: String = "" 11 | ) -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/utils/IsRecordFragment.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.utils 2 | 3 | import me.spartacus04.jext.utils.Constants.FRAGMENT_LIST 4 | import org.bukkit.Material 5 | 6 | /** 7 | * Checks if the material is a record fragment 8 | */ 9 | internal val Material.isRecordFragment: Boolean 10 | get() { 11 | return FRAGMENT_LIST.contains(this) 12 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/discs/sources/DiscSource.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.discs.sources 2 | 3 | import me.spartacus04.jext.Jext 4 | import me.spartacus04.jext.discs.Disc 5 | 6 | /** 7 | * The interface `DiscSource` is used to define the methods used to get discs. 8 | */ 9 | interface DiscSource { 10 | 11 | /** 12 | * Gets the discs. 13 | * 14 | * @return The list of discs. 15 | */ 16 | suspend fun getDiscs(plugin: Jext): List 17 | } -------------------------------------------------------------------------------- /docsAssets/logo-styles.css: -------------------------------------------------------------------------------- 1 | /* 2 | * All Margins and sizes are custom for the ktor-logo.png file. 3 | * You may need to modify it and find what works best for your case. 4 | */ 5 | .library-name a { 6 | position: relative; 7 | margin-left: 55px; 8 | } 9 | 10 | .library-name a::before { 11 | content: ''; 12 | background-image: url('../images/icon.png'); 13 | background-size: contain; 14 | background-repeat: no-repeat; 15 | position: absolute; 16 | width: 52px; 17 | height: 50px; 18 | top: -18px; 19 | left: -62px; 20 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/commands/JukeboxGuiCommand.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.commands 2 | 3 | import me.spartacus04.colosseum.commandHandling.command.ColosseumCommand 4 | import me.spartacus04.jext.Jext 5 | import me.spartacus04.jext.gui.JukeboxGui 6 | import org.bukkit.entity.Player 7 | 8 | class JukeboxGuiCommand(val plugin: Jext) : ColosseumCommand(plugin) { 9 | override val commandData = commandDescriptor("jukeboxgui") { 10 | subCommandName = "gui" 11 | } 12 | 13 | override fun executePlayer(ctx: CommandContext) { 14 | JukeboxGui.open(ctx.sender) 15 | } 16 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/config/fields/FieldJukeboxBehaviour.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.config.fields 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | enum class FieldJukeboxBehaviour { 6 | @SerializedName("vanilla") 7 | VANILLA, 8 | 9 | @SerializedName("gui") 10 | GUI; 11 | 12 | 13 | internal companion object { 14 | fun fromString(name: String): FieldJukeboxBehaviour { 15 | return entries.find { it.name == name || it.name == name.replace("-", "_").uppercase() } ?: throw IllegalArgumentException("Invalid serialized name") 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/webapi/auth/DisconnectHandler.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.webapi.auth 2 | 3 | import com.sun.net.httpserver.HttpExchange 4 | import me.spartacus04.jext.Jext 5 | import me.spartacus04.jext.webapi.utils.JextHttpHandler 6 | 7 | internal class DisconnectHandler(plugin: Jext) : JextHttpHandler(plugin, true) { 8 | override fun onPost(exchange: HttpExchange) { 9 | val addr = exchange.remoteAddress.address.address.map { it.toInt() }.joinToString(".") 10 | 11 | ConnectHandler.connectedHashMap.remove(addr) 12 | 13 | exchange.sendResponseHeaders(204, 0) 14 | } 15 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/commands/AdminGuiCommand.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.commands 2 | 3 | import me.spartacus04.colosseum.ColosseumPlugin 4 | import me.spartacus04.colosseum.commandHandling.command.ColosseumCommand 5 | import me.spartacus04.jext.gui.AdminGui 6 | import org.bukkit.entity.Player 7 | 8 | class AdminGuiCommand(plugin: ColosseumPlugin) : ColosseumCommand(plugin) { 9 | override val commandData = commandDescriptor("jextadmingui") { 10 | subCommandName = "admingui" 11 | } 12 | 13 | override fun executePlayer(ctx: CommandContext) { 14 | AdminGui.open(ctx.sender) 15 | } 16 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/webapi/discs/DiscsApplyGeyserHandler.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.webapi.discs 2 | 3 | import com.sun.net.httpserver.HttpExchange 4 | import me.spartacus04.jext.Jext 5 | import me.spartacus04.jext.webapi.utils.JextHttpHandler 6 | 7 | internal class DiscsApplyGeyserHandler(plugin: Jext) : JextHttpHandler(plugin, true) { 8 | override fun onPost(exchange: HttpExchange) { 9 | val body = exchange.requestBody.readBytes() 10 | 11 | plugin.geyserManager.applyResourcePack(body) 12 | exchange.sendResponseHeaders(204, 0) 13 | 14 | plugin.geyserManager.reloadGeyser() 15 | } 16 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/JextNamespace.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext 2 | 3 | import me.spartacus04.jext.Jext.Companion.INSTANCE 4 | import org.bukkit.NamespacedKey 5 | 6 | /** 7 | * The above class is an enum class that represents different namespaces and assigns a NamespacedKey to each namespace value. 8 | */ 9 | internal enum class JextNamespace(key: String) { 10 | NAMESPACE_ID("jext.namespace_id"), 11 | IDENTIFIER("jext.identifier"); 12 | 13 | private val namespacedKey = NamespacedKey(INSTANCE, key) 14 | /** 15 | * The function returns a NamespacedKey. 16 | */ 17 | operator fun invoke(): NamespacedKey = namespacedKey 18 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/discs/sources/file/FileSource.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.discs.sources.file 2 | 3 | import com.google.common.reflect.TypeToken 4 | import me.spartacus04.jext.Jext 5 | import me.spartacus04.jext.discs.Disc 6 | import me.spartacus04.jext.discs.sources.DiscSource 7 | 8 | internal class FileSource : DiscSource { 9 | override suspend fun getDiscs(plugin: Jext): List { 10 | val discTypeToken = object : TypeToken>() {}.type 11 | 12 | val contents = plugin.assetsManager.getAsset("discs")?.bufferedReader()?.readText() ?: return emptyList() 13 | 14 | return plugin.gson.fromJson>(contents, discTypeToken).map { it.toJextDisc(plugin) } 15 | } 16 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/utils/sha1.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.utils 2 | 3 | import java.io.File 4 | import java.io.FileInputStream 5 | import java.security.MessageDigest 6 | 7 | fun getFileSha1Hash(file: File): ByteArray { 8 | return try { 9 | val md = MessageDigest.getInstance("SHA-1") 10 | val fis = FileInputStream(file) 11 | 12 | val buffer = ByteArray(4096) // Read file in chunks of 4KB 13 | var bytesRead: Int 14 | 15 | while (fis.read(buffer).also { bytesRead = it } > 0) { 16 | md.update(buffer, 0, bytesRead) 17 | } 18 | 19 | fis.close() 20 | md.digest() 21 | } catch (e: Exception) { 22 | throw RuntimeException("Error calculating SHA-1 hash", e) 23 | } 24 | } -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [spartacus04] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/webapi/ResourcePackHandler.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.webapi 2 | 3 | import com.sun.net.httpserver.HttpExchange 4 | import me.spartacus04.jext.Jext 5 | import me.spartacus04.jext.webapi.utils.JextHttpHandler 6 | 7 | internal class ResourcePackHandler(plugin: Jext) : JextHttpHandler(plugin, false) { 8 | private val file = plugin.dataFolder.resolve("resource-pack.zip") 9 | override fun onGet(exchange: HttpExchange) { 10 | if(!file.exists()) { 11 | return notFound(exchange) 12 | } 13 | 14 | exchange.sendResponseHeaders(200, file.length()) 15 | 16 | file.inputStream().use { input -> 17 | exchange.responseBody.use { output -> 18 | input.copyTo(output) 19 | } 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/config/fields/FieldGuiStyle.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.config.fields 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | enum class FieldGuiStyle { 6 | @SerializedName("scroll-vertical") 7 | SCROLL_VERTICAL, 8 | 9 | @SerializedName("scroll-horizontal") 10 | SCROLL_HORIZONTAL, 11 | 12 | @SerializedName("page-horizontal") 13 | PAGE_HORIZONTAL, 14 | 15 | @SerializedName("page-vertical") 16 | PAGE_VERTICAL; 17 | 18 | @Suppress("unused") 19 | internal companion object { 20 | fun fromString(name: String): FieldGuiStyle { 21 | return FieldGuiStyle.entries.find { it.name == name || it.name == name.replace("-", "_").uppercase() } ?: throw IllegalArgumentException("Invalid serialized name") 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/webapi/config/ConfigApplyHandler.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.webapi.config 2 | 3 | import com.sun.net.httpserver.HttpExchange 4 | import me.spartacus04.jext.Jext 5 | import me.spartacus04.jext.utils.JextMetrics 6 | import me.spartacus04.jext.webapi.utils.JextHttpHandler 7 | 8 | internal class ConfigApplyHandler(plugin: Jext) : JextHttpHandler(plugin, true) { 9 | override fun onPost(exchange: HttpExchange) { 10 | val body = exchange.requestBody.bufferedReader().use { it.readText() } 11 | 12 | if(plugin.config.fromText(body)) { 13 | plugin.config.save() 14 | 15 | exchange.sendResponseHeaders(204, 0) 16 | JextMetrics.reloadMetrics() 17 | plugin.webServer.reload() 18 | } else { 19 | exchange.sendResponseHeaders(400, 0) 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | # This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time. 2 | # 3 | # You can adjust the behavior by modifying this file. 4 | # For more information, see: 5 | # https://github.com/actions/stale 6 | name: Mark stale issues and pull requests 7 | 8 | on: 9 | schedule: 10 | - cron: '00 20 * * *' 11 | workflow_dispatch: 12 | 13 | jobs: 14 | stale: 15 | 16 | runs-on: ubuntu-latest 17 | permissions: 18 | issues: write 19 | pull-requests: write 20 | 21 | steps: 22 | - uses: actions/stale@v5 23 | with: 24 | repo-token: ${{ secrets.GITHUB_TOKEN }} 25 | stale-issue-message: 'Stale issue, closing this rn' 26 | stale-pr-message: 'Stale pull request, closing this rn' 27 | stale-issue-label: 'no-issue-activity' 28 | stale-pr-label: 'no-pr-activity' 29 | -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/gui/items/NextPageItem.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.gui.items 2 | 3 | import me.spartacus04.jext.Jext 4 | import org.bukkit.Material 5 | import org.bukkit.entity.Player 6 | import xyz.xenondevs.invui.gui.PagedGui 7 | import xyz.xenondevs.invui.item.ItemProvider 8 | import xyz.xenondevs.invui.item.builder.ItemBuilder 9 | import xyz.xenondevs.invui.item.impl.controlitem.PageItem 10 | 11 | internal class NextPageItem(private val player: Player, private val plugin: Jext) : PageItem(true) { 12 | override fun getItemProvider(gui: PagedGui<*>): ItemProvider { 13 | val builder = ItemBuilder(Material.GREEN_STAINED_GLASS_PANE) 14 | 15 | builder.setDisplayName(plugin.i18nManager!![player, "next-page"]!!) 16 | 17 | if (!gui.hasNextPage()) 18 | builder.addLoreLines(plugin.i18nManager!![player, "no-page"]!!) 19 | 20 | return builder 21 | } 22 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/3-feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Suggest an idea for this project. 3 | title: '[FEATURE REQUEST]: ' 4 | assignees: 5 | - spartacus04 6 | labels: 7 | - enhancement 8 | body: 9 | - type: textarea 10 | id: a 11 | attributes: 12 | label: Is your feature request related to a problem? Please describe. 13 | description: A clear and concise description of what the problem is. 14 | placeholder: Ex. I'm always frustrated when [...] 15 | - type: textarea 16 | id: b 17 | attributes: 18 | label: Describe the solution you'd like. 19 | description: A clear and concise description of what you want to happen. 20 | validations: 21 | required: true 22 | - type: textarea 23 | id: d 24 | attributes: 25 | label: Additional context 26 | description: Add any other context or screenshots about the feature request here. 27 | -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/geyser/plugin/GeyserStandalone.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.geyser.plugin 2 | 3 | import me.spartacus04.jext.geyser.IPC.GeyserIPC 4 | import java.util.UUID 5 | import java.util.Base64 6 | 7 | internal class GeyserStandalone : GeyserMode { 8 | private val ipcCommunicator = GeyserIPC() 9 | 10 | init { 11 | ipcCommunicator.sendAndReceive(GeyserIPC.GeyserIPCCommand.OK) ?: throw IllegalStateException("Geyser not found") 12 | } 13 | 14 | override fun isBedrockPlayer(player: UUID): Boolean { 15 | ipcCommunicator.sendAndReceive(GeyserIPC.GeyserIPCCommand.IS_BEDROCK, player.toString())?.let { 16 | return it.toBoolean() 17 | } ?: return false 18 | } 19 | 20 | override fun applyResourcePack(buffer: ByteArray) { 21 | ipcCommunicator.send(GeyserIPC.GeyserIPCCommand.RESOURCE_PACK, Base64.getEncoder().encodeToString(buffer)) 22 | } 23 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/gui/items/PreviousPageItem.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.gui.items 2 | 3 | import me.spartacus04.jext.Jext 4 | import org.bukkit.Material 5 | import org.bukkit.entity.Player 6 | import xyz.xenondevs.invui.gui.PagedGui 7 | import xyz.xenondevs.invui.item.ItemProvider 8 | import xyz.xenondevs.invui.item.builder.ItemBuilder 9 | import xyz.xenondevs.invui.item.impl.controlitem.PageItem 10 | 11 | internal class PreviousPageItem(private val player: Player, private val plugin: Jext) : PageItem(false) { 12 | override fun getItemProvider(gui: PagedGui<*>): ItemProvider { 13 | val builder = ItemBuilder(Material.RED_STAINED_GLASS_PANE) 14 | 15 | builder.setDisplayName(plugin.i18nManager!![player, "prev-page"]!!) 16 | 17 | if (!gui.hasPreviousPage()) 18 | builder.addLoreLines(plugin.i18nManager!![player, "no-page"]!!) 19 | 20 | return builder 21 | } 22 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/webapi/auth/HealthHandler.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.webapi.auth 2 | 3 | import com.sun.net.httpserver.HttpExchange 4 | import me.spartacus04.jext.Jext 5 | import me.spartacus04.jext.webapi.utils.JextHttpHandler 6 | 7 | internal class HealthHandler(plugin: Jext) : JextHttpHandler(plugin, false) { 8 | override fun onGet(exchange: HttpExchange) { 9 | val token = exchange.requestHeaders["Authorization"]?.first()?.replace("Bearer ", "") 10 | 11 | if(token == null) { 12 | exchange.sendResponseHeaders(204, 0) 13 | } else { 14 | val addr = exchange.remoteAddress.address.address.map { it.toInt() }.joinToString(".") 15 | 16 | if(ConnectHandler.connectedHashMap[addr] == token) { 17 | exchange.sendResponseHeaders(204, 0) 18 | } else { 19 | exchange.sendResponseHeaders(401, 0) 20 | } 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/commands/ReloadCommand.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.commands 2 | 3 | import me.spartacus04.colosseum.commandHandling.command.ColosseumCommand 4 | import me.spartacus04.colosseum.i18n.sendI18nConfirm 5 | import me.spartacus04.jext.Jext 6 | import me.spartacus04.jext.utils.JextMetrics 7 | import org.bukkit.command.CommandSender 8 | 9 | class ReloadCommand(val plugin: Jext) : ColosseumCommand(plugin) { 10 | override val commandData = commandDescriptor("jextreload") { 11 | subCommandName = "reload" 12 | } 13 | 14 | override fun execute(ctx: CommandContext) { 15 | plugin.config.read() 16 | plugin.discs.reloadDiscs() 17 | plugin.integrations.reloadDefaultIntegrations() 18 | JextMetrics.reloadMetrics() 19 | plugin.webServer.reload() 20 | plugin.geyserManager.reloadGeyser() 21 | 22 | ctx.sender.sendI18nConfirm(plugin, "reloaded") 23 | } 24 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/commands/DiscCommand.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.commands 2 | 3 | import me.spartacus04.colosseum.commandHandling.command.ColosseumCommand 4 | import me.spartacus04.colosseum.i18n.sendI18nConfirm 5 | import me.spartacus04.colosseum.i18n.sendI18nWarn 6 | import me.spartacus04.jext.Jext 7 | import me.spartacus04.jext.commands.customArgs.ArgumentDisc 8 | import me.spartacus04.jext.discs.Disc 9 | import org.bukkit.entity.Player 10 | 11 | class DiscCommand(val plugin: Jext) : ColosseumCommand(plugin) { 12 | override val commandData = commandDescriptor("disc") { 13 | arguments.add(ArgumentDisc(plugin)) 14 | } 15 | 16 | override fun executePlayer(ctx: CommandContext) { 17 | val disc = ctx.getArgument(0) 18 | 19 | ctx.sender.inventory.addItem(disc.discItemStack) 20 | 21 | ctx.sender.sendI18nConfirm(plugin, "disc-command-success", 22 | "disc" to disc.displayName 23 | ) 24 | } 25 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/gui/items/ScrollUpItem.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.gui.items 2 | 3 | import me.spartacus04.jext.Jext 4 | import org.bukkit.Material 5 | import org.bukkit.entity.Player 6 | import xyz.xenondevs.invui.gui.ScrollGui 7 | import xyz.xenondevs.invui.item.ItemProvider 8 | import xyz.xenondevs.invui.item.builder.ItemBuilder 9 | import xyz.xenondevs.invui.item.impl.controlitem.ScrollItem 10 | 11 | internal class ScrollUpItem(private val player: Player, private val horizontal: Boolean, private val plugin: Jext) : ScrollItem(-1) { 12 | override fun getItemProvider(gui: ScrollGui<*>): ItemProvider { 13 | val builder = ItemBuilder(Material.GREEN_STAINED_GLASS_PANE) 14 | 15 | builder.setDisplayName(plugin.i18nManager!![player, if(horizontal) "scroll-left" else "scroll-up"]!!) 16 | 17 | if (!gui.canScroll(-1)) 18 | builder.addLoreLines(plugin.i18nManager!![player, "cant-scroll-further"]!!) 19 | 20 | return builder 21 | } 22 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/gui/items/ScrollDownItem.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.gui.items 2 | 3 | import me.spartacus04.jext.Jext 4 | import org.bukkit.Material 5 | import org.bukkit.entity.Player 6 | import xyz.xenondevs.invui.gui.ScrollGui 7 | import xyz.xenondevs.invui.item.ItemProvider 8 | import xyz.xenondevs.invui.item.builder.ItemBuilder 9 | import xyz.xenondevs.invui.item.impl.controlitem.ScrollItem 10 | 11 | internal class ScrollDownItem(private val player: Player, private val horizontal: Boolean, private val plugin: Jext) : ScrollItem(1) { 12 | override fun getItemProvider(gui: ScrollGui<*>): ItemProvider { 13 | val builder = ItemBuilder(Material.RED_STAINED_GLASS_PANE) 14 | 15 | builder.setDisplayName(plugin.i18nManager!![player, if(horizontal) "scroll-right" else "scroll-down"]!!) 16 | 17 | if (!gui.canScroll(1)) 18 | builder.addLoreLines(plugin.i18nManager!![player, "cant-scroll-further"]!!) 19 | 20 | return builder 21 | } 22 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/config/fields/FieldLanguageMode.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.config.fields 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | @Suppress("EnumEntryName") 6 | enum class FieldLanguageMode { 7 | @SerializedName("auto") 8 | AUTO, 9 | 10 | @SerializedName("silent") 11 | SILENT, 12 | 13 | @SerializedName("custom") 14 | CUSTOM, 15 | 16 | // locales 17 | 18 | af_za, ar_sa, ca_es, cs_cz, da_dk, de_de, 19 | el_gr, en_us, es_es, fi_fi, fr_fr, he_il, 20 | hu_hu, it_it, ja_jp, ko_kr, nl_nl, no_no, 21 | pl_pl, pt_br, pt_pt, ro_ro, ru_ru, sr_sp, 22 | sv_se, tr_tr, uk_ua, vi_vn, zh_cn, zh_tw; 23 | 24 | internal companion object { 25 | fun fromString(name: String): FieldLanguageMode { 26 | // return enum value, name can either be the actual name or the serialized name 27 | return entries.find { it.name == name || it.name == name.replace("-", "_").uppercase() } ?: throw IllegalArgumentException("Invalid serialized name") 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/discs/discplaying/DefaultDiscPlayingMethod.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.discs.discplaying 2 | 3 | import me.spartacus04.jext.Jext.Companion.INSTANCE 4 | import org.bukkit.Location 5 | import org.bukkit.SoundCategory 6 | import org.bukkit.entity.Player 7 | 8 | /** 9 | * The class `DefaultDiscPlayingMethod` is a implementation of the `DiscPlayingMethod` interface that plays discs from the resource pack. 10 | */ 11 | class DefaultDiscPlayingMethod : DiscPlayingMethod { 12 | override fun playLocation(location: Location, namespace: String, volume : Float, pitch : Float) { 13 | location.world!!.players.forEach { 14 | if(location.distance(it.location) <= INSTANCE.config.JUKEBOX_RANGE) { 15 | it.playSound(location, namespace, SoundCategory.RECORDS, volume, pitch) 16 | } 17 | } 18 | } 19 | 20 | override fun playPlayer(player: Player, namespace: String, volume : Float, pitch : Float) { 21 | player.playSound(player.location, namespace, SoundCategory.RECORDS, volume, pitch) 22 | } 23 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/webapi/discs/DiscsApplyHandler.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.webapi.discs 2 | 3 | import com.sun.net.httpserver.HttpExchange 4 | import me.spartacus04.jext.Jext 5 | import me.spartacus04.jext.gui.JukeboxGui 6 | import me.spartacus04.jext.webapi.utils.JextHttpHandler 7 | 8 | internal class DiscsApplyHandler(plugin: Jext) : JextHttpHandler(plugin, true) { 9 | override fun onPost(exchange: HttpExchange) { 10 | val body = exchange.requestBody.readBytes() 11 | val file = plugin.dataFolder.resolve("resource-pack.zip") 12 | 13 | file.writeBytes(body) 14 | 15 | plugin.discs.reloadDiscs { 16 | plugin.integrations.reloadDefaultIntegrations() 17 | JukeboxGui.loadFromFile() 18 | 19 | plugin.server.onlinePlayers.forEach { 20 | val baseUrl = plugin.baseUrl.getUrl(it) 21 | it.setResourcePack("$baseUrl/resource-pack.zip", plugin.assetsManager.resourcePackHostedHash) 22 | } 23 | } 24 | 25 | exchange.sendResponseHeaders(204, 0) 26 | } 27 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/config/legacy/V2Config.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.config.legacy 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import me.spartacus04.colosseum.ColosseumPlugin 5 | 6 | @Suppress("PropertyName") 7 | internal data class V2Config ( 8 | @SerializedName("lang") 9 | var LANGUAGE_FILE: String = "auto", 10 | 11 | @SerializedName("force-resource-pack") 12 | var FORCE_RESOURCE_PACK : Boolean = false, 13 | 14 | @SerializedName("ignore-failed-download") 15 | var IGNORE_FAILED_DOWNLOAD : Boolean = true, 16 | 17 | @SerializedName("allow-music-overlapping") 18 | var ALLOW_MUSIC_OVERLAPPING : Boolean = false, 19 | ) : ConfigMigrator { 20 | override fun migrateToNext(plugin: ColosseumPlugin): String { 21 | return plugin.gson.toJson(V3Config( 22 | LANGUAGE_MODE = LANGUAGE_FILE, 23 | FORCE_RESOURCE_PACK = FORCE_RESOURCE_PACK, 24 | IGNORE_FAILED_DOWNLOAD = IGNORE_FAILED_DOWNLOAD, 25 | ALLOW_MUSIC_OVERLAPPING = ALLOW_MUSIC_OVERLAPPING 26 | )) 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/discs/discplaying/DiscPlayingMethod.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.discs.discplaying 2 | 3 | import org.bukkit.Location 4 | import org.bukkit.entity.Player 5 | 6 | /** 7 | * The interface `DiscPlayingMethod` is used to define the methods used to play a disc. 8 | */ 9 | interface DiscPlayingMethod { 10 | /** 11 | * Plays the disc at the specified location. 12 | * 13 | * @param location The location to play the disc at. 14 | * @param namespace The namespace of the disc to play. 15 | * @param volume The volume of the disc. 16 | * @param pitch The pitch of the disc. 17 | */ 18 | fun playLocation(location: Location, namespace: String, volume: Float, pitch: Float) 19 | 20 | /** 21 | * Plays the disc for the specified player. 22 | * 23 | * @param player The player to play the disc for. 24 | * @param namespace The namespace of the disc to play. 25 | * @param volume The volume of the disc. 26 | * @param pitch The pitch of the disc. 27 | */ 28 | fun playPlayer(player: Player, namespace: String, volume: Float, pitch: Float) 29 | } -------------------------------------------------------------------------------- /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | name: Build plugin 2 | 3 | on: 4 | push: 5 | branches: [ master, dev/next ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | 15 | - name: Set up JDK 17 16 | uses: actions/setup-java@v4 17 | with: 18 | java-version: 17 19 | distribution: adopt 20 | 21 | - name: Cache Gradle dependencies 22 | uses: actions/cache@v4 23 | with: 24 | path: ~/.gradle/caches 25 | key: ${{ runner.OS }}-gradle-${{ hashFiles('**/*.gradle') }} 26 | restore-keys: | 27 | ${{ runner.OS }}-gradle- 28 | 29 | - name: Grant execute permission for gradlew 30 | run: chmod +x gradlew 31 | 32 | - name: Build with Gradle 33 | run: ./gradlew proguardJar 34 | env: 35 | jextVersion: ${{ github.sha }} 36 | 37 | - uses: actions/upload-artifact@v4 38 | with: 39 | name: "JEXT-Reborn_${{ github.sha }}.jar" 40 | path: ./build/libs/JEXT-Reborn_${{ github.sha }}.jar 41 | -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/integrations/GriefPreventionPermissionIntegration.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.integrations 2 | 3 | import me.ryanhamshire.GriefPrevention.ClaimPermission 4 | import me.ryanhamshire.GriefPrevention.GriefPrevention 5 | import org.bukkit.block.Block 6 | import org.bukkit.entity.Player 7 | 8 | internal class GriefPreventionPermissionIntegration : PermissionIntegration { 9 | override val id = "griefprevention" 10 | 11 | init { 12 | ClaimPermission.Access 13 | } 14 | 15 | override fun hasJukeboxAccess(player: Player, block: Block): Boolean = canInteract(player, block, ClaimPermission.Inventory) 16 | 17 | override fun hasJukeboxGuiAccess(player: Player, block: Block): Boolean = canInteract(player, block, ClaimPermission.Access) 18 | 19 | private fun canInteract(player: Player, block: Block, permission: ClaimPermission) : Boolean { 20 | return try { 21 | val claim = GriefPrevention.instance.dataStore.getClaimAt(block.location, false, null) 22 | 23 | claim.checkPermission(player, permission, null) == null 24 | } catch (_: NullPointerException) { 25 | true 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/config/legacy/V1Config.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.config.legacy 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import me.spartacus04.colosseum.ColosseumPlugin 5 | 6 | @Suppress("PropertyName") 7 | internal data class V1Config ( 8 | @SerializedName("force-resource-pack") 9 | var FORCE_RESOURCE_PACK : Boolean = false, 10 | 11 | @SerializedName("resource-pack-decline-kick-message") 12 | var RESOURCE_PACK_DECLINE_KICK_MESSAGE : String = "", 13 | 14 | @SerializedName("ignore-failed-download") 15 | var IGNORE_FAILED_DOWNLOAD : Boolean = true, 16 | 17 | @SerializedName("failed-download-kick-message") 18 | var FAILED_DOWNLOAD_KICK_MESSAGE : String = "", 19 | 20 | @SerializedName("allow-music-overlapping") 21 | var ALLOW_MUSIC_OVERLAPPING : Boolean = false, 22 | ) : ConfigMigrator { 23 | override fun migrateToNext(plugin: ColosseumPlugin): String { 24 | return plugin.gson.toJson(V2Config( 25 | FORCE_RESOURCE_PACK = FORCE_RESOURCE_PACK, 26 | IGNORE_FAILED_DOWNLOAD = IGNORE_FAILED_DOWNLOAD, 27 | ALLOW_MUSIC_OVERLAPPING = ALLOW_MUSIC_OVERLAPPING 28 | )) 29 | } 30 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Andrea Bonari 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, sublicense, and/or sell copies of the Software, 9 | and to permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | 1. The Software must be modified in some way before distribution or publication. 13 | 2. The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/config/legacy/V3Config.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.config.legacy 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import me.spartacus04.colosseum.ColosseumPlugin 5 | 6 | @Suppress("PropertyName") 7 | internal data class V3Config ( 8 | @SerializedName("lang") 9 | var LANGUAGE_MODE: String = "auto", 10 | 11 | @SerializedName("force-resource-pack") 12 | var FORCE_RESOURCE_PACK : Boolean = false, 13 | 14 | @SerializedName("ignore-failed-download") 15 | var IGNORE_FAILED_DOWNLOAD : Boolean = true, 16 | 17 | @SerializedName("allow-music-overlapping") 18 | var ALLOW_MUSIC_OVERLAPPING : Boolean = false, 19 | 20 | @SerializedName("allow-metrics") 21 | var ALLOW_METRICS : Boolean = true, 22 | ) : ConfigMigrator { 23 | override fun migrateToNext(plugin: ColosseumPlugin): String { 24 | return plugin.gson.toJson(V4Config( 25 | LANGUAGE_MODE = LANGUAGE_MODE, 26 | FORCE_RESOURCE_PACK = FORCE_RESOURCE_PACK, 27 | IGNORE_FAILED_DOWNLOAD = IGNORE_FAILED_DOWNLOAD, 28 | ALLOW_MUSIC_OVERLAPPING = ALLOW_MUSIC_OVERLAPPING, 29 | ALLOW_METRICS = ALLOW_METRICS, 30 | )) 31 | } 32 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/utils/JextMetrics.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.utils 2 | 3 | import me.spartacus04.jext.Jext.Companion.INSTANCE 4 | import me.spartacus04.jext.config.fields.FieldJukeboxBehaviour 5 | import me.spartacus04.jext.utils.Constants.BSTATS_METRICS 6 | import org.bstats.bukkit.Metrics 7 | import org.bstats.charts.SimplePie 8 | 9 | internal class JextMetrics() : Metrics(INSTANCE, BSTATS_METRICS) { 10 | init { 11 | super.addCustomChart(SimplePie("juke_gui") { 12 | when(INSTANCE.config.JUKEBOX_BEHAVIOUR) { 13 | FieldJukeboxBehaviour.VANILLA -> return@SimplePie "Vanilla" 14 | else -> return@SimplePie "GUI" 15 | } 16 | }) 17 | } 18 | 19 | companion object { 20 | private var METRICS = if(INSTANCE.config.ALLOW_METRICS) { 21 | JextMetrics() 22 | } else { 23 | null 24 | } 25 | 26 | fun reloadMetrics() { 27 | METRICS?.shutdown() 28 | METRICS = null 29 | 30 | METRICS = if(INSTANCE.config.ALLOW_METRICS) { 31 | JextMetrics() 32 | } else { 33 | null 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/commands/customArgs/ArgumentDisc.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.commands.customArgs 2 | 3 | import me.spartacus04.colosseum.commandHandling.argument.Argument 4 | import me.spartacus04.colosseum.commandHandling.exceptions.MalformedArgumentException 5 | import me.spartacus04.jext.Jext 6 | import me.spartacus04.jext.discs.Disc 7 | import org.bukkit.ChatColor 8 | import org.bukkit.command.CommandSender 9 | 10 | class ArgumentDisc(val plugin: Jext) : Argument() { 11 | override fun parse(input: String, sender: CommandSender): Disc { 12 | return plugin.discs.firstOrNull { it.namespace.equals(input, ignoreCase = true) } 13 | ?: throw MalformedArgumentException(input, "disc name") 14 | } 15 | 16 | override fun suggest( 17 | input: String, 18 | sender: CommandSender 19 | ): List { 20 | return plugin.discs.map { it.namespace }.filter { it.startsWith(input) } 21 | } 22 | 23 | override fun getParamFormat(isOptional: Boolean): String = 24 | if (isOptional) 25 | "${ChatColor.RESET}[${ChatColor.DARK_PURPLE}disc${ChatColor.RESET}]" 26 | else 27 | "${ChatColor.RESET}<${ChatColor.LIGHT_PURPLE}disc${ChatColor.RESET}>" 28 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/listeners/ListenerRegistrant.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.listeners 2 | 3 | import me.spartacus04.colosseum.ColosseumPlugin 4 | 5 | internal object ListenerRegistrant { 6 | private val listeners = listOf( 7 | RecordPacketEvent::class.java, 8 | ChestOpenEvent::class.java, 9 | CreeperDeathEvent::class.java, 10 | DiscUpdateEvent::class.java, 11 | JukeboxClickEvent::class.java, 12 | PlayerJoinEvent::class.java, 13 | ResourceStatusEvent::class.java, 14 | PrepareCraftingEvent::class.java, 15 | InventoryMoveItemEvent::class.java, 16 | BlockBrushEvent::class.java, 17 | DecoratedPotEvent::class.java, 18 | CrafterCraftDiscEvent::class.java, 19 | VaultDispenseEvent::class.java, 20 | TrialSpawnerDispenseEvent::class.java 21 | ) 22 | 23 | fun registerListeners(plugin: ColosseumPlugin) { 24 | plugin.colosseumLogger.debug("Registering listeners...") 25 | 26 | listeners.forEach { listener -> 27 | listener.constructors.first { it.parameters.size == 1 }.newInstance(plugin) 28 | } 29 | 30 | plugin.colosseumLogger.debug("Registered ${listeners.size} listeners.") 31 | } 32 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/commands/ExportCommand.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.commands 2 | 3 | import kotlinx.coroutines.CoroutineScope 4 | import kotlinx.coroutines.Dispatchers 5 | import kotlinx.coroutines.launch 6 | import me.spartacus04.colosseum.commandHandling.command.ColosseumCommand 7 | import me.spartacus04.colosseum.i18n.sendI18nConfirm 8 | import me.spartacus04.colosseum.i18n.sendI18nError 9 | import me.spartacus04.jext.Jext 10 | import org.bukkit.command.CommandSender 11 | 12 | class ExportCommand(val plugin: Jext) : ColosseumCommand(plugin) { 13 | override val commandData = commandDescriptor("jextexport") { 14 | subCommandName = "export" 15 | } 16 | 17 | override fun execute(ctx: CommandContext) { 18 | ctx.sender.sendI18nConfirm(plugin, "export-start", 19 | "file" to "exported.zip" 20 | ) 21 | 22 | plugin.scheduler.runTaskAsynchronously { 23 | CoroutineScope(Dispatchers.Default).launch { 24 | if(plugin.assetsManager.tryExportResourcePack()) { 25 | ctx.sender.sendI18nConfirm(plugin, "export-success") 26 | } else { 27 | ctx.sender.sendI18nError(plugin, "export-fail") 28 | } 29 | } 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/listeners/CreeperDeathEvent.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.listeners 2 | 3 | import me.spartacus04.colosseum.ColosseumPlugin 4 | import me.spartacus04.colosseum.listeners.ColosseumListener 5 | import me.spartacus04.jext.Jext 6 | import me.spartacus04.jext.utils.Constants.CREEPER_DROPPABLE_DISCS 7 | import org.bukkit.entity.Creeper 8 | import org.bukkit.event.EventHandler 9 | import org.bukkit.event.entity.EntityDeathEvent 10 | import org.bukkit.inventory.ItemStack 11 | 12 | internal class CreeperDeathEvent(val plugin: Jext) : ColosseumListener(plugin) { 13 | @EventHandler(ignoreCancelled = true) 14 | fun onCreeperDeath(event: EntityDeathEvent) { 15 | if(event.entity !is Creeper) return 16 | 17 | val disc = event.drops.find { 18 | it.type.isRecord 19 | } 20 | 21 | 22 | if (disc != null) { 23 | event.drops.remove(disc) 24 | 25 | val discs = ArrayList() 26 | 27 | discs.addAll(CREEPER_DROPPABLE_DISCS) 28 | plugin.discs.filter { it.creeperDrop }.map { it.discItemStack }.forEach { 29 | discs.add(it) 30 | } 31 | 32 | val discItem = discs.random() 33 | 34 | event.drops.addAll(listOf(discItem)) 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "gradle" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | target-branch: "master" 11 | schedule: 12 | interval: 'daily' 13 | ignore: 14 | - dependency-name: "com.sk89q.worldguard:worldguard-bukkit" 15 | 16 | - package-ecosystem: "npm" # See documentation for possible values 17 | directory: "/" # Location of package manifests 18 | target-branch: "gh-pages" 19 | schedule: 20 | interval: 'daily' 21 | ignore: 22 | - dependency-name: "svelte-gestures" 23 | - dependency-name: "@sveltejs/kit" 24 | - dependency-name: "svelte" 25 | - dependency-name: "@sveltejs/vite-plugin-svelte" 26 | - dependency-name: "svelte-check" 27 | - dependency-name: "tailwindcss" 28 | 29 | - package-ecosystem: "cargo" 30 | directory: "/src-tauri/" 31 | target-branch: "gh-pages" 32 | schedule: 33 | interval: 'daily' 34 | -------------------------------------------------------------------------------- /proguard-rules.pro: -------------------------------------------------------------------------------- 1 | -keep class !com.adierebel.**, !com.adierebel.** { *; } 2 | -keepclasseswithmembers public class com.javafx.main.Main, org.eclipse.jdt.internal.jarinjarloader.*, com.adierebel.MainKt { 3 | public static void main(java.lang.String[]); 4 | } 5 | 6 | -dontwarn net.minecraft.** 7 | -dontwarn okio.** 8 | -dontwarn okhttp3.** 9 | -dontwarn retrofit2.** 10 | -dontwarn org.apache.** 11 | -dontwarn com.sun.** 12 | -dontwarn com.j256.ormlite.** 13 | -dontwarn com.google.protobuf.** 14 | -dontwarn com.google.common.** 15 | -dontwarn com.mysql.** 16 | -dontwarn java.util.** 17 | -dontwarn javax.imageio.** 18 | -dontwarn javax.swing.plaf.** 19 | -dontwarn javax.xml.** 20 | -dontwarn java.sql.** 21 | 22 | -ignorewarnings 23 | 24 | # GSON 25 | -keepattributes Signature 26 | -keepattributes Signature 27 | -keep class * extends com.google.gson.TypeAdapter 28 | -keep class * implements com.google.gson.TypeAdapterFactory 29 | -keep class * implements com.google.gson.JsonSerializer 30 | -keep class * implements com.google.gson.JsonDeserializer 31 | -keepclassmembers class * { 32 | @com.google.gson.annotations.SerializedName ; 33 | } 34 | -keepattributes *Annotation*,EventHandler 35 | -keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken 36 | -keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/config/legacy/V4Config.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.config.legacy 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import me.spartacus04.colosseum.ColosseumPlugin 5 | 6 | @Suppress("PropertyName") 7 | internal data class V4Config ( 8 | @SerializedName("lang") 9 | var LANGUAGE_MODE: String = "auto", 10 | 11 | @SerializedName("force-resource-pack") 12 | var FORCE_RESOURCE_PACK : Boolean = false, 13 | 14 | @SerializedName("ignore-failed-download") 15 | var IGNORE_FAILED_DOWNLOAD : Boolean = true, 16 | 17 | @SerializedName("allow-music-overlapping") 18 | var ALLOW_MUSIC_OVERLAPPING : Boolean = false, 19 | 20 | @SerializedName("allow-metrics") 21 | var ALLOW_METRICS : Boolean = true, 22 | 23 | @SerializedName("jukebox-gui") 24 | var JUKEBOX_GUI : Boolean = false 25 | ) : ConfigMigrator { 26 | override fun migrateToNext(plugin: ColosseumPlugin): String { 27 | return plugin.gson.toJson(V5Config( 28 | LANGUAGE_MODE = LANGUAGE_MODE, 29 | FORCE_RESOURCE_PACK = FORCE_RESOURCE_PACK, 30 | IGNORE_FAILED_DOWNLOAD = IGNORE_FAILED_DOWNLOAD, 31 | ALLOW_MUSIC_OVERLAPPING = ALLOW_MUSIC_OVERLAPPING, 32 | ALLOW_METRICS = ALLOW_METRICS, 33 | JUKEBOX_GUI = JUKEBOX_GUI 34 | )) 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | ####################### 2 | # Main Data # 3 | ####################### 4 | 5 | project_name=jext 6 | minecraft_versions=1.14,1.14.1,1.14.2,1.14.3,1.14.4,\ 7 | 1.15,1.15.1,1.15.2,\ 8 | 1.16,1.16.1,1.16.2,1.16.3,1.16.4,1.16.5,\ 9 | 1.17,1.17.1,\ 10 | 1.18,1.18.1,1.18.2,\ 11 | 1.19,1.19.1,1.19.2,1.19.3,1.19.4,\ 12 | 1.20,1.20.1,1.20.2,1.20.3,1.20.4,1.20.5,1.20.6,\ 13 | 1.21,1.21.1,1.21.2,1.21.3,1.21.4,1.21.5,1.21.6,1.21.7,1.21.8,1.21.9,1.21.10 14 | 15 | ####################### 16 | # Hangar Deployment # 17 | ####################### 18 | 19 | hangar_slug=JukeboxExtendedReborn 20 | hangar_channel=Release 21 | 22 | ####################### 23 | # Modrinth Deployment # 24 | ####################### 25 | 26 | modrinth_projectId=LRzd464N 27 | modrinth_channel=release 28 | modrinth_loaders=paper,purpur,spigot,folia 29 | modrinth_url=https://modrinth.com/plugin/jukebox-extended-reborn 30 | 31 | ####################### 32 | # Spigot Deployment # 33 | ####################### 34 | 35 | spigot_updatePage=https://www.spigotmc.org/resources/jukebox-extended-reborn-add-custom-music-discs.103219/add-version 36 | 37 | ####################### 38 | # Config Values # 39 | ####################### 40 | 41 | org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled 42 | org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true 43 | -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/listeners/ResourceStatusEvent.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.listeners 2 | 3 | import me.spartacus04.colosseum.listeners.ColosseumListener 4 | import me.spartacus04.jext.Jext 5 | import org.bukkit.event.EventHandler 6 | import org.bukkit.event.player.PlayerResourcePackStatusEvent 7 | 8 | internal class ResourceStatusEvent(val plugin: Jext) : ColosseumListener(plugin) { 9 | @EventHandler 10 | fun onResourceStatus(e: PlayerResourcePackStatusEvent) { 11 | val status = e.status 12 | 13 | plugin.scheduler.runTaskLater({ 14 | if(!plugin.config.FORCE_RESOURCE_PACK) return@runTaskLater 15 | 16 | when(status) { 17 | PlayerResourcePackStatusEvent.Status.DECLINED -> { 18 | return@runTaskLater e.player.kickPlayer(plugin.i18nManager!![ 19 | e.player, 20 | "resource-pack-decline-kick-message" 21 | ]) 22 | } 23 | PlayerResourcePackStatusEvent.Status.FAILED_DOWNLOAD -> { 24 | return@runTaskLater e.player.kickPlayer(plugin.i18nManager!![ 25 | e.player, 26 | "failed-download-kick-message" 27 | ]) 28 | } 29 | 30 | else -> Unit 31 | } 32 | }, 100) 33 | } 34 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/webapi/auth/ConnectHandler.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.webapi.auth 2 | 3 | import com.sun.net.httpserver.HttpExchange 4 | import me.spartacus04.jext.Jext 5 | import me.spartacus04.jext.webapi.utils.JextHttpHandler 6 | 7 | internal class ConnectHandler(plugin: Jext) : JextHttpHandler(plugin, false) { 8 | override fun onPost(exchange: HttpExchange) { 9 | val addr = exchange.remoteAddress.address.address.map { it.toInt() }.joinToString(".") 10 | val body = exchange.requestBody.bufferedReader().readText() 11 | 12 | if(body == plugin.config.WEB_INTERFACE_PASSWORD) { 13 | val token = (0..64).map { (('a'..'z') + ('A'..'Z') + ('0'..'9')).random() }.joinToString("") 14 | 15 | connectedHashMap[addr] = token 16 | 17 | exchange.sendResponseHeaders(200, token.length.toLong()) 18 | 19 | exchange.responseBody.use { os -> 20 | os.write(token.toByteArray()) 21 | } 22 | } else { 23 | val response = "401 Unauthorized" 24 | exchange.sendResponseHeaders(401, response.length.toLong()) 25 | 26 | exchange.responseBody.use { os -> 27 | os.write(response.toByteArray()) 28 | } 29 | } 30 | } 31 | 32 | companion object { 33 | internal val connectedHashMap = HashMap() 34 | } 35 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/webapi/discs/DiscsReadHandler.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.webapi.discs 2 | 3 | import com.sun.net.httpserver.HttpExchange 4 | import me.spartacus04.jext.Jext 5 | import me.spartacus04.jext.webapi.utils.JextHttpHandler 6 | 7 | internal class DiscsReadHandler(plugin: Jext) : JextHttpHandler(plugin, true) { 8 | private val rpHosted = plugin.dataFolder.resolve("resource-pack.zip") 9 | private val rpCache = plugin.dataFolder.resolve("caches").resolve("${plugin.assetsManager.resourcePackHash.ifBlank { "current" }}.zip") 10 | 11 | override fun onGet(exchange: HttpExchange) { 12 | if(plugin.config.RESOURCE_PACK_HOST) { 13 | if(!rpHosted.exists()) { 14 | return notFound(exchange) 15 | } 16 | 17 | exchange.sendResponseHeaders(200, rpHosted.length()) 18 | 19 | rpHosted.inputStream().use { input -> 20 | exchange.responseBody.use { output -> 21 | input.copyTo(output) 22 | } 23 | } 24 | 25 | return 26 | } 27 | 28 | if(!rpCache.exists()) { 29 | return notFound(exchange) 30 | } 31 | 32 | exchange.sendResponseHeaders(200, rpCache.length()) 33 | 34 | rpCache.inputStream().use { input -> 35 | exchange.responseBody.use { output -> 36 | input.copyTo(output) 37 | } 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/discs/discstopping/DiscStoppingMethod.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.discs.discstopping 2 | 3 | import org.bukkit.Location 4 | import org.bukkit.entity.Player 5 | 6 | /** 7 | * The interface `DiscStoppingMethod` is used to define the methods used to stop a disc. 8 | */ 9 | interface DiscStoppingMethod { 10 | /** 11 | * Defines the list of required plugins for the disc stopping method. 12 | */ 13 | val requires: List 14 | 15 | /** 16 | * Stops the disc for the specified player. 17 | * 18 | * @param player The player to stop the disc for. 19 | */ 20 | fun stop(player: Player) 21 | 22 | /** 23 | * Stops the disc for the specified player with the specified namespace. 24 | * 25 | * @param player The player to stop the disc for. 26 | * @param namespace The namespace of the disc to stop. 27 | */ 28 | fun stop(player: Player, namespace: String) 29 | 30 | /** 31 | * Stops the disc for the specified location. 32 | * 33 | * @param location The location to stop the disc for. 34 | */ 35 | fun stop(location: Location) 36 | 37 | /** 38 | * Stops the disc for the specified location with the specified namespace. 39 | * 40 | * @param location The location to stop the disc for. 41 | * @param namespace The namespace of the disc to stop. 42 | */ 43 | fun stop(location: Location, namespace: String) 44 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/listeners/PrepareCraftingEvent.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.listeners 2 | 3 | import me.spartacus04.colosseum.listeners.ColosseumListener 4 | import me.spartacus04.colosseum.utils.version.VersionCompatibilityMin 5 | import me.spartacus04.jext.Jext 6 | import me.spartacus04.jext.discs.Disc 7 | import me.spartacus04.jext.utils.Constants.JEXT_FRAGMENT_OUTPUT 8 | import org.bukkit.event.EventHandler 9 | import org.bukkit.event.inventory.PrepareItemCraftEvent 10 | 11 | @VersionCompatibilityMin("1.19") 12 | internal class PrepareCraftingEvent(val plugin: Jext) : ColosseumListener(plugin) { 13 | @EventHandler 14 | fun prepareCraftingEvent(e: PrepareItemCraftEvent) { 15 | if (e.inventory.result == null || e.inventory.result!!.type != JEXT_FRAGMENT_OUTPUT) return 16 | 17 | val inventory = e.inventory.matrix.clone() 18 | 19 | val isCustomDisc = inventory.any { 20 | return@any Disc.isCustomDisc(it) 21 | } 22 | 23 | // check if every disc has same namespace, if they have the same namespace return the namespace else null 24 | val namespace = inventory.map { 25 | Disc.fromItemstack(it)?.namespace 26 | }.distinct().singleOrNull() 27 | 28 | if (isCustomDisc && namespace != null) { 29 | e.inventory.result = plugin.discs[namespace]!!.discItemStack 30 | } else if (isCustomDisc) { 31 | e.inventory.result = null 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/utils/WrapperPlayServerStopSoundCategory.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.utils 2 | 3 | import com.github.retrooper.packetevents.protocol.packettype.PacketType 4 | import com.github.retrooper.packetevents.protocol.sound.SoundCategory 5 | import com.github.retrooper.packetevents.wrapper.PacketWrapper 6 | import kotlin.experimental.and 7 | import kotlin.experimental.or 8 | 9 | internal class WrapperPlayServerStopSoundCategory(private var category: SoundCategory?) : PacketWrapper(PacketType.Play.Server.STOP_SOUND) { 10 | private var flags: Byte = 0 11 | 12 | private companion object { 13 | const val FLAG_CATEGORY = 0x01.toByte() 14 | } 15 | 16 | override fun read() { 17 | this.flags = readByte() 18 | 19 | 20 | if (this.flags and FLAG_CATEGORY != 0.toByte()) { 21 | category = SoundCategory.fromId(readVarInt()) 22 | } 23 | } 24 | 25 | override fun write() { 26 | this.flags = 0 27 | 28 | if(this.category != null) { 29 | this.flags = this.flags or FLAG_CATEGORY 30 | } 31 | 32 | writeByte(this.flags.toInt()) 33 | 34 | if(this.category != null) { 35 | writeByte(this.category!!.ordinal) 36 | } 37 | } 38 | 39 | override fun copy(wrapper: WrapperPlayServerStopSoundCategory) { 40 | this.flags = wrapper.flags 41 | this.category = wrapper.category 42 | } 43 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/commands/WebUICommand.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.commands 2 | 3 | import me.spartacus04.colosseum.commandHandling.argument.arguments.ArgumentString 4 | import me.spartacus04.colosseum.commandHandling.command.ColosseumCommand 5 | import me.spartacus04.colosseum.i18n.sendI18nError 6 | import me.spartacus04.colosseum.i18n.sendI18nInfo 7 | import me.spartacus04.jext.Jext 8 | import org.bukkit.command.CommandSender 9 | 10 | class WebUICommand(val plugin: Jext) : ColosseumCommand(plugin) { 11 | override val commandData = commandDescriptor("jextwebui") { 12 | subCommandName = "webui" 13 | 14 | optionalArguments.add(ArgumentString(listOf("config", "docs", "discs"), false)) 15 | } 16 | 17 | override fun execute(ctx: CommandContext) { 18 | if(!plugin.config.WEB_INTERFACE_API_ENABLED) { 19 | return ctx.sender.sendI18nError(plugin, "webui-disabled") 20 | } 21 | 22 | val args = if(ctx.size() > 0) ctx.getArgument(0) else "" 23 | 24 | val page = if(args.isEmpty()) "" else when(args) { 25 | "config" -> "config" 26 | "docs" -> "documentation" 27 | else -> "" 28 | } 29 | 30 | val ip = plugin.baseUrl.getUrl(ctx.sender) 31 | 32 | ctx.sender.sendI18nInfo(plugin, "webui", 33 | "url" to "https://spartacus04.github.io/jext-reborn/${page}?c=c&ip=${ip}&port=${plugin.config.WEB_INTERFACE_PORT}" 34 | ) 35 | } 36 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/integrations/WorldGuardPermissionIntegration.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.integrations 2 | 3 | import com.sk89q.worldedit.bukkit.BukkitAdapter 4 | import com.sk89q.worldguard.WorldGuard 5 | import com.sk89q.worldguard.bukkit.WorldGuardPlugin 6 | import com.sk89q.worldguard.protection.flags.Flags 7 | import com.sk89q.worldguard.protection.flags.StateFlag 8 | import org.bukkit.block.Block 9 | import org.bukkit.entity.Player 10 | 11 | 12 | internal class WorldGuardPermissionIntegration : PermissionIntegration { 13 | override val id = "worldguard" 14 | 15 | init { 16 | Flags.CHEST_ACCESS 17 | } 18 | 19 | override fun hasJukeboxAccess(player: Player, block: Block): Boolean = canInteract(player, block, Flags.USE) 20 | 21 | override fun hasJukeboxGuiAccess(player: Player, block: Block): Boolean = canInteract(player, block, Flags.CHEST_ACCESS, Flags.USE) 22 | 23 | private fun canInteract(player: Player, block: Block, vararg flags: StateFlag) : Boolean { 24 | val wgPlayer = WorldGuardPlugin.inst().wrapPlayer(player) 25 | val wgLocation = BukkitAdapter.adapt(block.location) 26 | 27 | if(WorldGuard.getInstance().platform.sessionManager.hasBypass(wgPlayer, wgPlayer.world)) return true 28 | 29 | val containerQuery = WorldGuard.getInstance().platform.regionContainer.createQuery() 30 | return flags.all { 31 | containerQuery.testState(wgLocation, wgPlayer, it) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/listeners/CrafterCraftDiscEvent.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.listeners 2 | 3 | import me.spartacus04.colosseum.listeners.ColosseumListener 4 | import me.spartacus04.colosseum.utils.version.VersionCompatibilityMin 5 | import me.spartacus04.jext.Jext 6 | import me.spartacus04.jext.discs.Disc 7 | import me.spartacus04.jext.utils.Constants.JEXT_FRAGMENT_OUTPUT 8 | import org.bukkit.block.Crafter 9 | import org.bukkit.event.EventHandler 10 | import org.bukkit.event.block.CrafterCraftEvent 11 | 12 | @VersionCompatibilityMin("1.21") 13 | internal class CrafterCraftDiscEvent(val plugin: Jext) : ColosseumListener(plugin) { 14 | @EventHandler 15 | fun onCrafterCraft(e: CrafterCraftEvent) { 16 | if (e.result.type != JEXT_FRAGMENT_OUTPUT) return 17 | if (e.block !is Crafter) return 18 | 19 | val inventory = (e.block as Crafter).inventory.contents.clone() 20 | 21 | val isCustomDisc = inventory.any { 22 | return@any Disc.isCustomDisc(it) 23 | } 24 | 25 | // check if every disc has same namespace, if they have the same namespace return the namespace else null 26 | val namespace = inventory.map { 27 | Disc.fromItemstack(it)?.namespace 28 | }.distinct().singleOrNull() 29 | 30 | if (isCustomDisc && namespace != null) { 31 | e.result = plugin.discs[namespace]!!.discItemStack 32 | } else if (isCustomDisc) { 33 | e.isCancelled = true 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ############################## 2 | ## Java 3 | ############################## 4 | .mtj.tmp/ 5 | *.class 6 | *.jar 7 | *.war 8 | *.ear 9 | *.nar 10 | hs_err_pid* 11 | 12 | ############################## 13 | ## Maven 14 | ############################## 15 | target/ 16 | pom.xml.tag 17 | pom.xml.releaseBackup 18 | pom.xml.versionsBackup 19 | pom.xml.next 20 | pom.xml.bak 21 | release.properties 22 | dependency-reduced-pom.xml 23 | buildNumber.properties 24 | .mvn/timing.properties 25 | .mvn/wrapper/maven-wrapper.jar 26 | 27 | ############################## 28 | ## Gradle 29 | ############################## 30 | bin/ 31 | build/ 32 | .gradle 33 | .gradletasknamecache 34 | gradle-app.setting 35 | !gradle-wrapper.jar 36 | 37 | ############################## 38 | ## IntelliJ 39 | ############################## 40 | out/ 41 | .idea/ 42 | .idea_modules/ 43 | *.iml 44 | *.ipr 45 | *.iws 46 | 47 | ############################## 48 | ## Eclipse 49 | ############################## 50 | .settings/ 51 | bin/ 52 | tmp/ 53 | .metadata 54 | .classpath 55 | .project 56 | *.tmp 57 | *.bak 58 | *.swp 59 | *~.nib 60 | local.properties 61 | .loadpath 62 | .factorypath 63 | 64 | ############################## 65 | ## NetBeans 66 | ############################## 67 | nbproject/private/ 68 | build/ 69 | nbbuild/ 70 | dist/ 71 | nbdist/ 72 | nbactions.xml 73 | nb-configuration.xml 74 | 75 | ############################## 76 | ## Visual Studio Code 77 | ############################## 78 | .vscode/ 79 | .code-workspace 80 | 81 | ############################## 82 | ## OS X 83 | ############################## 84 | .DS_Store -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/commands/FragmentCommand.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.commands 2 | 3 | import me.spartacus04.colosseum.commandHandling.argument.arguments.ArgumentInteger 4 | import me.spartacus04.colosseum.commandHandling.command.ColosseumCommand 5 | import me.spartacus04.colosseum.i18n.sendI18nConfirm 6 | import me.spartacus04.colosseum.i18n.sendI18nError 7 | import me.spartacus04.jext.Jext 8 | import me.spartacus04.jext.commands.customArgs.ArgumentDisc 9 | import me.spartacus04.jext.discs.Disc 10 | import org.bukkit.entity.Player 11 | 12 | class FragmentCommand(val plugin: Jext) : ColosseumCommand(plugin) { 13 | override val commandData = commandDescriptor("fragment") { 14 | arguments.add(ArgumentDisc(plugin)) 15 | optionalArguments.add(ArgumentInteger(listOf(1, 9))) 16 | } 17 | 18 | override fun executePlayer(ctx: CommandContext) { 19 | if(plugin.serverVersion < "1.19") { 20 | ctx.sender.sendI18nError(plugin, "command-not-supported", 21 | "command" to "fragment", 22 | "reason" to "not supported in < 1.19" 23 | ) 24 | return 25 | } 26 | 27 | val disc = ctx.getArgument(0) 28 | val amount = if(ctx.size() > 1) { 29 | ctx.getArgument(1) 30 | } else { 31 | 1 32 | } 33 | 34 | ctx.sender.inventory.addItem(disc.fragmentItemStack) 35 | 36 | ctx.sender.sendI18nConfirm(plugin, "fragment-command-success", 37 | "disc" to disc.displayName, 38 | "amount" to amount.toString() 39 | ) 40 | } 41 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/integrations/PermissionIntegration.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.integrations 2 | 3 | import org.bukkit.block.Block 4 | import org.bukkit.entity.Player 5 | 6 | /** 7 | * The "PermissionIntegration" interface represents an integration with another plugin. 8 | * It is used to check if a player can interact with a jukebox GUI or a jukebox block. 9 | */ 10 | interface PermissionIntegration { 11 | /** 12 | * The "id" property represents the id of the integration. It must be unique across all integrations. 13 | */ 14 | val id: String 15 | /** 16 | * The function checks if a player can interact with a jukebox GUI. 17 | * 18 | * @param player The player parameter represents the player who is trying to interact with the jukebox GUI. 19 | * @param block The "block" parameter represents the jukebox block that the player is trying to interact with. 20 | * 21 | * @return Returns true if the player can interact with the jukebox GUI, otherwise false. 22 | */ 23 | fun hasJukeboxGuiAccess(player: Player, block: Block): Boolean 24 | /** 25 | * The function checks if a player can interact with a jukebox block. 26 | * 27 | * @param player The player parameter represents the player who wants to interact with the jukebox. 28 | * @param block The "block" parameter represents the jukebox block that the player is trying to interact with. 29 | * 30 | * @return Returns true if the player can interact with the jukebox block, otherwise false. 31 | */ 32 | fun hasJukeboxAccess(player: Player, block: Block): Boolean 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/discs/discplaying/NbsDiscPlayingMethod.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.discs.discplaying 2 | 3 | import com.xxmicloxx.NoteBlockAPI.model.Song 4 | import com.xxmicloxx.NoteBlockAPI.songplayer.EntitySongPlayer 5 | import com.xxmicloxx.NoteBlockAPI.songplayer.PositionSongPlayer 6 | import me.spartacus04.jext.Jext.Companion.INSTANCE 7 | import org.bukkit.Location 8 | import org.bukkit.entity.Player 9 | import kotlin.math.roundToInt 10 | 11 | /** 12 | * The class `NbsDiscPlayingMethod` is a implementation of the `DiscPlayingMethod` interface that plays discs from the nbs directory. 13 | */ 14 | class NbsDiscPlayingMethod(private val song: Song) : DiscPlayingMethod { 15 | 16 | override fun playLocation(location: Location, namespace: String, volume: Float, pitch: Float) { 17 | val songPlayer = PositionSongPlayer(song) 18 | songPlayer.targetLocation = location 19 | songPlayer.volume = volume.roundToInt().toByte() 20 | 21 | songPlayer.distance = INSTANCE.config.JUKEBOX_RANGE 22 | 23 | location.world!!.players.forEach { 24 | songPlayer.addPlayer(it) 25 | } 26 | 27 | songPlayer.isPlaying = true 28 | songPlayer.autoDestroy = true 29 | } 30 | 31 | override fun playPlayer(player: Player, namespace: String, volume: Float, pitch: Float) { 32 | val songPlayer = EntitySongPlayer(song) 33 | songPlayer.entity = player 34 | songPlayer.volume = volume.roundToInt().toByte() 35 | 36 | songPlayer.distance = INSTANCE.config.JUKEBOX_RANGE 37 | 38 | songPlayer.addPlayer(player) 39 | 40 | songPlayer.isPlaying = true 41 | } 42 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/language/DefaultMessages.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.language 2 | 3 | object DefaultMessages { 4 | const val ENABLED_MESSAGE = "Enabled Jukebox Extended Reborn, Do Re Mi!" 5 | const val DISABLED_MESSAGE = "Disabled Jukebox Extended Reborn, Mi Re Do!" 6 | 7 | const val UPDATE_LINK = "https://modrinth.com/plugin/jukebox-extended-reborn" 8 | 9 | const val NO_DISCS_FOUND = "No custom discs found! Check out the documentation for more info." 10 | const val DOCUMENTATION_LINK = "https://spartacus04.github.io/jext-reborn/docs/" 11 | 12 | const val CROWDIN_MESSAGE = "It looks like your language isn't in JEXT yet. Why not contribute and add it yourself here?" 13 | const val CROWDIN_LINK = "https://crwd.in/jext-reborn" 14 | 15 | const val VULNERABLE_MESSAGE = "Spigot version is outdated and is vulnerable to a crash exploit. Please update it." 16 | 17 | const val WEBAPI_RESOURCEPACK_NOT_FOUND = "resource-pack.zip not found, please provide it in the plugin directory." 18 | 19 | const val WEBSERVER_STARTED = "Webserver started on port %port%" 20 | const val WEBSERVER_STOPPED = "Webserver stopped" 21 | 22 | const val GEYSER_RELOAD = "Warning: Geyser won't apply the resource pack changes until you restart the server!" 23 | 24 | const val RESOURCEPACK_DOWNLOAD_START = "Downloading resource pack..." 25 | const val RESOURCEPACK_DOWNLOAD_SUCCESS = "Resource pack downloaded!" 26 | const val RESOURCEPACK_DOWNLOAD_FAIL = "Resource pack download failed! No disc will be loaded." 27 | 28 | const val NBS_NOT_FOUND = "No %name%.nbs file found in the nbs folder! This disc won't be loaded." 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/utils/BaseUrl.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.utils 2 | 3 | import me.spartacus04.jext.Jext 4 | import org.bukkit.Bukkit 5 | import org.bukkit.command.CommandSender 6 | import org.bukkit.entity.Player 7 | import java.net.InetAddress 8 | 9 | @Suppress("HttpUrlsUsage") 10 | class BaseUrl(private val plugin: Jext) { 11 | fun getUrl(commandSender: CommandSender) : String { 12 | var hostName = getBaseUrl(commandSender) 13 | 14 | if(!hostName.contains(":")) { 15 | val split = hostName.split("/") 16 | 17 | if(split.size > 3) { 18 | hostName = split[0] + "//" + split[2] + ":${plugin.config.WEB_INTERFACE_PORT}" + "/" + split.subList(3, split.size).joinToString("/") 19 | } else { 20 | hostName += ":${plugin.config.WEB_INTERFACE_PORT}" 21 | } 22 | } 23 | 24 | if(!hostName.startsWith("https://") && !hostName.startsWith("http://")) { 25 | hostName = "http://$hostName" 26 | } 27 | 28 | if(hostName.endsWith("/")) { 29 | hostName = hostName.dropLast(1) 30 | } 31 | 32 | return hostName 33 | } 34 | 35 | private fun getBaseUrl(commandSender: CommandSender) : String { 36 | if(plugin.config.WEB_INTERFACE_BASE_URL.isNotBlank()) { 37 | return plugin.config.WEB_INTERFACE_BASE_URL 38 | } else if(Bukkit.getIp().isNotBlank()) { 39 | return Bukkit.getIp() 40 | } 41 | 42 | if(commandSender is Player) { 43 | return InetAddress.getLocalHost().hostAddress 44 | } 45 | 46 | return "127.0.0.1" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/webapi/config/ConfigReadHandler.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.webapi.config 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.sun.net.httpserver.HttpExchange 5 | import me.spartacus04.jext.Jext 6 | import me.spartacus04.jext.config.ConfigField 7 | import me.spartacus04.jext.webapi.utils.JextHttpHandler 8 | 9 | internal class ConfigReadHandler(plugin: Jext) : JextHttpHandler(plugin, true) { 10 | override fun onGet(exchange: HttpExchange) { 11 | val data = plugin.config::class.java.declaredFields.mapNotNull { 12 | val id = it.getAnnotation(SerializedName::class.java).value 13 | 14 | if(id == "\$schema") return@mapNotNull null 15 | 16 | val data = it.getAnnotation(ConfigField::class.java) 17 | 18 | it.isAccessible = true 19 | val value = plugin.gson.toJson(it.get(plugin.config)) 20 | it.isAccessible = false 21 | 22 | """ 23 | { 24 | "name": "${data.name}", 25 | "id": "$id", 26 | "description": "${data.description}", 27 | "value": $value, 28 | "defaultValue": ${data.defaultValue} 29 | ${if(data.enumValues.isNotBlank()) ",\"enumValues\": ${data.enumValues}" else ""} 30 | } 31 | """.trimIndent() 32 | } 33 | 34 | val response = "[${data.joinToString(",")}]" 35 | 36 | exchange.sendResponseHeaders(200, response.length.toLong()) 37 | exchange.responseBody.use { output -> 38 | output.write(response.toByteArray()) 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/geyser/IPC/GeyserIPC.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.geyser.IPC 2 | 3 | import java.io.File 4 | import java.nio.file.Files 5 | 6 | internal class GeyserIPC { 7 | private val ipcFile = File(System.getProperty("java.io.tmpdir") + "/geyser-jext-ipc") 8 | 9 | private val timeout = 5000L 10 | 11 | init { 12 | if(!ipcFile.exists()) { 13 | Files.createFile(ipcFile.toPath()) 14 | } 15 | 16 | ipcFile.deleteOnExit() 17 | } 18 | 19 | fun listen(callback: (String) -> Unit) { 20 | Thread { 21 | var last = ipcFile.readText() 22 | while(true) { 23 | val text = ipcFile.readText() 24 | 25 | if(text != last) { 26 | callback(text) 27 | last = text 28 | } 29 | } 30 | }.start() 31 | } 32 | 33 | fun send(command: GeyserIPCCommand, data: String? = null) { 34 | ipcFile.writeText("${command.name}${if(data != null) " $data" else ""}") 35 | } 36 | 37 | fun sendAndReceive(command: GeyserIPCCommand, data: String? = null): String? { 38 | send(command, data) 39 | 40 | val current = ipcFile.readText() 41 | 42 | val start = System.currentTimeMillis() 43 | 44 | while(System.currentTimeMillis() - start < timeout) { 45 | val text = ipcFile.readText() 46 | if(text != current) { 47 | return text 48 | } 49 | } 50 | 51 | return null 52 | } 53 | 54 | internal enum class GeyserIPCCommand { 55 | RESOURCE_PACK, 56 | IS_BEDROCK, 57 | OK, 58 | } 59 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/discs/DiscPersistentDataContainer.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.discs 2 | 3 | import me.spartacus04.jext.JextNamespace 4 | import org.bukkit.inventory.meta.ItemMeta 5 | import org.bukkit.persistence.PersistentDataType 6 | 7 | /** 8 | * This class is used to store the disc data in the item meta. 9 | * 10 | * @constructor Creates a new DiscPersistentDataContainer object 11 | * 12 | * @param meta The item meta 13 | */ 14 | internal class DiscPersistentDataContainer(meta: ItemMeta) { 15 | private val id = "JEXT" 16 | private val container = meta.persistentDataContainer 17 | 18 | /** 19 | * Sets and gets the disc namespace from the item meta. 20 | */ 21 | var namespaceID: String? 22 | get() = container.get( 23 | JextNamespace.NAMESPACE_ID(), 24 | PersistentDataType.STRING 25 | ) 26 | set(value) { 27 | container.set( 28 | JextNamespace.NAMESPACE_ID(), 29 | PersistentDataType.STRING, 30 | value!! 31 | ) 32 | } 33 | 34 | /** 35 | * Sets a JEXT identifier in the item meta. 36 | */ 37 | fun setIdentifier() { 38 | container.set( 39 | JextNamespace.IDENTIFIER(), 40 | PersistentDataType.STRING, 41 | id 42 | ) 43 | } 44 | 45 | /** 46 | * Checks if the item meta contains a JEXT identifier. 47 | * 48 | * @return A boolean value that represents whether the item meta contains a JEXT identifier or not 49 | */ 50 | fun checkIdentifier(): Boolean = container.get( 51 | JextNamespace.IDENTIFIER(), 52 | PersistentDataType.STRING 53 | ) == id 54 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/gui/AdminGui.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.gui 2 | 3 | import me.spartacus04.jext.Jext 4 | import me.spartacus04.jext.Jext.Companion.INSTANCE 5 | import org.bukkit.entity.Player 6 | import xyz.xenondevs.invui.inventory.VirtualInventory 7 | import xyz.xenondevs.invui.inventory.event.ItemPostUpdateEvent 8 | import xyz.xenondevs.invui.inventory.event.ItemPreUpdateEvent 9 | 10 | internal class AdminGui private constructor(player: Player, inventory: VirtualInventory, inventoryName: String, val plugin: Jext) : BaseGui(player, inventory, inventoryName, plugin) { 11 | override fun onInit() { 12 | plugin.discs.forEachIndexed { i, it -> 13 | inventory.setItemSilently(i, it.discItemStack) 14 | } 15 | 16 | if(plugin.serverVersion >= "1.19") { 17 | plugin.discs.forEachIndexed {i, it -> 18 | inventory.setItemSilently(i + plugin.discs.size(), it.fragmentItemStack) 19 | } 20 | } 21 | } 22 | 23 | override fun onItemPreUpdate(event: ItemPreUpdateEvent) { } 24 | 25 | override fun onItemPostUpdate(event: ItemPostUpdateEvent) { 26 | inventory.setItemSilently(event.slot, 27 | if(event.slot >= plugin.discs.size()) 28 | plugin.discs[event.slot - plugin.discs.size()].fragmentItemStack 29 | else 30 | plugin.discs[event.slot].discItemStack 31 | ) 32 | } 33 | 34 | companion object { 35 | fun open(player: Player) = AdminGui( 36 | player, 37 | VirtualInventory( 38 | INSTANCE.discs.size() * if(INSTANCE.serverVersion >= "1.19") 2 else 1 39 | ), 40 | INSTANCE.i18nManager!![player, "jukebox"]!!, 41 | INSTANCE 42 | ) 43 | } 44 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/geyser/extension/GeyserExtension.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.geyser.extension 2 | 3 | import me.spartacus04.jext.geyser.IPC.GeyserIPC 4 | import org.geysermc.api.Geyser 5 | import org.geysermc.event.subscribe.Subscribe 6 | import org.geysermc.geyser.api.event.lifecycle.GeyserPostInitializeEvent 7 | import org.geysermc.geyser.api.extension.Extension 8 | import java.util.Base64 9 | import java.util.UUID 10 | import java.util.zip.ZipFile 11 | import kotlin.io.path.writeBytes 12 | 13 | @Suppress("unused") 14 | internal class GeyserExtension : Extension { 15 | private val ipc = GeyserIPC() 16 | 17 | @Subscribe 18 | fun onPostInitialize(event: GeyserPostInitializeEvent) { 19 | println("JEXT Extension loaded!") 20 | 21 | ipc.listen { 22 | if(it.startsWith("IS_BEDROCK")) { 23 | ipc.send( 24 | GeyserIPC.GeyserIPCCommand.OK, 25 | Geyser.api().isBedrockPlayer(UUID.fromString(it.split(" ")[1])).toString() 26 | ) 27 | } else if(it.startsWith("RESOURCE_PACK")) { 28 | val rpData = Base64.getDecoder().decode(it.split(" ")[1]) 29 | 30 | val file = dataFolder().resolve("../../packs/jext_resources.mcpack") 31 | file.writeBytes(rpData) 32 | 33 | val zip = ZipFile(file.toFile()) 34 | val entry = zip.getEntry("mappings.json") 35 | 36 | val mappingsFile = dataFolder().resolve("../../custom_mappings/mappings.json") 37 | mappingsFile.writeBytes(zip.getInputStream(entry).readBytes()) 38 | } else if(it.startsWith("OK")) { 39 | println("Geyser found!") 40 | 41 | ipc.send(GeyserIPC.GeyserIPCCommand.OK) 42 | } 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/commands/PlayAtCommand.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.commands 2 | 3 | import me.spartacus04.colosseum.commandHandling.argument.arguments.ArgumentDecimal 4 | import me.spartacus04.colosseum.commandHandling.argument.arguments.ArgumentLocation 5 | import me.spartacus04.colosseum.commandHandling.command.ColosseumCommand 6 | import me.spartacus04.colosseum.i18n.sendI18nConfirm 7 | import me.spartacus04.jext.Jext 8 | import me.spartacus04.jext.commands.customArgs.ArgumentDisc 9 | import me.spartacus04.jext.discs.Disc 10 | import org.bukkit.Location 11 | import org.bukkit.entity.Player 12 | 13 | class PlayAtCommand(val plugin: Jext) : ColosseumCommand(plugin) { 14 | override val commandData = commandDescriptor("playat") { 15 | arguments.addAll(listOf( 16 | ArgumentDisc(plugin), 17 | ArgumentLocation() 18 | )) 19 | 20 | optionalArguments.addAll(listOf( 21 | ArgumentDecimal(listOf(0.5, 1.0, 2.0)), 22 | ArgumentDecimal(listOf(4.0, 1.0, 0.5)) 23 | )) 24 | } 25 | 26 | override fun executePlayer(ctx: CommandContext) { 27 | val disc = ctx.getArgument(0) 28 | val location = ctx.getArgument(1) 29 | val pitch = if(ctx.size() > 2) { 30 | ctx.getArgument(2).toFloat() 31 | } else { 32 | 1.0f 33 | } 34 | val volume = if(ctx.size() > 3) { 35 | ctx.getArgument(3).toFloat() 36 | } else { 37 | 1.0f 38 | } 39 | 40 | disc.play(location, volume, pitch) 41 | 42 | ctx.sender.sendI18nConfirm(plugin, "playat-command-success", 43 | "disc" to disc.displayName, 44 | "location" to "(${location.blockX}, ${location.blockY}, ${location.blockZ})", 45 | ) 46 | } 47 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/geyser/plugin/GeyserManager.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.geyser.plugin 2 | 3 | import kotlinx.coroutines.CoroutineScope 4 | import kotlinx.coroutines.Dispatchers 5 | import kotlinx.coroutines.launch 6 | import me.spartacus04.jext.Jext.Companion.INSTANCE 7 | import me.spartacus04.jext.language.DefaultMessages.GEYSER_RELOAD 8 | import org.bukkit.entity.Player 9 | 10 | class GeyserManager { 11 | private var geyser: GeyserMode? = null 12 | 13 | init { 14 | INSTANCE.scheduler.runTaskAsynchronously { 15 | CoroutineScope(Dispatchers.Default).launch { 16 | geyser = try { 17 | GeyserSpigot() 18 | } catch (_ : NoClassDefFoundError) { 19 | try { 20 | GeyserStandalone() 21 | } catch (_ : IllegalStateException) { 22 | null 23 | } 24 | } 25 | } 26 | } 27 | } 28 | 29 | fun reloadGeyser() { 30 | INSTANCE.scheduler.runTaskAsynchronously { 31 | CoroutineScope(Dispatchers.Default).launch { 32 | geyser = try { 33 | GeyserSpigot() 34 | } catch (_ : NoClassDefFoundError) { 35 | try { 36 | GeyserStandalone() 37 | } catch (_ : IllegalStateException) { 38 | null 39 | } 40 | } 41 | 42 | INSTANCE.colosseumLogger.info(GEYSER_RELOAD) 43 | } 44 | } 45 | 46 | } 47 | 48 | fun isBedrockPlayer(player: Player) : Boolean { 49 | return geyser?.isBedrockPlayer(player.uniqueId) ?: false 50 | } 51 | 52 | fun applyResourcePack(buffer: ByteArray) { 53 | geyser?.applyResourcePack(buffer) 54 | } 55 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/listeners/PlayerJoinEvent.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.listeners 2 | 3 | import me.spartacus04.colosseum.i18n.sendI18nInfo 4 | import me.spartacus04.colosseum.listeners.ColosseumListener 5 | import me.spartacus04.colosseum.logging.sendInfo 6 | import me.spartacus04.colosseum.logging.sendUrl 7 | import me.spartacus04.jext.Jext 8 | import me.spartacus04.jext.language.DefaultMessages.CROWDIN_LINK 9 | import me.spartacus04.jext.language.DefaultMessages.CROWDIN_MESSAGE 10 | import me.spartacus04.jext.language.DefaultMessages.UPDATE_LINK 11 | import org.bukkit.event.EventHandler 12 | import org.bukkit.event.player.PlayerJoinEvent 13 | 14 | internal class PlayerJoinEvent(val plugin: Jext) : ColosseumListener(plugin) { 15 | @EventHandler 16 | fun onPlayerJoin(playerJoinEvent: PlayerJoinEvent) { 17 | val player = playerJoinEvent.player 18 | 19 | if(plugin.config.WEB_INTERFACE_API_ENABLED && plugin.config.RESOURCE_PACK_HOST) { 20 | if(!plugin.dataFolder.resolve("resource-pack.zip").exists()) return 21 | 22 | val baseUrl = plugin.baseUrl.getUrl(player) 23 | 24 | player.setResourcePack("$baseUrl/resource-pack.zip", plugin.assetsManager.resourcePackHostedHash) 25 | } 26 | 27 | if (player.hasPermission("jext.notifyupdate") && plugin.config.CHECK_FOR_UPDATES) { 28 | plugin.checkForUpdates("spartacus04/jext-reborn") { 29 | if(it != plugin.description.version) { 30 | player.sendI18nInfo(plugin, "update-available") 31 | player.sendUrl(plugin, UPDATE_LINK) 32 | } 33 | } 34 | } 35 | 36 | if(!plugin.i18nManager!!.hasLanguage(playerJoinEvent.player.locale)) { 37 | player.sendInfo(plugin, CROWDIN_MESSAGE) 38 | player.sendUrl(plugin, CROWDIN_LINK) 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/discs/discstopping/NbsDiscStoppingMethod.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.discs.discstopping 2 | 3 | import com.xxmicloxx.NoteBlockAPI.NoteBlockAPI 4 | import com.xxmicloxx.NoteBlockAPI.songplayer.PositionSongPlayer 5 | import org.bukkit.Location 6 | import org.bukkit.entity.Player 7 | 8 | /** 9 | * The class `NbsDiscStoppingMethod` is an implementation of the `DiscStoppingMethod` interface that stops discs from the nbs directory. 10 | */ 11 | class NbsDiscStoppingMethod : DiscStoppingMethod { 12 | override val requires = listOf("NoteBlockAPI") 13 | 14 | override fun stop(player: Player) { 15 | NoteBlockAPI.stopPlaying(player) 16 | } 17 | 18 | override fun stop(player: Player, namespace: String) { 19 | NoteBlockAPI.stopPlaying(player) 20 | } 21 | 22 | override fun stop(location: Location) { 23 | location.world!!.players.forEach { 24 | val songPlayers = NoteBlockAPI.getSongPlayersByPlayer(it) 25 | 26 | songPlayers?.forEach { songPlayer -> 27 | if(songPlayer is PositionSongPlayer && songPlayer.targetLocation == location) { 28 | songPlayer.isPlaying = false 29 | songPlayer.destroy() 30 | } 31 | } 32 | } 33 | } 34 | 35 | override fun stop(location: Location, namespace: String) { 36 | location.world!!.players.forEach { 37 | val songPlayers = NoteBlockAPI.getSongPlayersByPlayer(it) 38 | 39 | songPlayers?.forEach { songPlayer -> 40 | if( 41 | songPlayer is PositionSongPlayer && 42 | songPlayer.targetLocation == location && 43 | songPlayer.song.path.nameWithoutExtension == namespace 44 | ) { 45 | songPlayer.isPlaying = false 46 | songPlayer.destroy() 47 | } 48 | } 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/listeners/RecordPacketEvent.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.listeners 2 | 3 | import com.github.retrooper.packetevents.event.PacketSendEvent 4 | import com.github.retrooper.packetevents.protocol.packettype.PacketType 5 | import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerEffect 6 | import me.spartacus04.colosseum.ColosseumPlugin 7 | import me.spartacus04.colosseum.listeners.ColosseumPacketListener 8 | import me.spartacus04.jext.discs.Disc 9 | import net.md_5.bungee.api.ChatMessageType 10 | import net.md_5.bungee.api.chat.TextComponent 11 | import org.bukkit.Location 12 | import org.bukkit.block.Jukebox 13 | import org.bukkit.entity.Player 14 | 15 | internal class RecordPacketEvent(val plugin: ColosseumPlugin) : ColosseumPacketListener(plugin) { 16 | override fun onPacketSend(event: PacketSendEvent) { 17 | if(event.packetType != PacketType.Play.Server.EFFECT) return 18 | 19 | val packet = WrapperPlayServerEffect(event) 20 | 21 | // https://minecraft.wiki/w/Java_Edition_protocol/Packets#World_Event 22 | 23 | if(packet.type != 1010) return 24 | 25 | val player = event.getPlayer() 26 | 27 | val position = packet.position.toVector3d() 28 | 29 | plugin.scheduler.runTaskLater({ 30 | val block = Location(player.world, position.x, position.y, position.z).block 31 | val blockState = block.state 32 | 33 | if (blockState !is Jukebox) return@runTaskLater 34 | val disc = Disc.fromItemstack(blockState.record) ?: return@runTaskLater 35 | 36 | actionBarDisplay(player, disc) 37 | }, 1) 38 | 39 | } 40 | 41 | private fun actionBarDisplay(player: Player, disc: Disc) { 42 | player.spigot().sendMessage( 43 | ChatMessageType.ACTION_BAR, 44 | TextComponent(plugin.i18nManager!![player, "now-playing", 45 | "name" to disc.displayName 46 | ]) 47 | ) 48 | 49 | 50 | } 51 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/discs/discstopping/DefaultDiscStoppingMethod.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.discs.discstopping 2 | 3 | import com.github.retrooper.packetevents.PacketEvents 4 | import me.spartacus04.jext.Jext.Companion.INSTANCE 5 | import me.spartacus04.jext.utils.WrapperPlayServerStopSoundCategory 6 | import org.bukkit.Location 7 | import org.bukkit.SoundCategory 8 | import org.bukkit.entity.Player 9 | 10 | /** 11 | * The class `DefaultDiscStoppingMethod` is an implementation of the `DiscStoppingMethod` interface that stops discs played from the resource pack. 12 | */ 13 | class DefaultDiscStoppingMethod : DiscStoppingMethod { 14 | override val requires = listOf() 15 | 16 | private fun stopOldVersions(player: Player) { 17 | val category = com.github.retrooper.packetevents.protocol.sound.SoundCategory.RECORD 18 | val packet = WrapperPlayServerStopSoundCategory(category) 19 | 20 | PacketEvents.getAPI().playerManager.sendPacket(player, packet) 21 | } 22 | 23 | override fun stop(player: Player) { 24 | if(INSTANCE.serverVersion < "1.19") { 25 | stopOldVersions(player) 26 | } else { 27 | player.stopSound(SoundCategory.RECORDS) 28 | } 29 | } 30 | 31 | override fun stop(player: Player, namespace: String) { 32 | player.stopSound(namespace, SoundCategory.RECORDS) 33 | } 34 | 35 | override fun stop(location: Location, namespace: String) { 36 | for (player in location.world!!.players) { 37 | player.stopSound(namespace, SoundCategory.RECORDS) 38 | } 39 | } 40 | 41 | override fun stop(location: Location) { 42 | for (player in location.world!!.players) { 43 | if (player.location.distance(location) <= 64) { 44 | if(INSTANCE.serverVersion < "1.19") { 45 | stopOldVersions(player) 46 | } else { 47 | player.stopSound(SoundCategory.RECORDS) 48 | } 49 | } 50 | } 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/2-webui_bug_report.yml: -------------------------------------------------------------------------------- 1 | name: WebUI Bug report 2 | description: Create a report to help me improve the webui 3 | title: '[Bug]: ' 4 | assignees: 5 | - spartacus04 6 | labels: 7 | - bug 8 | - webui 9 | body: 10 | - type: textarea 11 | id: description 12 | attributes: 13 | label: Describe the bug 14 | description: A clear and concise description of what the bug is. 15 | validations: 16 | required: true 17 | - type: textarea 18 | id: reproduce 19 | attributes: 20 | label: Steps to reproduce 21 | description: Steps to reproduce the behaviour 22 | placeholder: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error 23 | validations: 24 | required: true 25 | - type: textarea 26 | id: expected 27 | attributes: 28 | label: Expected behaviour 29 | description: A clear and concise description of what you expected to happen. 30 | validations: 31 | required: true 32 | - type: textarea 33 | id: logs 34 | attributes: 35 | label: Logs 36 | description: If applicable, check the browser developer console and paste the error logs 37 | - type: checkboxes 38 | id: dekstop_app 39 | attributes: 40 | label: Desktop app 41 | options: 42 | - label: This issue is exclusive to the desktop app 43 | required: false 44 | - type: checkboxes 45 | id: Checkboxes 46 | attributes: 47 | label: Before submitting the issue 48 | options: 49 | - label: >- 50 | I have searched the issue tracker and did not find an issue 51 | describing my bug. 52 | required: true 53 | - label: >- 54 | I agree to the fact that if this issue becomes inactive it will be 55 | closed. 56 | required: true 57 | - label: >- 58 | I agree to the fact that if most fields in this issue are not filled correctly 59 | the issue will be marked as invalid and closed. 60 | required: true 61 | 62 | -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/listeners/InventoryMoveItemEvent.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.listeners 2 | 3 | import me.spartacus04.colosseum.listeners.ColosseumListener 4 | import me.spartacus04.colosseum.utils.version.VersionCompatibilityMin 5 | import me.spartacus04.jext.Jext 6 | import me.spartacus04.jext.config.fields.FieldJukeboxBehaviour 7 | import me.spartacus04.jext.discs.Disc 8 | import me.spartacus04.jext.language.DefaultMessages.VULNERABLE_MESSAGE 9 | import org.bukkit.block.Jukebox 10 | import org.bukkit.event.EventHandler 11 | import org.bukkit.event.inventory.InventoryMoveItemEvent 12 | import org.bukkit.event.inventory.InventoryType 13 | 14 | @VersionCompatibilityMin("1.19.4") 15 | internal class InventoryMoveItemEvent(val plugin: Jext) : ColosseumListener(plugin) { 16 | override fun register() { 17 | try { 18 | InventoryType.JUKEBOX 19 | super.register() 20 | } catch (_: NoSuchFieldError) { 21 | plugin.colosseumLogger.warn(VULNERABLE_MESSAGE) 22 | } 23 | } 24 | 25 | @EventHandler 26 | fun inventoryMoveItemEvent(e: InventoryMoveItemEvent) { 27 | if(e.isCancelled) return 28 | 29 | when(plugin.config.JUKEBOX_BEHAVIOUR) { 30 | FieldJukeboxBehaviour.VANILLA -> { 31 | if(e.destination.type == InventoryType.JUKEBOX) { 32 | val jukebox = e.destination.location!!.block.state as Jukebox 33 | if (jukebox.isPlaying) return 34 | 35 | Disc.fromItemstack(e.item)?.play(e.destination.location!!) 36 | } else if(e.source.type == InventoryType.JUKEBOX) { 37 | val disc = Disc.fromItemstack(e.item) ?: return 38 | plugin.discs.stop(e.source.location!!, disc.namespace) 39 | } 40 | } 41 | else -> { 42 | if(e.source.type == InventoryType.PLAYER && e.destination.type == InventoryType.JUKEBOX) { 43 | e.isCancelled = true 44 | } 45 | } 46 | } 47 | } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/config/legacy/V5Config.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.config.legacy 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import me.spartacus04.colosseum.ColosseumPlugin 5 | 6 | @Suppress("PropertyName") 7 | internal data class V5Config ( 8 | @SerializedName("lang") 9 | var LANGUAGE_MODE: String = "auto", 10 | 11 | @SerializedName("force-resource-pack") 12 | var FORCE_RESOURCE_PACK : Boolean = false, 13 | 14 | @SerializedName("ignore-failed-download") 15 | var IGNORE_FAILED_DOWNLOAD : Boolean = true, 16 | 17 | @SerializedName("allow-music-overlapping") 18 | var ALLOW_MUSIC_OVERLAPPING : Boolean = false, 19 | 20 | @SerializedName("allow-metrics") 21 | var ALLOW_METRICS : Boolean = true, 22 | 23 | @SerializedName("jukebox-gui") 24 | var JUKEBOX_GUI : Boolean = false, 25 | 26 | @SerializedName("discs-random-chance") 27 | var DISCS_RANDOM_CHANCE : Int = 200, 28 | 29 | @SerializedName("fragments-random-chance") 30 | var FRAGMENTS_RANDOM_CHANCE : Int = 200, 31 | 32 | @SerializedName("disc-loottables-limit") 33 | var DISC_LIMIT : HashMap = hashMapOf( 34 | "chests/*" to 2 35 | ), 36 | 37 | @SerializedName("fragment-loottables-limit") 38 | var FRAGMENT_LIMIT : HashMap = hashMapOf( 39 | "chests/*" to 3 40 | ) 41 | ) : ConfigMigrator { 42 | override fun migrateToNext(plugin: ColosseumPlugin): String { 43 | return plugin.gson.toJson(V6Config( 44 | LANGUAGE_MODE = LANGUAGE_MODE, 45 | FORCE_RESOURCE_PACK = FORCE_RESOURCE_PACK, 46 | IGNORE_FAILED_DOWNLOAD = IGNORE_FAILED_DOWNLOAD, 47 | ALLOW_MUSIC_OVERLAPPING = ALLOW_MUSIC_OVERLAPPING, 48 | ALLOW_METRICS = ALLOW_METRICS, 49 | JUKEBOX_BEHAVIOUR = if(JUKEBOX_GUI) "legacy-gui" else "vanilla", 50 | DISCS_RANDOM_CHANCE = DISCS_RANDOM_CHANCE, 51 | FRAGMENTS_RANDOM_CHANCE = FRAGMENTS_RANDOM_CHANCE, 52 | DISC_LIMIT = DISC_LIMIT, 53 | FRAGMENT_LIMIT = FRAGMENT_LIMIT 54 | )) 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/webapi/utils/JextHttpHandler.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.webapi.utils 2 | 3 | import com.sun.net.httpserver.HttpExchange 4 | import com.sun.net.httpserver.HttpHandler 5 | import me.spartacus04.jext.Jext 6 | import me.spartacus04.jext.webapi.auth.ConnectHandler 7 | 8 | internal open class JextHttpHandler(val plugin: Jext, val requireAuth: Boolean) : HttpHandler { 9 | override fun handle(exchange: HttpExchange) { 10 | val origin = exchange.requestHeaders["Origin"]?.firstOrNull() ?: "*" 11 | 12 | exchange.responseHeaders.add("Access-Control-Allow-Origin", origin) 13 | exchange.responseHeaders.add("Access-Control-Allow-Methods", "GET, POST, OPTIONS") 14 | exchange.responseHeaders.add("Access-Control-Allow-Headers", "Authorization, *") 15 | 16 | if(exchange.requestMethod == "OPTIONS") { 17 | exchange.sendResponseHeaders(200, 0) 18 | return exchange.close() 19 | } 20 | 21 | if(requireAuth && !isLoggedIn(exchange)) { 22 | val response = "401 Unauthorized" 23 | exchange.sendResponseHeaders(401, response.length.toLong()) 24 | 25 | exchange.responseBody.use { os -> 26 | os.write(response.toByteArray()) 27 | } 28 | 29 | exchange.close() 30 | } 31 | 32 | when(exchange.requestMethod) { 33 | "POST" -> onPost(exchange) 34 | "GET" -> onGet(exchange) 35 | else -> notFound(exchange) 36 | } 37 | 38 | exchange.close() 39 | } 40 | 41 | fun notFound(exchange: HttpExchange) = exchange.sendResponseHeaders(404, 0) 42 | 43 | open fun onPost(exchange: HttpExchange) = notFound(exchange) 44 | 45 | open fun onGet(exchange: HttpExchange) = notFound(exchange) 46 | 47 | private fun isLoggedIn(exchange: HttpExchange): Boolean { 48 | val addr = exchange.remoteAddress.address.address.map { it.toInt() }.joinToString(".") 49 | val token = exchange.requestHeaders["Authorization"]?.first()?.replace("Bearer ", "") ?: return false 50 | 51 | return ConnectHandler.connectedHashMap[addr] == token 52 | } 53 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/commands/StopMusicCommand.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.commands 2 | 3 | import me.spartacus04.colosseum.commandHandling.argument.arguments.ArgumentPlayers 4 | import me.spartacus04.colosseum.commandHandling.command.ColosseumCommand 5 | import me.spartacus04.colosseum.i18n.sendI18nConfirm 6 | import me.spartacus04.colosseum.i18n.sendI18nInfo 7 | import me.spartacus04.colosseum.i18n.sendI18nWarn 8 | import me.spartacus04.jext.Jext 9 | import me.spartacus04.jext.commands.customArgs.ArgumentDisc 10 | import me.spartacus04.jext.discs.Disc 11 | import org.bukkit.command.CommandSender 12 | import org.bukkit.entity.Player 13 | 14 | class StopMusicCommand(val plugin: Jext) : ColosseumCommand(plugin) { 15 | override val commandData = commandDescriptor("stopmusic") { 16 | arguments.add(ArgumentPlayers(false)) 17 | optionalArguments.add(ArgumentDisc(plugin)) 18 | } 19 | 20 | override fun execute(ctx: CommandContext) { 21 | val players = ctx.getArgument>(0) 22 | val discs = if(ctx.size() > 1) { 23 | listOf(ctx.getArgument(1)) 24 | } else { 25 | plugin.discs.toList() 26 | } 27 | 28 | players.forEach { player -> 29 | discs.forEach { 30 | plugin.discs.stop(player, it.namespace) 31 | } 32 | 33 | if(discs.size == 1) { 34 | player.sendI18nInfo(plugin, "stopped-music", 35 | "name" to discs[0].displayName 36 | ) 37 | } else { 38 | player.sendI18nInfo(plugin, "stopped-all-music",) 39 | } 40 | } 41 | 42 | if(players.size > 1) { 43 | ctx.sender.sendI18nConfirm(plugin, "stopped-music-for-multiple", 44 | "playercount" to players.size.toString() 45 | ) 46 | } else if (players.size == 1) { 47 | ctx.sender.sendI18nConfirm(plugin, "stopped-music-for", 48 | "player" to players[0].name 49 | ) 50 | } else { 51 | ctx.sender.sendI18nWarn(plugin, "no-players-stopped-music") 52 | } 53 | 54 | } 55 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/config/ConfigFactory.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.config 2 | 3 | import com.google.common.reflect.TypeToken 4 | import com.google.gson.annotations.SerializedName 5 | import me.spartacus04.colosseum.ColosseumPlugin 6 | import me.spartacus04.jext.config.legacy.* 7 | import me.spartacus04.jext.utils.JextFileBind 8 | 9 | internal object ConfigFactory { 10 | private fun getSerializedNames(clazz: Class<*>) = 11 | clazz.declaredFields.map { 12 | it.getAnnotation(SerializedName::class.java).value 13 | } 14 | 15 | private val legacyConfigs = listOf( 16 | V1Config::class.java, 17 | V2Config::class.java, 18 | V3Config::class.java, 19 | V4Config::class.java, 20 | V5Config::class.java, 21 | V6Config::class.java, 22 | V7Config::class.java, 23 | V8Config::class.java 24 | ) 25 | 26 | private fun updateOldConfig(plugin: ColosseumPlugin) { 27 | var text = plugin.dataFolder.resolve("config.json").readText() 28 | 29 | val currentConfigParams = Config::class.java.declaredFields.map { 30 | it.getAnnotation(SerializedName::class.java).value 31 | } 32 | 33 | if(currentConfigParams.all { text.contains(it) }) return 34 | 35 | legacyConfigs.forEach { 36 | val params = getSerializedNames(it) 37 | 38 | val map = plugin.gson.fromJson>(text, object : TypeToken>() {}.type) 39 | map.remove("\$schema") 40 | 41 | if(params.all { param -> map.containsKey(param) } && map.keys.all { key -> params.contains(key) }) { 42 | val instance = plugin.gson.fromJson(text, it) 43 | text = instance.migrateToNext(plugin) 44 | } 45 | } 46 | 47 | plugin.dataFolder.resolve("config.json").writeText(text) 48 | } 49 | 50 | fun createConfigObject(plugin: ColosseumPlugin) : Config { 51 | if(!plugin.dataFolder.exists()) plugin.dataFolder.mkdirs() 52 | 53 | if(plugin.dataFolder.resolve("config.json").exists()) { 54 | updateOldConfig(plugin) 55 | } 56 | 57 | return JextFileBind.create(Config::class.java) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/gui/JukeboxEntry.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.gui 2 | 3 | import me.spartacus04.jext.Jext.Companion.INSTANCE 4 | import me.spartacus04.jext.utils.Constants.SOUND_MAP 5 | import org.bukkit.Material 6 | import org.bukkit.inventory.ItemStack 7 | 8 | /** 9 | * The class `JukeboxEntry` is a data class that represents a jukebox entry. 10 | * 11 | * @property type The `type` property is a string that represents the type of the jukebox entry. It can be either `jext` or `vanilla`. 12 | * @property value The `value` property is a string that represents the value of the jukebox entry. It can be either a disc namespace or a material name. 13 | * @constructor Creates a new jukebox entry. 14 | */ 15 | internal data class JukeboxEntry( 16 | var type: String, 17 | var value: String, 18 | ) { 19 | /** 20 | * The function `toItemStack` returns an ItemStack object based on the type and value of the jukebox entry. 21 | * 22 | * @return 23 | */ 24 | fun toItemStack() : ItemStack { 25 | return when(type) { 26 | "jext" -> { 27 | val disc = INSTANCE.discs[value] 28 | 29 | disc?.discItemStack 30 | ?: arrayListOf( 31 | SOUND_MAP.keys.map { mat -> ItemStack(mat) }, 32 | INSTANCE.discs.map { discm -> discm.discItemStack } 33 | ).flatten().random() 34 | } 35 | "jext-nbs" -> { 36 | val disc = INSTANCE.discs[value] 37 | 38 | disc?.discItemStack 39 | ?: arrayListOf( 40 | SOUND_MAP.keys.map { mat -> ItemStack(mat) }, 41 | INSTANCE.discs.map { discm -> discm.discItemStack } 42 | ).flatten().random() 43 | } 44 | else -> { 45 | val material = Material.matchMaterial(value) 46 | 47 | if(material != null) { 48 | return ItemStack(material) 49 | } 50 | 51 | arrayListOf( 52 | SOUND_MAP.keys.map { mat -> ItemStack(mat) }, 53 | INSTANCE.discs.map { disc -> disc.discItemStack } 54 | ).flatten().random() 55 | } 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/commands/DiscGiveCommand.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.commands 2 | 3 | import me.spartacus04.colosseum.commandHandling.argument.arguments.ArgumentInteger 4 | import me.spartacus04.colosseum.commandHandling.argument.arguments.ArgumentPlayers 5 | import me.spartacus04.colosseum.commandHandling.command.ColosseumCommand 6 | import me.spartacus04.colosseum.i18n.sendI18nConfirm 7 | import me.spartacus04.colosseum.i18n.sendI18nError 8 | import me.spartacus04.colosseum.i18n.sendI18nInfo 9 | import me.spartacus04.colosseum.i18n.sendI18nWarn 10 | import me.spartacus04.jext.Jext 11 | import me.spartacus04.jext.commands.customArgs.ArgumentDisc 12 | import me.spartacus04.jext.discs.Disc 13 | import org.bukkit.command.CommandSender 14 | import org.bukkit.entity.Player 15 | 16 | class DiscGiveCommand(val plugin: Jext) : ColosseumCommand(plugin) { 17 | override val commandData = commandDescriptor("discgive") { 18 | arguments.addAll( listOf( 19 | ArgumentPlayers(false), 20 | ArgumentDisc(plugin), 21 | )) 22 | 23 | optionalArguments.add( 24 | ArgumentInteger(listOf(1)) 25 | ) 26 | } 27 | 28 | override fun execute(ctx: CommandContext) { 29 | val players = ctx.getArgument>(0) 30 | val disc = ctx.getArgument(1) 31 | 32 | val amount = if(ctx.size() > 2) { 33 | ctx.getArgument(2) 34 | } else { 35 | 1 36 | } 37 | 38 | players.forEach { 39 | for(i in 1..amount) { 40 | it.inventory.addItem(disc.discItemStack) 41 | } 42 | 43 | it.sendI18nInfo(plugin, "disc-received", 44 | "disc" to disc.displayName 45 | ) 46 | } 47 | 48 | if(players.size > 1) { 49 | ctx.sender.sendI18nConfirm(plugin, "disc-given-multiple", 50 | "disc" to disc.displayName, 51 | "playercount" to players.size.toString() 52 | ) 53 | } else if(players.size == 1) { 54 | ctx.sender.sendI18nConfirm(plugin, "disc-given", 55 | "disc" to disc.displayName, 56 | "player" to players[0].name 57 | ) 58 | } else { 59 | ctx.sender.sendI18nWarn(plugin, "no-disc-given") 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/config/legacy/V6Config.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.config.legacy 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import me.spartacus04.colosseum.ColosseumPlugin 5 | import me.spartacus04.jext.config.fields.FieldJukeboxBehaviour 6 | import me.spartacus04.jext.config.fields.FieldLanguageMode 7 | 8 | @Suppress("PropertyName") 9 | internal data class V6Config ( 10 | @SerializedName("lang") 11 | var LANGUAGE_MODE: String = "auto", 12 | 13 | @SerializedName("force-resource-pack") 14 | var FORCE_RESOURCE_PACK : Boolean = false, 15 | 16 | @SerializedName("ignore-failed-download") 17 | var IGNORE_FAILED_DOWNLOAD : Boolean = true, 18 | 19 | @SerializedName("allow-music-overlapping") 20 | var ALLOW_MUSIC_OVERLAPPING : Boolean = false, 21 | 22 | @SerializedName("allow-metrics") 23 | var ALLOW_METRICS : Boolean = true, 24 | 25 | @SerializedName("jukebox-behaviour") 26 | var JUKEBOX_BEHAVIOUR : String = "vanilla", 27 | 28 | @SerializedName("discs-random-chance") 29 | var DISCS_RANDOM_CHANCE : Int = 200, 30 | 31 | @SerializedName("fragments-random-chance") 32 | var FRAGMENTS_RANDOM_CHANCE : Int = 200, 33 | 34 | @SerializedName("disc-loottables-limit") 35 | var DISC_LIMIT : HashMap = hashMapOf(), 36 | 37 | @SerializedName("fragment-loottables-limit") 38 | var FRAGMENT_LIMIT : HashMap = hashMapOf(), 39 | ) : ConfigMigrator { 40 | override fun migrateToNext(plugin: ColosseumPlugin): String { 41 | return plugin.gson.toJson(V7Config( 42 | LANGUAGE_MODE = try { 43 | FieldLanguageMode.fromString(LANGUAGE_MODE) 44 | } catch (_: IllegalArgumentException) { FieldLanguageMode.AUTO }, 45 | JUKEBOX_BEHAVIOUR = try { 46 | FieldJukeboxBehaviour.fromString(JUKEBOX_BEHAVIOUR) 47 | } catch (_: IllegalArgumentException) { 48 | if(JUKEBOX_BEHAVIOUR == "legacy-gui") 49 | FieldJukeboxBehaviour.GUI 50 | else 51 | FieldJukeboxBehaviour.VANILLA 52 | }, 53 | DISABLE_MUSIC_OVERLAP = !ALLOW_MUSIC_OVERLAPPING, 54 | DISC_LIMIT = DISC_LIMIT, 55 | FRAGMENT_LIMIT = FRAGMENT_LIMIT, 56 | FORCE_RESOURCE_PACK = FORCE_RESOURCE_PACK, 57 | ALLOW_METRICS = ALLOW_METRICS 58 | )) 59 | } 60 | } -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish Release 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | # Setup 12 | - uses: actions/checkout@v4 13 | 14 | - name: Set up JDK 17 15 | uses: actions/setup-java@v4 16 | with: 17 | java-version: 17 18 | distribution: adopt 19 | 20 | - name: Cache Gradle dependencies 21 | uses: actions/cache@v4 22 | with: 23 | path: ~/.gradle/caches 24 | key: ${{ runner.OS }}-gradle-${{ hashFiles('**/*.gradle') }} 25 | restore-keys: | 26 | ${{ runner.OS }}-gradle- 27 | 28 | - name: Grant execute permission for gradlew 29 | run: chmod +x gradlew 30 | 31 | # Attach output to release 32 | - name: Get artifact version 33 | id: get_version 34 | run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV 35 | 36 | # Build and publish 37 | - name: Build with Gradle 38 | run: ./gradlew proguardJar 39 | env: 40 | jextVersion: ${{ env.VERSION }} 41 | 42 | - name: Publish to Modrinth 43 | run: ./gradlew modrinth 44 | env: 45 | modrinthApiKey: ${{ secrets.MODRINTH_TOKEN }} 46 | modrinthChangelog: ${{ github.event.release.body }} 47 | jextVersion: ${{ env.VERSION }} 48 | 49 | - name: Sync Modrinth body 50 | run: ./gradlew modrinthSyncBody 51 | env: 52 | modrinthApiKey: ${{ secrets.MODRINTH_TOKEN }} 53 | modrinthChangelog: ${{ github.event.release.body }} 54 | jextVersion: ${{ env.VERSION }} 55 | 56 | - name: Publish to Hangar 57 | run: ./gradlew publishPluginPublicationToHangar 58 | env: 59 | hangarApiKey: ${{ secrets.HANGAR_TOKEN }} 60 | hangarChangelog: ${{ github.event.release.body }} 61 | jextVersion: ${{ env.VERSION }} 62 | 63 | - name: Attach artifact to release 64 | id: upload-release-asset 65 | uses: actions/upload-release-asset@v1.0.2 66 | env: 67 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 68 | with: 69 | upload_url: ${{ github.event.release.upload_url }} 70 | asset_path: ./build/libs/JEXT-Reborn_${{ env.VERSION }}.jar 71 | asset_name: JEXT-Reborn_${{ env.VERSION }}.jar 72 | asset_content_type: application/java-archive 73 | -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/commands/PlayMusicCommand.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.commands 2 | 3 | import me.spartacus04.colosseum.commandHandling.argument.arguments.ArgumentDecimal 4 | import me.spartacus04.colosseum.commandHandling.argument.arguments.ArgumentPlayers 5 | import me.spartacus04.colosseum.commandHandling.command.ColosseumCommand 6 | import me.spartacus04.colosseum.i18n.sendI18nConfirm 7 | import me.spartacus04.colosseum.i18n.sendI18nError 8 | import me.spartacus04.colosseum.i18n.sendI18nInfo 9 | import me.spartacus04.jext.Jext 10 | import me.spartacus04.jext.commands.customArgs.ArgumentDisc 11 | import me.spartacus04.jext.discs.Disc 12 | import org.bukkit.command.CommandSender 13 | import org.bukkit.entity.Player 14 | 15 | class PlayMusicCommand(val plugin: Jext) : ColosseumCommand(plugin) { 16 | override val commandData = commandDescriptor("playmusic") { 17 | subCommandName = "play" 18 | 19 | arguments.addAll(listOf( 20 | ArgumentPlayers(false), 21 | ArgumentDisc(plugin) 22 | )) 23 | 24 | optionalArguments.addAll(listOf( 25 | ArgumentDecimal(listOf(0.5, 1.0, 1.5)), 26 | ArgumentDecimal(listOf(4.0, 1.0, 0.5)) 27 | )) 28 | } 29 | 30 | override fun execute(ctx: CommandContext) { 31 | val players = ctx.getArgument>(0) 32 | val disc = ctx.getArgument(1) 33 | val pitch = if(ctx.size() > 2) { 34 | ctx.getArgument(2).toFloat() 35 | } else { 36 | 1.0f 37 | } 38 | val volume = if(ctx.size() > 3) { 39 | ctx.getArgument(3).toFloat() 40 | } else { 41 | 1.0f 42 | } 43 | 44 | players.forEach { 45 | disc.play(it, volume, pitch) 46 | 47 | it.sendI18nInfo(plugin, "music-now-playing", 48 | "name" to disc.displayName 49 | ) 50 | } 51 | 52 | if(players.size > 1) { 53 | ctx.sender.sendI18nConfirm(plugin, "played-music-to-multiple", 54 | "name" to disc.displayName, 55 | "playercount" to players.size.toString() 56 | ) 57 | } else if(players.size == 1) { 58 | ctx.sender.sendI18nConfirm(plugin, "played-music-to", 59 | "name" to disc.displayName, 60 | "player" to players[0].name 61 | ) 62 | } else { 63 | ctx.sender.sendI18nError(plugin, "no-music-played") 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/utils/JextFileBind.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.utils 2 | 3 | import me.spartacus04.colosseum.ColosseumPlugin 4 | 5 | /** 6 | * The class `FileBind` is used to bind a file to a class. 7 | * 8 | * @param filePath The path of the file. 9 | * @param clazz The class to bind the file to. 10 | */ 11 | open class JextFileBind(@Transient private val filePath: String, @Transient private val clazz: Class<*>, @Transient private val plugin: ColosseumPlugin) { 12 | /** 13 | * Reads the file and binds it to the class. 14 | */ 15 | fun read() { 16 | if(!plugin.dataFolder.exists()) plugin.dataFolder.mkdirs() 17 | 18 | val file = plugin.dataFolder.resolve(filePath) 19 | 20 | if(!file.exists()) { 21 | file.createNewFile() 22 | 23 | save() 24 | } 25 | 26 | val obj = plugin.gson.fromJson(file.readText(), clazz) 27 | 28 | obj.javaClass.declaredFields.forEach { field -> 29 | field.isAccessible = true 30 | 31 | field.set(this, field.get(obj)) 32 | } 33 | } 34 | 35 | /** 36 | * Reads the file from the specified text and binds it to the class. 37 | * 38 | * @param text The text to read from. 39 | * 40 | * @return Returns true if the text was successfully read, otherwise false. 41 | */ 42 | fun fromText(text: String) : Boolean { 43 | try { 44 | val obj = plugin.gson.fromJson(text, clazz) 45 | 46 | obj.javaClass.declaredFields.forEach { field -> 47 | field.isAccessible = true 48 | 49 | field.set(this, field.get(obj)) 50 | } 51 | 52 | return true 53 | } catch (_: Exception) { 54 | return false 55 | } 56 | } 57 | 58 | /** 59 | * Saves the class to the file. 60 | */ 61 | fun save() { 62 | val text = plugin.gson.toJson(this) 63 | 64 | plugin.dataFolder.resolve(filePath).writeText(text) 65 | } 66 | 67 | companion object { 68 | /** 69 | * Creates a new instance of the specified class and binds it to the file. 70 | * 71 | * @param clazz The class to bind the file to. 72 | * 73 | * @return The instance of the class. 74 | */ 75 | fun create(clazz: Class): T { 76 | val instance = clazz.getDeclaredConstructor().newInstance() 77 | 78 | instance.read() 79 | 80 | return instance 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/integrations/PermissionsIntegrationManager.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.integrations 2 | 3 | import org.bukkit.block.Block 4 | import org.bukkit.entity.Player 5 | 6 | /** 7 | * The "PermissionsIntegrationManager" class is used to manage the permissions integrations. 8 | */ 9 | class PermissionsIntegrationManager { 10 | private val permissionIntegrations = ArrayList() 11 | 12 | private fun registerIntegration(permissionIntegration: PermissionIntegration) { 13 | if(permissionIntegrations.any { it.id == permissionIntegration.id }) throw IllegalArgumentException("Integration with id ${permissionIntegration.id} is already registered!") 14 | 15 | permissionIntegrations.add(permissionIntegration) 16 | } 17 | 18 | /** 19 | * Registers the specified permission integrations. 20 | * 21 | * @param permissionIntegrations The permission integrations to register. 22 | */ 23 | @Suppress("unused") 24 | fun registerIntegrations(vararg permissionIntegrations: PermissionIntegration) { 25 | permissionIntegrations.forEach { registerIntegration(it) } 26 | } 27 | 28 | /** 29 | * Checks if the player has access to the jukebox. 30 | * 31 | * @param player The player to check. 32 | * @param block The block to check. 33 | * 34 | * @return Returns true if the player has access to the jukebox, otherwise false. 35 | */ 36 | fun hasJukeboxAccess(player: Player, block: Block): Boolean = permissionIntegrations.all { it.hasJukeboxAccess(player, block) } 37 | 38 | /** 39 | * Checks if the player has access to the jukebox GUI. 40 | * 41 | * @param player The player to check. 42 | * @param block The block to check. 43 | * 44 | * @return Returns true if the player has access to the jukebox GUI, otherwise false. 45 | */ 46 | fun hasJukeboxGuiAccess(player: Player, block: Block): Boolean = permissionIntegrations.all { it.hasJukeboxGuiAccess(player, block) } 47 | 48 | 49 | /** 50 | * Reloads the default integrations. 51 | */ 52 | fun reloadDefaultIntegrations() { 53 | permissionIntegrations.removeIf { it.id == "worldguard" || it.id == "griefprevention" } 54 | 55 | try { 56 | registerIntegration(WorldGuardPermissionIntegration()) 57 | } catch(_: NoClassDefFoundError) { } 58 | 59 | try { 60 | registerIntegration(GriefPreventionPermissionIntegration()) 61 | } catch (_: NoClassDefFoundError) { } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/geyser/plugin/GeyserSpigot.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.geyser.plugin 2 | 3 | import me.spartacus04.jext.Jext.Companion.INSTANCE 4 | import org.geysermc.event.subscribe.Subscribe 5 | import org.geysermc.geyser.api.GeyserApi 6 | import org.geysermc.geyser.api.event.EventRegistrar 7 | import org.geysermc.geyser.api.event.lifecycle.GeyserDefineCustomItemsEvent 8 | import org.geysermc.geyser.api.item.custom.CustomItemData 9 | import org.geysermc.geyser.api.item.custom.CustomItemOptions 10 | import java.util.UUID 11 | 12 | internal class GeyserSpigot : EventRegistrar, GeyserMode { 13 | 14 | init { 15 | GeyserApi.api().eventBus().register(this, INSTANCE) 16 | GeyserApi.api().eventBus().subscribe(this, GeyserDefineCustomItemsEvent::class.java, this::onGeyserInit) 17 | } 18 | 19 | @Suppress("unused") 20 | @Subscribe 21 | private fun onGeyserInit(event : GeyserDefineCustomItemsEvent) { 22 | INSTANCE.discs.map { 23 | val itemOptions = CustomItemOptions.builder() 24 | .customModelData(it.discItemStack.itemMeta!!.customModelData) 25 | .build() 26 | 27 | CustomItemData.builder() 28 | .icon("music_disc_${it.namespace}") 29 | .name("music_disc_${it.namespace}") 30 | .displayName("Music Disc") 31 | .customItemOptions(itemOptions) 32 | .build() 33 | }.forEach { 34 | event.register("minecraft:music_disc_11", it) 35 | } 36 | 37 | if(INSTANCE.serverVersion >= "1.19") { 38 | INSTANCE.discs.map { 39 | val itemOptions = CustomItemOptions.builder() 40 | .customModelData(it.fragmentItemStack?.itemMeta?.customModelData ?: 0) 41 | .build() 42 | 43 | CustomItemData.builder() 44 | .icon("fragment_${it.namespace}") 45 | .name("fragment_${it.namespace}") 46 | .displayName("Disc Fragment") 47 | .customItemOptions(itemOptions) 48 | .build() 49 | }.forEach { 50 | event.register("minecraft:disc_fragment_5", it) 51 | } 52 | } 53 | } 54 | 55 | override fun isBedrockPlayer(player: UUID) = GeyserApi.api().isBedrockPlayer(player) 56 | 57 | override fun applyResourcePack(buffer: ByteArray) { 58 | val geyserPlugin = INSTANCE.server.pluginManager.getPlugin("Geyser-Spigot") ?: return 59 | 60 | geyserPlugin.dataFolder.resolve("packs").resolve("jext_resources.mcpack").writeBytes(buffer) 61 | } 62 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/listeners/BlockBrushEvent.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.listeners 2 | 3 | import me.spartacus04.colosseum.listeners.ColosseumListener 4 | import me.spartacus04.colosseum.utils.version.VersionCompatibilityMin 5 | import me.spartacus04.jext.Jext 6 | import me.spartacus04.jext.utils.Constants.WEIGHTED_LOOT_TABLE_ITEMS 7 | import me.spartacus04.jext.utils.Constants.ChanceStack 8 | import org.bukkit.Material 9 | import org.bukkit.block.BrushableBlock 10 | import org.bukkit.event.EventHandler 11 | import org.bukkit.event.block.Action 12 | import org.bukkit.event.player.PlayerInteractEvent 13 | import kotlin.random.Random 14 | 15 | @VersionCompatibilityMin("1.20") 16 | internal class BlockBrushEvent(val plugin: Jext) : ColosseumListener(plugin) { 17 | @EventHandler 18 | fun onBlockBrush(event: PlayerInteractEvent) { 19 | val brushableBlock = getBrushableBlock(event) ?: return 20 | 21 | val items = ArrayList() 22 | 23 | plugin.discs.forEach { 24 | if(it.lootTables.contains(brushableBlock.lootTable!!.key.key)) 25 | items.add(ChanceStack(it.lootTables[brushableBlock.lootTable!!.key.key]!!, it.discItemStack)) 26 | 27 | if(it.fragmentLootTables.contains(brushableBlock.lootTable!!.key.key)) 28 | items.add(ChanceStack(it.fragmentLootTables[brushableBlock.lootTable!!.key.key]!!, it.fragmentItemStack!!)) 29 | } 30 | 31 | val lootTableItems = WEIGHTED_LOOT_TABLE_ITEMS[brushableBlock.lootTable!!.key.key]!! 32 | 33 | brushableBlock.lootTable = null 34 | 35 | val totalItems = lootTableItems + items.sumOf { it.chance } 36 | val random = (0 until totalItems).random(Random(brushableBlock.seed)) 37 | 38 | if(random >= lootTableItems) { 39 | var remainingChance = random - lootTableItems 40 | 41 | for(item in items) { 42 | remainingChance -= item.chance 43 | 44 | if(remainingChance < 0) { 45 | brushableBlock.item = item.stack 46 | brushableBlock.update(true) 47 | break 48 | } 49 | } 50 | } 51 | } 52 | 53 | private fun getBrushableBlock(event: PlayerInteractEvent) : BrushableBlock? { 54 | if(event.action != Action.RIGHT_CLICK_BLOCK) return null 55 | if(event.item == null || event.item!!.type != Material.BRUSH) return null 56 | 57 | val brushableBlock = event.clickedBlock!!.state as? BrushableBlock ?: return null 58 | 59 | if(brushableBlock.lootTable == null) return null 60 | 61 | return brushableBlock 62 | } 63 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/discs/sources/file/FileDisc.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.discs.sources.file 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import me.spartacus04.jext.Jext 5 | import me.spartacus04.jext.discs.Disc 6 | import me.spartacus04.jext.discs.DiscUtils 7 | import me.spartacus04.jext.discs.discplaying.DefaultDiscPlayingMethod 8 | import me.spartacus04.jext.utils.Constants.JEXT_DISC_MATERIAL 9 | import me.spartacus04.jext.utils.Constants.JEXT_FRAGMENT_MATERIAL 10 | import org.bukkit.Bukkit 11 | 12 | @Suppress("PropertyName") 13 | internal data class FileDisc( 14 | @SerializedName("title") 15 | val TITLE: String = "", 16 | 17 | @SerializedName("author") 18 | val AUTHOR: String = "", 19 | 20 | @SerializedName("duration") 21 | val DURATION: Int = -1, 22 | 23 | @SerializedName("disc-namespace") 24 | val DISC_NAMESPACE: String, 25 | 26 | @SerializedName("model-data") 27 | val MODEL_DATA: Int, 28 | 29 | @SerializedName("creeper-drop") 30 | val CREEPER_DROP: Boolean = false, 31 | 32 | @SerializedName("lores") 33 | val LORE: List = listOf(), 34 | 35 | @SerializedName("loot-tables") 36 | val LOOT_TABLES: HashMap = HashMap(), 37 | 38 | @SerializedName("fragment-loot-tables") 39 | val FRAGMENT_LOOT_TABLES: HashMap = HashMap(), 40 | ) { 41 | fun toJextDisc(plugin: Jext) : Disc { 42 | return Disc( 43 | "jext", 44 | DiscUtils.buildCustomItemstack( 45 | JEXT_DISC_MATERIAL, 46 | MODEL_DATA, 47 | DISC_NAMESPACE, 48 | LORE, 49 | TITLE, 50 | AUTHOR 51 | ), 52 | if(plugin.serverVersion >= "1.19") DiscUtils.buildCustomItemstack( 53 | JEXT_FRAGMENT_MATERIAL!!, 54 | MODEL_DATA, 55 | DISC_NAMESPACE, 56 | LORE, 57 | TITLE, 58 | AUTHOR 59 | ) else null, 60 | DISC_NAMESPACE, 61 | if(AUTHOR.isNotEmpty()) { 62 | plugin.i18nManager!![Bukkit.getConsoleSender(), "disc-name", 63 | "author" to AUTHOR, 64 | "title" to TITLE 65 | ]!! 66 | } else { 67 | plugin.i18nManager!![Bukkit.getConsoleSender(), "disc-name-simple", 68 | "title" to TITLE 69 | ]!! 70 | }, 71 | DURATION, 72 | CREEPER_DROP, 73 | LOOT_TABLES, 74 | FRAGMENT_LOOT_TABLES, 75 | DefaultDiscPlayingMethod(), 76 | plugin 77 | ) 78 | } 79 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/commands/FragmentGiveCommand.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.commands 2 | 3 | import me.spartacus04.colosseum.commandHandling.argument.arguments.ArgumentInteger 4 | import me.spartacus04.colosseum.commandHandling.argument.arguments.ArgumentPlayers 5 | import me.spartacus04.colosseum.commandHandling.command.ColosseumCommand 6 | import me.spartacus04.colosseum.i18n.sendI18nError 7 | import me.spartacus04.colosseum.i18n.sendI18nInfo 8 | import me.spartacus04.colosseum.i18n.sendI18nWarn 9 | import me.spartacus04.jext.Jext 10 | import me.spartacus04.jext.commands.customArgs.ArgumentDisc 11 | import me.spartacus04.jext.discs.Disc 12 | import org.bukkit.command.CommandSender 13 | import org.bukkit.entity.Player 14 | 15 | class FragmentGiveCommand(val plugin: Jext) : ColosseumCommand(plugin) { 16 | override val commandData = commandDescriptor("fragmentgive") { 17 | arguments.addAll(listOf( 18 | ArgumentPlayers(false), 19 | ArgumentDisc(plugin) 20 | )) 21 | 22 | optionalArguments.add( 23 | ArgumentInteger(listOf(1, 9)) 24 | ) 25 | } 26 | 27 | override fun execute(ctx: CommandContext) { 28 | if(plugin.serverVersion < "1.19") { 29 | ctx.sender.sendI18nError(plugin, "command-not-supported", 30 | "command" to "fragmentgive", 31 | "reason" to "not supported in < 1.19" 32 | ) 33 | return 34 | } 35 | 36 | val players = ctx.getArgument>(0) 37 | val disc = ctx.getArgument(1) 38 | val amount = if(ctx.size() > 1) { 39 | ctx.getArgument(2) 40 | } else { 41 | 1 42 | } 43 | 44 | players.forEach { 45 | it.inventory.addItem(disc.fragmentItemStack!!.apply { 46 | this.amount = amount 47 | }) 48 | 49 | it.sendI18nInfo(plugin, "fragment-received", 50 | "disc" to disc.displayName, 51 | "amount" to amount.toString() 52 | ) 53 | } 54 | 55 | if(players.size > 1) { 56 | ctx.sender.sendI18nInfo(plugin, "fragment-given-multiple", 57 | "disc" to disc.displayName, 58 | "playercount" to players.size.toString(), 59 | "amount" to amount.toString() 60 | ) 61 | } else if(players.size == 1) { 62 | ctx.sender.sendI18nInfo(plugin, "fragment-given", 63 | "disc" to disc.displayName, 64 | "player" to players[0].name, 65 | "amount" to amount.toString() 66 | ) 67 | } else { 68 | ctx.sender.sendI18nWarn(plugin, "no-fragment-given") 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/listeners/VaultDispenseEvent.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.listeners 2 | 3 | import me.spartacus04.colosseum.listeners.ColosseumListener 4 | import me.spartacus04.colosseum.utils.version.VersionCompatibilityMin 5 | import me.spartacus04.jext.Jext 6 | import me.spartacus04.jext.utils.Constants 7 | import me.spartacus04.jext.utils.isRecordFragment 8 | import org.bukkit.Material 9 | import org.bukkit.block.data.type.Vault 10 | import org.bukkit.event.EventHandler 11 | import org.bukkit.event.block.BlockDispenseLootEvent 12 | 13 | @Suppress("UnstableApiUsage") 14 | @VersionCompatibilityMin("1.21") 15 | internal class VaultDispenseEvent(val plugin: Jext) : ColosseumListener(plugin) { 16 | @EventHandler 17 | fun onVaultDispenseItem(e: BlockDispenseLootEvent) { 18 | if(e.block.type != Material.VAULT) return 19 | 20 | if(e.dispensedLoot.size == 3) { 21 | generateLoot(e, (e.block.blockData as Vault).isOminous) 22 | } 23 | } 24 | 25 | private fun generateLoot(e: BlockDispenseLootEvent, ominous: Boolean) { 26 | val items = ArrayList() 27 | val lootTable = if(ominous) { 28 | "chests/trial_chambers/reward_ominous" 29 | } else { 30 | "chests/trial_chambers/reward" 31 | } 32 | 33 | plugin.discs.forEach { 34 | if(it.lootTables.contains(lootTable)) 35 | items.add(Constants.ChanceStack(it.lootTables[lootTable]!!, it.discItemStack)) 36 | 37 | if(it.fragmentLootTables.contains(lootTable)) 38 | items.add( 39 | Constants.ChanceStack( 40 | it.fragmentLootTables[lootTable]!!, 41 | it.fragmentItemStack!! 42 | ) 43 | ) 44 | } 45 | 46 | val lootTableItems = Constants.WEIGHTED_LOOT_TABLE_ITEMS[lootTable]!! 47 | 48 | val totalItems = lootTableItems + items.sumOf { it.chance } 49 | val random = (0 until totalItems).random() 50 | 51 | if(random >= lootTableItems) { 52 | var remainingChance = random - lootTableItems 53 | 54 | for(item in items) { 55 | remainingChance -= item.chance 56 | 57 | if(remainingChance < 0) { 58 | e.dispensedLoot[2] = item.stack.apply { 59 | if(this.type.isRecordFragment) { 60 | this.amount = if(plugin.config.DISC_LIMIT.containsKey(lootTable)) plugin.config.DISC_LIMIT[lootTable]!! 61 | else if(plugin.config.DISC_LIMIT.containsKey("chests/*")) plugin.config.DISC_LIMIT["chests/*"]!! 62 | else 2 63 | } 64 | } 65 | 66 | break 67 | } 68 | } 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/config/legacy/V7Config.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.config.legacy 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import me.spartacus04.colosseum.ColosseumPlugin 5 | import me.spartacus04.jext.config.Config 6 | import me.spartacus04.jext.config.fields.FieldGuiStyle 7 | import me.spartacus04.jext.config.fields.FieldJukeboxBehaviour 8 | import me.spartacus04.jext.config.fields.FieldLanguageMode 9 | 10 | @Suppress("PropertyName") 11 | internal data class V7Config( 12 | @SerializedName("lang") 13 | var LANGUAGE_MODE: FieldLanguageMode = FieldLanguageMode.AUTO, 14 | 15 | @SerializedName("jukebox-behaviour") 16 | var JUKEBOX_BEHAVIOUR : FieldJukeboxBehaviour = FieldJukeboxBehaviour.VANILLA, 17 | 18 | @SerializedName("jukebox-gui-style") 19 | var GUI_STYLE : FieldGuiStyle = FieldGuiStyle.SCROLL_VERTICAL, 20 | 21 | @SerializedName("jukebox-gui-size") 22 | var GUI_SIZE : Int = 96, 23 | 24 | @SerializedName("disable-music-overlap") 25 | var DISABLE_MUSIC_OVERLAP : Boolean = true, 26 | 27 | @SerializedName("disc-loottables-limit") 28 | var DISC_LIMIT : HashMap = hashMapOf(), 29 | 30 | @SerializedName("fragment-loottables-limit") 31 | var FRAGMENT_LIMIT : HashMap = hashMapOf(), 32 | 33 | @SerializedName("force-resource-pack") 34 | var FORCE_RESOURCE_PACK : Boolean = false, 35 | 36 | @SerializedName("check-for-updates") 37 | var CHECK_FOR_UPDATES : Boolean = true, 38 | 39 | @SerializedName("allow-metrics") 40 | var ALLOW_METRICS : Boolean = true, 41 | 42 | @SerializedName("enable-resource-pack-host") 43 | var RESOURCE_PACK_HOST : Boolean = true, 44 | 45 | @SerializedName("web-interface-port") 46 | var WEB_INTERFACE_PORT : Int = 9871, 47 | 48 | @SerializedName("web-interface-api-enabled") 49 | var WEB_INTERFACE_API_ENABLED : Boolean = true, 50 | 51 | @SerializedName("web-interface-password") 52 | var WEB_INTERFACE_PASSWORD : String = "", 53 | ) : ConfigMigrator { 54 | override fun migrateToNext(plugin: ColosseumPlugin): String { 55 | return plugin.gson.toJson(V8Config( 56 | LANGUAGE_MODE = LANGUAGE_MODE, 57 | JUKEBOX_BEHAVIOUR = JUKEBOX_BEHAVIOUR, 58 | GUI_STYLE = GUI_STYLE, 59 | GUI_SIZE = GUI_SIZE, 60 | DISABLE_MUSIC_OVERLAP = DISABLE_MUSIC_OVERLAP, 61 | DISC_LIMIT = DISC_LIMIT, 62 | FRAGMENT_LIMIT = FRAGMENT_LIMIT, 63 | FORCE_RESOURCE_PACK = FORCE_RESOURCE_PACK, 64 | CHECK_FOR_UPDATES = CHECK_FOR_UPDATES, 65 | ALLOW_METRICS = ALLOW_METRICS, 66 | RESOURCE_PACK_HOST = RESOURCE_PACK_HOST, 67 | WEB_INTERFACE_PORT = WEB_INTERFACE_PORT, 68 | WEB_INTERFACE_API_ENABLED = WEB_INTERFACE_API_ENABLED, 69 | WEB_INTERFACE_PASSWORD = WEB_INTERFACE_PASSWORD 70 | )) 71 | } 72 | 73 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/listeners/TrialSpawnerDispenseEvent.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.listeners 2 | 3 | import me.spartacus04.colosseum.listeners.ColosseumListener 4 | import me.spartacus04.colosseum.utils.version.VersionCompatibilityMin 5 | import me.spartacus04.jext.Jext 6 | import me.spartacus04.jext.utils.Constants 7 | import me.spartacus04.jext.utils.isRecordFragment 8 | import org.bukkit.Material 9 | import org.bukkit.block.data.type.TrialSpawner 10 | import org.bukkit.event.EventHandler 11 | import org.bukkit.event.block.BlockDispenseLootEvent 12 | import kotlin.collections.contains 13 | 14 | @Suppress("UnstableApiUsage") 15 | @VersionCompatibilityMin("1.21") 16 | internal class TrialSpawnerDispenseEvent(val plugin: Jext) : ColosseumListener(plugin) { 17 | @EventHandler 18 | fun onVaultDispenseItem(e: BlockDispenseLootEvent) { 19 | if(e.block.type != Material.TRIAL_SPAWNER) return 20 | 21 | if(e.dispensedLoot[0].type != Material.TRIAL_KEY && e.dispensedLoot[0].type != Material.OMINOUS_TRIAL_KEY) { 22 | generateLoot(e, (e.block.blockData as TrialSpawner).isOminous) 23 | } 24 | } 25 | 26 | private fun generateLoot(e: BlockDispenseLootEvent, ominous: Boolean) { 27 | val items = ArrayList() 28 | val lootTable = if(ominous) { 29 | "spawners/ominous/trial_chamber/consumables" 30 | } else { 31 | "spawners/trial_chamber/consumables" 32 | } 33 | 34 | plugin.discs.forEach { 35 | if(it.lootTables.contains(lootTable)) 36 | items.add(Constants.ChanceStack(it.lootTables[lootTable]!!, it.discItemStack)) 37 | 38 | if(it.fragmentLootTables.contains(lootTable)) 39 | items.add( 40 | Constants.ChanceStack( 41 | it.fragmentLootTables[lootTable]!!, 42 | it.fragmentItemStack!! 43 | ) 44 | ) 45 | } 46 | 47 | val lootTableItems = Constants.WEIGHTED_LOOT_TABLE_ITEMS[lootTable]!! 48 | 49 | val totalItems = lootTableItems + items.sumOf { it.chance } 50 | val random = (0 until totalItems).random() 51 | 52 | if(random >= lootTableItems) { 53 | var remainingChance = random - lootTableItems 54 | 55 | for(item in items) { 56 | remainingChance -= item.chance 57 | 58 | if(remainingChance < 0) { 59 | e.dispensedLoot[0] = item.stack.apply { 60 | if(this.type.isRecordFragment) { 61 | this.amount = if(plugin.config.DISC_LIMIT.containsKey(lootTable)) plugin.config.DISC_LIMIT[lootTable]!! 62 | else if(plugin.config.DISC_LIMIT.containsKey("chests/*")) plugin.config.DISC_LIMIT["chests/*"]!! 63 | else 2 64 | } 65 | } 66 | 67 | break 68 | } 69 | } 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/discs/sources/nbs/NbsSource.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.discs.sources.nbs 2 | 3 | import com.google.common.reflect.TypeToken 4 | import com.xxmicloxx.NoteBlockAPI.utils.NBSDecoder 5 | import me.spartacus04.jext.Jext 6 | import me.spartacus04.jext.discs.Disc 7 | import me.spartacus04.jext.discs.sources.DiscSource 8 | import org.bukkit.Bukkit 9 | 10 | internal class NbsSource : DiscSource { 11 | private val nbsTypeToken = object : TypeToken>() {}.type 12 | 13 | private fun isNoteBlockApiPresent(): Boolean { 14 | return Bukkit.getPluginManager().getPlugin("NoteBlockAPI") != null 15 | } 16 | 17 | override suspend fun getDiscs(plugin: Jext): List { 18 | if (!isNoteBlockApiPresent()) { 19 | return emptyList() 20 | } 21 | 22 | val baseNbsDir = plugin.dataFolder.resolve("nbs") 23 | 24 | if(!baseNbsDir.exists()) { 25 | baseNbsDir.mkdirs() 26 | } 27 | 28 | val contents = plugin.assetsManager.getAsset("nbs")?.bufferedReader()?.readText() ?: "[]" 29 | 30 | val discsMeta = plugin.gson.fromJson>(contents, nbsTypeToken) 31 | 32 | // rename all nbs files to lowercase to avoid issues 33 | 34 | baseNbsDir.listFiles { _, name -> name.endsWith(".nbs") }?.forEach { nbsFile -> 35 | val newName = nbsFile.name.lowercase() 36 | nbsFile.renameTo(baseNbsDir.resolve(newName)) 37 | } 38 | 39 | val nbsFiles = baseNbsDir.listFiles { _, name -> name.endsWith(".nbs") } ?: emptyArray() 40 | 41 | // Remove discs that don't have a corresponding NBS file and add new discs when needed 42 | 43 | var changes = false 44 | 45 | nbsFiles.forEach { nbsFile -> 46 | val discMeta = discsMeta.find { it.DISC_NAMESPACE == nbsFile.nameWithoutExtension } 47 | 48 | if(discMeta == null) { 49 | changes = true 50 | 51 | val song = NBSDecoder.parse(nbsFile) 52 | 53 | val newDiscMeta = NbsDisc( 54 | TITLE = song.title.ifBlank { nbsFile.nameWithoutExtension }, 55 | AUTHOR = song.originalAuthor, 56 | DISC_NAMESPACE = nbsFile.nameWithoutExtension, 57 | MODEL_DATA = 0, 58 | LORE = song.description.split("\n").filter { it.isNotBlank() } 59 | ) 60 | 61 | discsMeta.add(newDiscMeta) 62 | } 63 | } 64 | 65 | discsMeta.removeIf { discMeta -> 66 | val nbsFile = baseNbsDir.resolve("${discMeta.DISC_NAMESPACE}.nbs") 67 | 68 | if(!nbsFile.exists()) { 69 | changes = true 70 | return@removeIf true 71 | } 72 | 73 | return@removeIf false 74 | } 75 | 76 | if(changes) { 77 | plugin.assetsManager.saveAsset("nbs", plugin.gson.toJson(discsMeta)) 78 | } 79 | 80 | return discsMeta.mapNotNull { it.toJextDisc(plugin) } 81 | } 82 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/listeners/JukeboxClickEvent.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.listeners 2 | 3 | import me.spartacus04.colosseum.listeners.ColosseumListener 4 | import me.spartacus04.jext.Jext 5 | import me.spartacus04.jext.config.fields.FieldJukeboxBehaviour 6 | import me.spartacus04.jext.discs.Disc 7 | import me.spartacus04.jext.gui.JukeboxGui 8 | import org.bukkit.Material 9 | import org.bukkit.block.Block 10 | import org.bukkit.block.Jukebox 11 | import org.bukkit.event.EventHandler 12 | import org.bukkit.event.EventPriority 13 | import org.bukkit.event.block.Action 14 | import org.bukkit.event.block.BlockBreakEvent 15 | import org.bukkit.event.player.PlayerInteractEvent 16 | 17 | internal class JukeboxClickEvent(val plugin: Jext) : ColosseumListener(plugin) { 18 | @EventHandler(ignoreCancelled = true, priority = EventPriority.LOW) 19 | fun onJukeboxInteract(event: PlayerInteractEvent) { 20 | val block = event.clickedBlock ?: return 21 | 22 | if (event.action != Action.RIGHT_CLICK_BLOCK || block.type != Material.JUKEBOX) return 23 | if (event.player.isSneaking) return 24 | 25 | if(!event.player.hasPermission("jext.usejukebox")){ 26 | event.isCancelled = true 27 | return 28 | } 29 | 30 | when(plugin.config.JUKEBOX_BEHAVIOUR) { 31 | FieldJukeboxBehaviour.VANILLA -> defaultBehaviour(event, block) 32 | else -> jukeboxGui(event, block) 33 | } 34 | } 35 | 36 | private fun defaultBehaviour(event: PlayerInteractEvent, block: Block) { 37 | if(!plugin.integrations.hasJukeboxAccess(event.player, block)) { 38 | event.isCancelled = true 39 | return 40 | } 41 | 42 | val state = block.state as? Jukebox ?: return 43 | val location = block.location 44 | 45 | if(state.record.type == Material.AIR) { 46 | val disc = event.item ?: return 47 | if(!disc.type.isRecord) return 48 | 49 | Disc.fromItemstack(disc)?.play(location) 50 | } 51 | else { 52 | Disc.fromItemstack(state.record)?.namespace?.let { 53 | plugin.discs.stop(location, it) 54 | } 55 | } 56 | } 57 | 58 | private fun jukeboxGui(event: PlayerInteractEvent, block: Block) { 59 | event.isCancelled = true 60 | 61 | if(!plugin.integrations.hasJukeboxGuiAccess(event.player, block)) { 62 | return 63 | } 64 | 65 | JukeboxGui.open(event.player, block) 66 | } 67 | 68 | @EventHandler(ignoreCancelled = true) 69 | fun onJukeboxBreak(event: BlockBreakEvent) { 70 | val loc = event.block.location 71 | 72 | JukeboxGui.destroyJukebox(loc).forEach { 73 | loc.world!!.dropItemNaturally(loc, it) 74 | } 75 | 76 | val block = event.block 77 | val state = block.state as? Jukebox ?: return 78 | 79 | Disc.fromItemstack(state.record)?.namespace?.let { 80 | plugin.discs.stop(loc, it) 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/resources/langs/zh_CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "now-playing": "§6正在播放: §a%name%", 3 | "disc-name": "§a%author% §7- §a%title%", 4 | "disc-name-simple": "§a%title%", 5 | "update-available": "发现新版本!", 6 | "resource-pack-decline-kick-message": "必须启用音乐资源包!", 7 | "failed-download-kick-message": "资源包下载失败,请重新加入以重试。", 8 | "jukebox": "§9唱片机", 9 | "scroll-down": "§7向下滚动", 10 | "scroll-up": "§7向上滚动", 11 | "scroll-left": "§7向左滚动", 12 | "scroll-right": "§7向右滚动", 13 | "next-page": "§7下一页", 14 | "prev-page": "§7上一页", 15 | "no-page": "§c没有更多的页面!", 16 | "cant-scroll-further": "§c无法进一步滚动!", 17 | "disc-command-success": "§f已获取唱片 §d%disc% §f!", 18 | "fragment-command-success": "§fObtained §d%amount% §ffragments of §d%disc%§f!", 19 | "disc-namespace-not-found": "§c无法找到命名空间 §6%namespace% §c!", 20 | "disc-received": "§f已收到唱片 §d%disc% §f!", 21 | "fragment-received": "§fReceived §d%amount% §ffragments of §d%disc%§f!", 22 | "disc-given-multiple": "§f将唱片 §6%disc% §f 给到了 §6%playercount% §f位玩家 !", 23 | "fragment-given-multiple": "§fGiven §6%amount% §ffragments of §6%disc% §fto §6%playercount% §fplayers!", 24 | "disc-given": "§f将唱片 §6%disc% §f 给了 §6%player% §f !", 25 | "fragment-given": "§fGiven §6%amount% §ffragments of §6%disc% §fto §6%player%§f!", 26 | "no-disc-given": "§f出错了,唱片没有给到任何玩家!", 27 | "no-fragment-given": "§f给没有玩家的片段出现了问题!", 28 | "invalid-location": "§c坐标无效!", 29 | "wrong-number-format": "§c %param% 参数的数字格式错误!", 30 | "cannot-find-player": "§c找不到玩家: §6%player%§c.", 31 | "music-now-playing": "§f正在播放 $d%name% §f!", 32 | "played-music-to-multiple": "§f为 §6%playercount%个 §f玩家播放了 §6%name% §f!", 33 | "played-music-to": "§f为玩家 §6%player% §f播放了 §6%name% §f!", 34 | "played-music-to-no-one": "§f出错了,没有为任何玩家播放音乐!", 35 | "stopped-music": "§f已停止播放 §d%name%§f!", 36 | "stopped-all-music": "§f已停止所有音乐!", 37 | "stopped-music-for-multiple": "§f已为§6%playercount%位 §f玩家停止播放 !", 38 | "stopped-music-for": "§f已为玩家§6%player%§f停止播放音乐 !", 39 | "stopped-music-for-no-one": "§f出错了, 没有为任何玩家停止播放!", 40 | "export-start": "§f正在将资源包导出至 §6%file%§f……", 41 | "export-success": "§f资源包已导出至 §6%file%§f!", 42 | "export-fail": "§c资源包导出失败!", 43 | "reloaded": "§c已重新载入配置!", 44 | "error-missing-permission": "§c你没有权限使用此命令!", 45 | "error-malformed-command": "§cMalformed command: expected '§e%expected%§c' arguments, got '§e%got%§c'.", 46 | "error-malformed-argument": "cMalformed argument: expected '§e%expected%§c' at '§e%at%§c'.", 47 | "error-insufficient-consumes": "§cError: Expected at least §e'%expected%'§c arguments at §e'%at%'§c, got '§e%got%§c'.", 48 | "error-invalid-sender-at": "§cError: Invalid sender '§e%sender%§c' at §e'%at%'§c'.", 49 | "error-no-players": "错误:没有找到玩家", 50 | "error-no-entities": "错误:未找到实体", 51 | "error-invalid-sender": "§cThis command can only be run by §6%runner%§c.", 52 | "command-not-supported": "§c命令 §6%command% §c不受支持,因为 §6%reason%§c。", 53 | "webui": "§a网页已启用,地址 §2%url%", 54 | "webui-disabled": "§c网页已禁用!", 55 | "usage": "[用法]: §6/%command%", 56 | "playat-command-success": "§fPlaying music §d%disc% §fat location §6%location%§f in world §6%world%§f!" 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/discs/DiscUtils.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.discs 2 | 3 | import me.spartacus04.jext.Jext.Companion.INSTANCE 4 | import org.bukkit.Bukkit 5 | import org.bukkit.ChatColor 6 | import org.bukkit.Material 7 | import org.bukkit.inventory.ItemFlag 8 | import org.bukkit.inventory.ItemStack 9 | 10 | /** 11 | * The object `DiscUtils` is a utility object that's used to build custom itemstacks for the discs. 12 | */ 13 | object DiscUtils { 14 | private fun getProcessedLores(lores: List, title: String, author: String): ArrayList { 15 | val lore = ArrayList() 16 | 17 | if(!INSTANCE.config.DISC_MODIFY_ITEM_NAME) 18 | if(author != "") 19 | lore.add("${ChatColor.GRAY}$author - $title") 20 | else 21 | lore.add("${ChatColor.GRAY}$title") 22 | 23 | lore.addAll(lores) 24 | 25 | return lore 26 | } 27 | 28 | /** 29 | * Builds a custom itemstack for the disc. 30 | * 31 | * @param material The material of the itemstack. 32 | * @param modelData The model data of the itemstack. 33 | * @param namespace The namespace of the disc. 34 | * @param lores The lores of the itemstack. 35 | * @param title The title of the itemstack. 36 | * @param author The author of the itemstack. 37 | * 38 | * @return The custom itemstack. 39 | */ 40 | fun buildCustomItemstack(material: Material, modelData: Int, namespace: String, lores: List, title: String, author: String): ItemStack { 41 | val disc = if(INSTANCE.serverVersion >= "1.21.5") { 42 | Bukkit.getItemFactory().createItemStack("${material.name.lowercase()}[jukebox_playable=\"minecraft:11\",tooltip_display={hidden_components:[\"minecraft:jukebox_playable\"]}]") 43 | } else if(INSTANCE.serverVersion >= "1.21") { 44 | Bukkit.getItemFactory().createItemStack("${material.name.lowercase()}[jukebox_playable={song:\"minecraft:11\",show_in_tooltip:false}]") 45 | } else { 46 | ItemStack(material) 47 | } 48 | val meta = disc.itemMeta 49 | 50 | meta!!.addItemFlags(if(INSTANCE.serverVersion >= "1.19.5") 51 | ItemFlag.HIDE_ADDITIONAL_TOOLTIP 52 | else 53 | ItemFlag.valueOf("HIDE_POTION_EFFECTS") 54 | ) 55 | 56 | meta.setCustomModelData(modelData) 57 | 58 | if (INSTANCE.config.DISC_MODIFY_ITEM_NAME) 59 | if(author != "") { 60 | meta.setItemName("${ChatColor.YELLOW}$author - $title") 61 | meta.setDisplayName("${ChatColor.YELLOW}$author - $title") 62 | } else { 63 | meta.setItemName("${ChatColor.YELLOW}$title") 64 | meta.setDisplayName("${ChatColor.YELLOW}$title") 65 | } 66 | 67 | // Store custom disc data 68 | val helper = DiscPersistentDataContainer(meta) 69 | helper.namespaceID = namespace 70 | helper.setIdentifier() 71 | 72 | meta.lore = getProcessedLores(lores, title, author) 73 | disc.itemMeta = meta 74 | return disc 75 | } 76 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/gui/BaseGui.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.gui 2 | 3 | import me.spartacus04.jext.Jext 4 | import org.bukkit.block.Block 5 | import org.bukkit.entity.Player 6 | import xyz.xenondevs.invui.inventory.VirtualInventory 7 | import xyz.xenondevs.invui.inventory.event.ItemPostUpdateEvent 8 | import xyz.xenondevs.invui.inventory.event.ItemPreUpdateEvent 9 | import xyz.xenondevs.invui.window.Window 10 | 11 | /** 12 | * The abstract class `BaseGui` is used to create a base gui for the plugin. 13 | */ 14 | abstract class BaseGui { 15 | internal val targetPlayer: Player 16 | internal val inventoryId: String 17 | internal val targetBlock: Block? 18 | internal val isBedrock: Boolean 19 | internal val inventory: VirtualInventory 20 | private val inventoryName: String 21 | private val plugin: Jext 22 | 23 | /** 24 | * Create a new jukebox container for a player 25 | */ 26 | protected constructor(player: Player, inventory: VirtualInventory, inventoryName: String, plugin: Jext) { 27 | this.targetPlayer = player 28 | this.inventoryId = player.uniqueId.toString() 29 | this.targetBlock = null 30 | this.isBedrock = plugin.geyserManager.isBedrockPlayer(player) 31 | this.inventory = inventory 32 | this.inventoryName = inventoryName 33 | this.plugin = plugin 34 | 35 | this.onInit() 36 | this.setHandlers() 37 | this.finalizeGui() 38 | } 39 | 40 | /** 41 | * Create a new jukebox container for a block 42 | */ 43 | protected constructor(player: Player, block: Block, inventory: VirtualInventory, inventoryName: String, plugin: Jext) { 44 | this.targetPlayer = player 45 | this.inventoryId = "${block.location.world!!.name}:${block.location.blockX}:${block.location.blockY}:${block.location.blockZ}" 46 | this.targetBlock = block 47 | this.isBedrock = plugin.geyserManager.isBedrockPlayer(player) 48 | this.inventory = inventory 49 | this.inventoryName = inventoryName 50 | this.plugin = plugin 51 | 52 | this.onInit() 53 | this.setHandlers() 54 | this.finalizeGui() 55 | } 56 | 57 | /** 58 | * Fires when an item is about to be updated. 59 | */ 60 | abstract fun onItemPreUpdate(event: ItemPreUpdateEvent) 61 | 62 | /** 63 | * Fires when an item has been updated. 64 | */ 65 | abstract fun onItemPostUpdate(event: ItemPostUpdateEvent) 66 | 67 | /** 68 | * Fires when the gui is initialized. 69 | */ 70 | abstract fun onInit() 71 | 72 | private fun setHandlers() { 73 | inventory.setPreUpdateHandler(this::onItemPreUpdate) 74 | inventory.setPostUpdateHandler(this::onItemPostUpdate) 75 | } 76 | 77 | /** 78 | * Finalizes the gui. 79 | */ 80 | @SuppressWarnings 81 | fun finalizeGui() { 82 | val gui = GuiBuilder().buildGui(targetPlayer, inventory, plugin) 83 | 84 | val window = Window.single() 85 | .setViewer(targetPlayer) 86 | .setTitle(inventoryName) 87 | .setGui(gui) 88 | .build() 89 | 90 | window.open() 91 | 92 | inventory.notifyWindows() 93 | } 94 | } -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/config/legacy/V8Config.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.config.legacy 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import me.spartacus04.colosseum.ColosseumPlugin 5 | import me.spartacus04.jext.config.Config 6 | import me.spartacus04.jext.config.fields.FieldGuiStyle 7 | import me.spartacus04.jext.config.fields.FieldJukeboxBehaviour 8 | import me.spartacus04.jext.config.fields.FieldLanguageMode 9 | 10 | /** 11 | * The data class `Config` is used to store the plugin's configuration settings. 12 | */ 13 | @Suppress("PropertyName") 14 | internal data class V8Config( 15 | @SerializedName("lang") 16 | var LANGUAGE_MODE: FieldLanguageMode = FieldLanguageMode.AUTO, 17 | 18 | @SerializedName("jukebox-behaviour") 19 | var JUKEBOX_BEHAVIOUR : FieldJukeboxBehaviour = FieldJukeboxBehaviour.VANILLA, 20 | 21 | @SerializedName("jukebox-gui-style") 22 | var GUI_STYLE : FieldGuiStyle = FieldGuiStyle.SCROLL_VERTICAL, 23 | 24 | @SerializedName("jukebox-gui-size") 25 | var GUI_SIZE : Int = 96, 26 | 27 | @SerializedName("disable-music-overlap") 28 | var DISABLE_MUSIC_OVERLAP : Boolean = true, 29 | 30 | @SerializedName("jukebox-range") 31 | var JUKEBOX_RANGE: Int = 64, 32 | 33 | @SerializedName("disc-loottables-limit") 34 | var DISC_LIMIT : HashMap = HashMap(), 35 | 36 | @SerializedName("fragment-loottables-limit") 37 | var FRAGMENT_LIMIT : HashMap = HashMap(), 38 | 39 | @SerializedName("force-resource-pack") 40 | var FORCE_RESOURCE_PACK : Boolean = false, 41 | 42 | @SerializedName("check-for-updates") 43 | var CHECK_FOR_UPDATES : Boolean = true, 44 | 45 | @SerializedName("allow-metrics") 46 | var ALLOW_METRICS : Boolean = true, 47 | 48 | @SerializedName("enable-resource-pack-host") 49 | var RESOURCE_PACK_HOST : Boolean = true, 50 | 51 | @SerializedName("web-interface-port") 52 | var WEB_INTERFACE_PORT : Int = 9871, 53 | 54 | @SerializedName("override-web-interface-base-url") 55 | var WEB_INTERFACE_BASE_URL: String = "", 56 | 57 | @SerializedName("web-interface-api-enabled") 58 | var WEB_INTERFACE_API_ENABLED : Boolean = true, 59 | 60 | @SerializedName("web-interface-password") 61 | var WEB_INTERFACE_PASSWORD : String = "", 62 | ) : ConfigMigrator { 63 | override fun migrateToNext(plugin: ColosseumPlugin): String { 64 | return plugin.gson.toJson(Config( 65 | LANGUAGE_MODE = LANGUAGE_MODE, 66 | JUKEBOX_BEHAVIOUR = JUKEBOX_BEHAVIOUR, 67 | GUI_STYLE = GUI_STYLE, 68 | GUI_SIZE = GUI_SIZE, 69 | DISABLE_MUSIC_OVERLAP = DISABLE_MUSIC_OVERLAP, 70 | DISC_LIMIT = DISC_LIMIT, 71 | FRAGMENT_LIMIT = FRAGMENT_LIMIT, 72 | FORCE_RESOURCE_PACK = FORCE_RESOURCE_PACK, 73 | CHECK_FOR_UPDATES = CHECK_FOR_UPDATES, 74 | ALLOW_METRICS = ALLOW_METRICS, 75 | RESOURCE_PACK_HOST = RESOURCE_PACK_HOST, 76 | WEB_INTERFACE_BASE_URL = WEB_INTERFACE_BASE_URL, 77 | WEB_INTERFACE_PORT = WEB_INTERFACE_PORT, 78 | WEB_INTERFACE_API_ENABLED = WEB_INTERFACE_API_ENABLED, 79 | WEB_INTERFACE_PASSWORD = WEB_INTERFACE_PASSWORD 80 | )) 81 | } 82 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH= 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /src/main/resources/langs/ja_JP.json: -------------------------------------------------------------------------------- 1 | { 2 | "now-playing": "§6再生中: §a%name%", 3 | "disc-name": "§a%author% §7- §a%title%", 4 | "disc-name-simple": "§a%title%", 5 | "update-available": "新しいアップデートが利用可能です!", 6 | "resource-pack-decline-kick-message": "音楽のリソースパックを有効にしてください!", 7 | "failed-download-kick-message": "リソースパックのダウンロードに失敗しました。もう一度やり直してください。", 8 | "jukebox": "§9ジュークボックス", 9 | "scroll-down": "§7スクロールダウン", 10 | "scroll-up": "§7上にスクロール", 11 | "scroll-left": "§7左スクロール", 12 | "scroll-right": "§7右スクロール", 13 | "next-page": "§7次のページ", 14 | "prev-page": "§7前のページ", 15 | "no-page": "§cこれ以上ページがありません!", 16 | "cant-scroll-further": "§cこれ以上スクロールできません!", 17 | "disc-command-success": "§f§d%disc% §fdiscを獲得しました!", 18 | "fragment-command-success": "§f §d%amount% §ffragments of §d%disc%§fを獲得しました!", 19 | "disc-namespace-not-found": "§cディスクは名前空間§6%namespace% §c見つかりませんでした!", 20 | "disc-received": "§f Received §d%disc% §fdisc!", 21 | "fragment-received": "§f Received §d%amount% §ffragments of §d%disc%§f!", 22 | "disc-given-multiple": "§6%disc% §fdisc を §6%playercount% §fplayersに与えました!", 23 | "fragment-given-multiple": "§6%amount% §ffragments of §6%disc% §fに§6%playercount% §fplayers!", 24 | "disc-given": "§6%disc% §fディスクを §6%player%§fに与えました!", 25 | "fragment-given": "§6%amount% §ffragments of §6%disc% §fto §6%player%§f!", 26 | "no-disc-given": "§fプレイヤーにディスクが渡されていません。何かが間違っています!", 27 | "no-fragment-given": "§fフラグメントをプレイヤーに与えられませんでした。何かが間違っています!", 28 | "invalid-location": "§c無効な場所です!", 29 | "wrong-number-format": "§c %param% パラメータの数値形式が間違っています!", 30 | "cannot-find-player": "§cプレイヤーが見つかりません: §6%player%§c.", 31 | "music-now-playing": "§fMusic $d%name% §fがプレイ中です!", 32 | "played-music-to-multiple": "§f Played music §6%name% §fto §6%playercount% §fplayers!", 33 | "played-music-to": "§f Played music §6%name% §fto §6%player%§f!", 34 | "played-music-to-no-one": "§f音楽を再生しないようにしました。何か問題が発生しました!", 35 | "stopped-music": "§f Stoped music §d%name%§f!", 36 | "stopped-all-music": "§fすべての音楽を停止しました!", 37 | "stopped-music-for-multiple": "§f§6%playercount% §fプレイヤーの音楽を停止しました!", 38 | "stopped-music-for": "§f音楽を§6%player%§f停止しました!", 39 | "stopped-music-for-no-one": "§fプレイヤーなしで音楽を停止しました。何か問題が発生しました!", 40 | "export-start": "§fリソースを§6%file%§fにエクスポートしています...", 41 | "export-success": "§fResourcepack を §6%file%§f にエクスポートしました!", 42 | "export-fail": "§cリソースパックのエクスポートに失敗しました!", 43 | "reloaded": "§c再読み込みしました!", 44 | "error-missing-permission": "§cこのコマンドを使用する権限がありません!", 45 | "error-malformed-command": "§c不正な形式のコマンド: '§e%expected%§c' 引数を想定し、'§e%got%§c'を得ました。", 46 | "error-malformed-argument": "cMalformed argument: expected '§e%expected%§c' at '§e%at%§c'.", 47 | "error-insufficient-consumes": "§cError: Expected at least §e'%expected%'§c arguments at §e'%at%'§c, got '§e%got%§c'", 48 | "error-invalid-sender-at": "§cエラー: 無効な送信者'§e%sender%§c '§e%at%'§c'。", 49 | "error-no-players": "エラー:プレイヤーが見つかりませんでした", 50 | "error-no-entities": "エラー:エンティティが見つかりません", 51 | "error-invalid-sender": "§cこのコマンドは§6%runner%§cでのみ実行できます。", 52 | "command-not-supported": "§6%command% §cコマンドはサポートされていません: §6%reason%§c.", 53 | "webui": "§aWeb UI は §2%url% で利用できます", 54 | "webui-disabled": "§cWeb UIは現在無効になっています!", 55 | "usage": "[使用方法]: §6/%command%", 56 | "playat-command-success": "§f Playing music §d%disc% §fat location §6%location%§f in world §6%world%§f!" 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/me/spartacus04/jext/discs/sources/nbs/NbsDisc.kt: -------------------------------------------------------------------------------- 1 | package me.spartacus04.jext.discs.sources.nbs 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.xxmicloxx.NoteBlockAPI.utils.NBSDecoder 5 | import me.spartacus04.colosseum.i18n.ColosseumI18nManager 6 | import me.spartacus04.jext.Jext 7 | import me.spartacus04.jext.Jext.Companion.INSTANCE 8 | import me.spartacus04.jext.discs.Disc 9 | import me.spartacus04.jext.discs.DiscUtils 10 | import me.spartacus04.jext.discs.discplaying.NbsDiscPlayingMethod 11 | import me.spartacus04.jext.language.DefaultMessages.NBS_NOT_FOUND 12 | import me.spartacus04.jext.utils.Constants.JEXT_DISC_MATERIAL 13 | import me.spartacus04.jext.utils.Constants.JEXT_FRAGMENT_MATERIAL 14 | import org.bukkit.Bukkit 15 | import kotlin.math.ceil 16 | 17 | @Suppress("PropertyName") 18 | internal data class NbsDisc( 19 | @SerializedName("title") 20 | val TITLE: String = "", 21 | 22 | @SerializedName("author") 23 | val AUTHOR: String = "", 24 | 25 | @SerializedName("disc-namespace") 26 | val DISC_NAMESPACE: String, 27 | 28 | @SerializedName("model-data") 29 | val MODEL_DATA: Int, 30 | 31 | @SerializedName("creeper-drop") 32 | val CREEPER_DROP: Boolean = false, 33 | 34 | @SerializedName("lores") 35 | val LORE: List = listOf(), 36 | 37 | @SerializedName("loot-tables") 38 | val LOOT_TABLES: HashMap = HashMap(), 39 | 40 | @SerializedName("fragment-loot-tables") 41 | val FRAGMENT_LOOT_TABLES: HashMap = HashMap(), 42 | ) { 43 | fun toJextDisc(plugin: Jext) : Disc? { 44 | val nbsFile = plugin.dataFolder.resolve("nbs").resolve("$DISC_NAMESPACE.nbs") 45 | 46 | if(!nbsFile.exists()) { 47 | plugin.colosseumLogger.warn( 48 | ColosseumI18nManager.replacePlaceholders(NBS_NOT_FOUND, hashMapOf( 49 | "name" to DISC_NAMESPACE 50 | )) 51 | ) 52 | 53 | return null 54 | } 55 | 56 | val song = NBSDecoder.parse(nbsFile) 57 | 58 | return Disc( 59 | "jext-nbs", 60 | DiscUtils.buildCustomItemstack( 61 | JEXT_DISC_MATERIAL, 62 | MODEL_DATA, 63 | DISC_NAMESPACE, 64 | LORE, 65 | TITLE, 66 | AUTHOR 67 | ), 68 | if(plugin.serverVersion >= "1.19") DiscUtils.buildCustomItemstack( 69 | JEXT_FRAGMENT_MATERIAL!!, 70 | MODEL_DATA, 71 | DISC_NAMESPACE, 72 | LORE, 73 | TITLE, 74 | AUTHOR 75 | ) else null, 76 | DISC_NAMESPACE, 77 | if(AUTHOR.isNotEmpty()) { 78 | INSTANCE.i18nManager!![Bukkit.getConsoleSender(), "disc-name", 79 | "author" to AUTHOR, 80 | "title" to TITLE 81 | ]!! 82 | } else { 83 | INSTANCE.i18nManager!![Bukkit.getConsoleSender(), "disc-name-simple", 84 | "title" to TITLE 85 | ]!! 86 | }, 87 | ceil(song.length / song.speed).toInt(), 88 | CREEPER_DROP, 89 | LOOT_TABLES, 90 | FRAGMENT_LOOT_TABLES, 91 | NbsDiscPlayingMethod(song), 92 | plugin 93 | ) 94 | } 95 | } -------------------------------------------------------------------------------- /src/main/resources/langs/ko_KR.json: -------------------------------------------------------------------------------- 1 | { 2 | "now-playing": "§a%name% §6재생 중", 3 | "disc-name": "§a%author% §7- §a%title%", 4 | "disc-name-simple": "§a%title%", 5 | "update-available": "새로운 업데이트가 있습니다!", 6 | "resource-pack-decline-kick-message": "음악 리소스팩을 적용시켜주세요!", 7 | "failed-download-kick-message": "리소스팩 다운로드에 실패하였습니다. 재접속해 다시 시도해 보세요.", 8 | "jukebox": "§9주크박스", 9 | "scroll-down": "§7아래로 스크롤", 10 | "scroll-up": "§7위로 스크롤", 11 | "scroll-left": "§7왼쪽으로 스크롤", 12 | "scroll-right": "§7오른쪽으로 스크롤", 13 | "next-page": "§7다음 페이지", 14 | "prev-page": "§7이전 페이지", 15 | "no-page": "§c페이지가 더 이상 없습니다!", 16 | "cant-scroll-further": "§c더 이상 스크롤할 수 없습니다!", 17 | "disc-command-success": "§d%disc%§f을 획득했습니다!", 18 | "fragment-command-success": "§fObtained §d%amount% §ffragments of §d%disc%§f!", 19 | "disc-namespace-not-found": "§6%namespace% §c을 포함한 음반을 찾을 수 없습니다!", 20 | "disc-received": "§d%disc%§f을 획득했습니다!", 21 | "fragment-received": "§fReceived §d%amount% §ffragments of §d%disc%§f!", 22 | "disc-given-multiple": "§6%disc% §f음반을 §6%playercount% §f명의 플레이어에게 지급했습니다.", 23 | "fragment-given-multiple": "§fGiven §6%amount% §ffragments of §6%disc% §fto §6%playercount% §fplayers!", 24 | "disc-given": "§6%disc% §f음반을 §6%player% §f에게 지급했습니다.", 25 | "fragment-given": "§fGiven §6%amount% §ffragments of §6%disc% §fto §6%player%§f!", 26 | "no-disc-given": "§f음반을 제공할 플레이어를 찾지 못했습니다.", 27 | "no-fragment-given": "§fGiven fragments to no player, something went wrong!", 28 | "invalid-location": "§c잘못된 위치!", 29 | "wrong-number-format": "§c%param% 매개변수의 숫자 형식이 잘못되었습니다.", 30 | "cannot-find-player": "플레이어 §6%player%§c을 찾을 수 없습니다.", 31 | "music-now-playing": "$d%name% §f을 재생합니다!", 32 | "played-music-to-multiple": "§6%playercount%§f명의 플레이어에게 §6%name%§f을 재생합니다!", 33 | "played-music-to": "§f플레이어 §6%player%§f에게 §6%name%§f을 재생합니다!", 34 | "played-music-to-no-one": "§f음악을 재생할 플레이어를 찾지 못했습니다.", 35 | "stopped-music": "§f음악 §d%name%§f을 중지합니다!", 36 | "stopped-all-music": "§f모든 음악을 멈췄습니다.", 37 | "stopped-music-for-multiple": "§6%playercount%§f명의 플레이어의 음악을 멈췄습니다.", 38 | "stopped-music-for": "§6%player%§f의 음악을 멈췄습니다!", 39 | "stopped-music-for-no-one": "§f음악을 재생할 플레이어를 찾지 못해 음악을 멈췄습니다.", 40 | "export-start": "§fExporting resourcepack to §6%file%§f...", 41 | "export-success": "§fResourcepack exported to §6%file%§f!", 42 | "export-fail": "§cResourcepack export failed!", 43 | "reloaded": "§c리로드!", 44 | "error-missing-permission": "§cYou don't have permission to use this command!", 45 | "error-malformed-command": "§cMalformed command: expected '§e%expected%§c' arguments, got '§e%got%§c'.", 46 | "error-malformed-argument": "cMalformed argument: expected '§e%expected%§c' at '§e%at%§c'.", 47 | "error-insufficient-consumes": "§cError: Expected at least §e'%expected%'§c arguments at §e'%at%'§c, got '§e%got%§c'.", 48 | "error-invalid-sender-at": "§cError: Invalid sender '§e%sender%§c' at §e'%at%'§c'.", 49 | "error-no-players": "Error: No players found", 50 | "error-no-entities": "Error: No entities found", 51 | "error-invalid-sender": "§cThis command can only be run by §6%runner%§c.", 52 | "command-not-supported": "§6%command% §c은 지원하지 않는 명령어입니다. 이유: §6%reason%§c.", 53 | "webui": "§aWeb UI available at §2%url%", 54 | "webui-disabled": "§cThe Web UI is currently disabled!", 55 | "usage": "[사용법]: §6/%command%", 56 | "playat-command-success": "§fPlaying music §d%disc% §fat location §6%location%§f in world §6%world%§f!" 57 | } 58 | -------------------------------------------------------------------------------- /src/main/resources/langs/zh_TW.json: -------------------------------------------------------------------------------- 1 | { 2 | "now-playing": "§6正在播放:§a%name%", 3 | "disc-name": "§a%author% §7- §a%title%", 4 | "disc-name-simple": "§a%title%", 5 | "update-available": "有可用的更新!", 6 | "resource-pack-decline-kick-message": "请启用音乐资源包!", 7 | "failed-download-kick-message": "资源包下载失败, 请重新加入以重试.", 8 | "jukebox": "§9音乐盒", 9 | "scroll-down": "§7向下滚动", 10 | "scroll-up": "§7向上滚动", 11 | "scroll-left": "§7向左滚动", 12 | "scroll-right": "向右滚动", 13 | "next-page": "§7下一页", 14 | "prev-page": "§7上一页", 15 | "no-page": "§c没有更多页面了!", 16 | "cant-scroll-further": "§c无法进一步滚动!", 17 | "disc-command-success": "§f获得 §d%disc% §f唱片!", 18 | "fragment-command-success": "§fObtained §d%amount% §ffragments of §d%disc%§f!", 19 | "disc-namespace-not-found": "§c未找到命名空间为 §6%namespace% §c唱片!", 20 | "disc-received": "§f收到 §d%disc% §f唱片!", 21 | "fragment-received": "§fReceived §d%amount% §ffragments of §d%disc%§f!", 22 | "disc-given-multiple": "§f给予了 §6%playercount% §f玩家 §6%disc% §f唱片!", 23 | "fragment-given-multiple": "§fGiven §6%amount% §ffragments of §6%disc% §fto §6%playercount% §fplayers!", 24 | "disc-given": "§f给予了§6%player% §f一张§6 %disc% §f唱片!", 25 | "fragment-given": "§fGiven §6%amount% §ffragments of §6%disc% §fto §6%player%§f!", 26 | "no-disc-given": "§fGiven disc to no player, something went wrong!", 27 | "no-fragment-given": "§fGiven fragments to no player, something went wrong!", 28 | "invalid-location": "§cInvalid location!", 29 | "wrong-number-format": "§cWrong number format for %param% parameter!", 30 | "cannot-find-player": "§cCannot find player: §6%player%§c.", 31 | "music-now-playing": "§fMusic $d%name% §fis now playing!", 32 | "played-music-to-multiple": "§fPlayed music §6%name% §fto §6%playercount% §fplayers!", 33 | "played-music-to": "§fPlayed music §6%name% §fto §6%player%§f!", 34 | "played-music-to-no-one": "§fPlayed music to no player, something went wrong!", 35 | "stopped-music": "§f已停止音乐§d %name%!", 36 | "stopped-all-music": "§f已停止所有音乐!", 37 | "stopped-music-for-multiple": "§fStopped music for §6%playercount% §fplayers!", 38 | "stopped-music-for": "§fStopped music for §6%player%§f!", 39 | "stopped-music-for-no-one": "§fStopped music for no player, something went wrong!", 40 | "export-start": "§fExporting resourcepack to §6%file%§f...", 41 | "export-success": "§fResourcepack exported to §6%file%§f!", 42 | "export-fail": "§cResourcepack export failed!", 43 | "reloaded": "§c重载完毕!", 44 | "error-missing-permission": "§cYou don't have permission to use this command!", 45 | "error-malformed-command": "§cMalformed command: expected '§e%expected%§c' arguments, got '§e%got%§c'.", 46 | "error-malformed-argument": "cMalformed argument: expected '§e%expected%§c' at '§e%at%§c'.", 47 | "error-insufficient-consumes": "§cError: Expected at least §e'%expected%'§c arguments at §e'%at%'§c, got '§e%got%§c'.", 48 | "error-invalid-sender-at": "§cError: Invalid sender '§e%sender%§c' at §e'%at%'§c'.", 49 | "error-no-players": "Error: No players found", 50 | "error-no-entities": "Error: No entities found", 51 | "error-invalid-sender": "§cThis command can only be run by §6%runner%§c.", 52 | "command-not-supported": "§cThe command §6%command% §cis not supported: §6%reason%§c.", 53 | "webui": "§a网页用户界面可在 %url% 处访问", 54 | "webui-disabled": "§c Web UI 已禁用!", 55 | "usage": "[使用]: §6/%command%", 56 | "playat-command-success": "§fPlaying music §d%disc% §fat location §6%location%§f in world §6%world%§f!" 57 | } 58 | -------------------------------------------------------------------------------- /src/main/resources/langs/ar_SA.json: -------------------------------------------------------------------------------- 1 | { 2 | "now-playing": "القسم 6 الآن يلعب: §a%name%", 3 | "disc-name": "§a%author% §7- §a%title%", 4 | "disc-name-simple": "§a%title%", 5 | "update-available": "يوجد تحديث جديد!", 6 | "resource-pack-decline-kick-message": "الرجاء تمكين حزمة الموارد للموسيقى!", 7 | "failed-download-kick-message": "فشل تنزيل حزمة الموارد، يرجى إعادة الانضمام للمحاولة مرة أخرى.", 8 | "jukebox": "الفقرة 9Jkebox", 9 | "scroll-down": "الفقرة 7 التمرير لأسفل", 10 | "scroll-up": "الفقرة 7 التمرير لأعلى", 11 | "scroll-left": "الفقرة 7 التمرير لليسار", 12 | "scroll-right": "الفقرة 7التمرير الأيمن", 13 | "next-page": "الفقرة 7", 14 | "prev-page": "الفقرة 7", 15 | "no-page": "§cلا يوجد المزيد من الصفحات!", 16 | "cant-scroll-further": "لا يمكن التمرير أكثر!", 17 | "disc-command-success": "§fObObd%disc% §fdisc!", 18 | "fragment-command-success": "§fObobd%amount% §ffragments of §d%disc%§f!", 19 | "disc-namespace-not-found": "§cdisc مع مساحة الاسم §6 لم يتم العثور على%namespace% §ccc!", 20 | "disc-received": "القسم المستلم%disc% §fdisc!", 21 | "fragment-received": "القسم المستلم%amount% §ffragments of §d%disc%§f!", 22 | "disc-given-multiple": "§fGiven §6%disc% §fdisc إلى §6%playercount% §fplayers!", 23 | "fragment-given-multiple": "§fGiven §6%amount% §ffragments of §6%disc% §fto §6%playercount% §fplayers!", 24 | "disc-given": "§fGiven §6%disc% §fdisc إلى §6%player%§f!", 25 | "fragment-given": "§fGiven §6%amount% §ffragments of §6%disc% §fto §6%player%§f!", 26 | "no-disc-given": "قرص بدون لاعب، حدث خطأ ما!", 27 | "no-fragment-given": "§fmens أجزاء إلى لا لاعب, حدث خطأ ما!", 28 | "invalid-location": "§ca موقع غير صالح!", 29 | "wrong-number-format": "§cWnumber تنسيق رقم خاطئ للمعلمة %param%!", 30 | "cannot-find-player": "لا يمكن العثور على اللاعب: §6%player%§c.", 31 | "music-now-playing": "§fMusic $d%name% §fis يلعب الآن!", 32 | "played-music-to-multiple": "§fPlay Music §6%name% §fto §6%playercount% §fplayers!", 33 | "played-music-to": "§fPlay Music §6%name% §fto §6%player%§f!", 34 | "played-music-to-no-one": "§fPPlay موسيقى بدون لاعب، حدث خطأ ما!", 35 | "stopped-music": "§fStoped موسيقى يطابق%name%§f!", 36 | "stopped-all-music": "توقفت جميع الموسيقى!", 37 | "stopped-music-for-multiple": "§fتوقفت الموسيقى ل §6%playercount% §fplayers!", 38 | "stopped-music-for": "§fتوقفت الموسيقى ل §6%player%§f!", 39 | "stopped-music-for-no-one": "§fStopموسيقى لا لاعب ، حدث خطأ ما!", 40 | "export-start": "§fتصدير الموارد إلى §6%file%§f...", 41 | "export-success": "§fResourcepack صدّر إلى §6%file%§f!", 42 | "export-fail": "فشل تصدير §cResourcepack!", 43 | "reloaded": "باسكويلد!", 44 | "error-missing-permission": "§ ليس لديك إذن لاستخدام هذا الأمر!", 45 | "error-malformed-command": "§cMalform: الحجج المتوقعة '§e%expected%§c' ، حصلت على '§e%got%§c'.", 46 | "error-malformed-argument": "cMalformed argument: expected '§e%expected%§c' at '§e%at%§c'.", 47 | "error-insufficient-consumes": "§cError: توقّعت على الأقل §e'%expected%'§c الحجج عند §e'%at%'§c، حصلت على '§e%got%§c'.", 48 | "error-invalid-sender-at": "§cError: المرسل غير صحيح '§e%sender%§c' على §e'%at%'§c'.", 49 | "error-no-players": "خطأ: لم يتم العثور على لاعبين", 50 | "error-no-entities": "خطأ: لم يتم العثور على كيانات", 51 | "error-invalid-sender": "§cهذا الأمر يمكن تشغيله فقط بواسطة §6%runner%§c.", 52 | "command-not-supported": "§cالأمر §6%command% §cis غير مدعوم: §6%reason%§c.", 53 | "webui": "§aWeb واجهة المستخدم متوفرة في §2%url%", 54 | "webui-disabled": "§cواجهة المستخدم معطلة حاليًا!", 55 | "usage": "", 56 | "playat-command-success": "§fPplay music §d%disc% §fat site §6%location%§f في World §6%world%§f!" 57 | } 58 | -------------------------------------------------------------------------------- /src/main/resources/langs/hu_HU.json: -------------------------------------------------------------------------------- 1 | { 2 | "now-playing": "§6Most szól:: §a%name%", 3 | "disc-name": "§a%author% §7- §a%title%", 4 | "disc-name-simple": "§a%title%", 5 | "update-available": "Új frissítés érhető el!", 6 | "resource-pack-decline-kick-message": "Engedélyezd a resource packet a zenéhez!", 7 | "failed-download-kick-message": "Resource pack letöltése sikertelen, csatlakozz újra.", 8 | "jukebox": "§9Zenedoboz", 9 | "scroll-down": "§7Legörgetés", 10 | "scroll-up": "§7Felfelegörgetés", 11 | "scroll-left": "§7Balragörgetés", 12 | "scroll-right": "§7Jobbragörgetés", 13 | "next-page": "§7Következő oldal", 14 | "prev-page": "§7Előző oldal", 15 | "no-page": "§cNincs több oldal!", 16 | "cant-scroll-further": "§cNem tudsz tovább görgetni!", 17 | "disc-command-success": "§d%disc% §fmegszerezve!", 18 | "fragment-command-success": "§fObtained §d%amount% §ffragments of §d%disc%§f!", 19 | "disc-namespace-not-found": "§cNem található §6%namespace% §cnevű lemez!", 20 | "disc-received": "§fReceived §d%disc% §fdisc!", 21 | "fragment-received": "§fReceived §d%amount% §ffragments of §d%disc%§f!", 22 | "disc-given-multiple": "§fGiven §6%disc% §fdisc to §6%playercount% §fplayers!", 23 | "fragment-given-multiple": "§fGiven §6%amount% §ffragments of §6%disc% §fto §6%playercount% §fplayers!", 24 | "disc-given": "§fGiven §6%disc% §fdisc to §6%player%§f!", 25 | "fragment-given": "§fGiven §6%amount% §ffragments of §6%disc% §fto §6%player%§f!", 26 | "no-disc-given": "§fGiven disc to no player, something went wrong!", 27 | "no-fragment-given": "§fGiven fragments to no player, something went wrong!", 28 | "invalid-location": "§cInvalid location!", 29 | "wrong-number-format": "§cWrong number format for %param% parameter!", 30 | "cannot-find-player": "§cCannot find player: §6%player%§c.", 31 | "music-now-playing": "§fMusic $d%name% §fis now playing!", 32 | "played-music-to-multiple": "§fPlayed music §6%name% §fto §6%playercount% §fplayers!", 33 | "played-music-to": "§fPlayed music §6%name% §fto §6%player%§f!", 34 | "played-music-to-no-one": "§fPlayed music to no player, something went wrong!", 35 | "stopped-music": "§fStopped music §d%name%§f!", 36 | "stopped-all-music": "§fStopped all music!", 37 | "stopped-music-for-multiple": "§fStopped music for §6%playercount% §fplayers!", 38 | "stopped-music-for": "§fStopped music for §6%player%§f!", 39 | "stopped-music-for-no-one": "§fStopped music for no player, something went wrong!", 40 | "export-start": "§fExporting resourcepack to §6%file%§f...", 41 | "export-success": "§fResourcepack exported to §6%file%§f!", 42 | "export-fail": "§cResourcepack export failed!", 43 | "reloaded": "§cReloaded!", 44 | "error-missing-permission": "§cYou don't have permission to use this command!", 45 | "error-malformed-command": "§cMalformed command: expected '§e%expected%§c' arguments, got '§e%got%§c'.", 46 | "error-malformed-argument": "cMalformed argument: expected '§e%expected%§c' at '§e%at%§c'.", 47 | "error-insufficient-consumes": "§cError: Expected at least §e'%expected%'§c arguments at §e'%at%'§c, got '§e%got%§c'.", 48 | "error-invalid-sender-at": "§cError: Invalid sender '§e%sender%§c' at §e'%at%'§c'.", 49 | "error-no-players": "Error: No players found", 50 | "error-no-entities": "Error: No entities found", 51 | "error-invalid-sender": "§cThis command can only be run by §6%runner%§c.", 52 | "command-not-supported": "§cThe command §6%command% §cis not supported: §6%reason%§c.", 53 | "webui": "§aWeb UI available at §2%url%", 54 | "webui-disabled": "A Web UI jelenleg inaktív!", 55 | "usage": "[Usage]: §6/%command%", 56 | "playat-command-success": "§fPlaying music §d%disc% §fat location §6%location%§f in world §6%world%§f!" 57 | } 58 | -------------------------------------------------------------------------------- /src/main/resources/langs/he_IL.json: -------------------------------------------------------------------------------- 1 | { 2 | "now-playing": "§6עכשיו מתנגן: §a%name%", 3 | "disc-name": "§a%author% §7- §a%title%", 4 | "disc-name-simple": "§a%title%", 5 | "update-available": "עדכון חדש זמין!", 6 | "resource-pack-decline-kick-message": "אנא הפעל את חבילת המשאבים עבור המוזיקה!", 7 | "failed-download-kick-message": "Resource pack download failed, please re-join to try again.", 8 | "jukebox": "§9Jukebox", 9 | "scroll-down": "§7Scroll down", 10 | "scroll-up": "§7Scroll up", 11 | "scroll-left": "§7Scroll left", 12 | "scroll-right": "§7Scroll right", 13 | "next-page": "§7עמוד הבא", 14 | "prev-page": "§7לעמוד הקודם", 15 | "no-page": "§cThere aren't any more pages!", 16 | "cant-scroll-further": "§cCan't scroll further!", 17 | "disc-command-success": "§fObtained §d%disc% §fdisc!", 18 | "fragment-command-success": "§fObtained §d%amount% §ffragments of §d%disc%§f!", 19 | "disc-namespace-not-found": "§cDisc with namespace §6%namespace% §ccouldn't be found!", 20 | "disc-received": "§fReceived §d%disc% §fdisc!", 21 | "fragment-received": "§fReceived §d%amount% §ffragments of §d%disc%§f!", 22 | "disc-given-multiple": "§fGiven §6%disc% §fdisc to §6%playercount% §fplayers!", 23 | "fragment-given-multiple": "§fGiven §6%amount% §ffragments of §6%disc% §fto §6%playercount% §fplayers!", 24 | "disc-given": "§fGiven §6%disc% §fdisc to §6%player%§f!", 25 | "fragment-given": "§fGiven §6%amount% §ffragments of §6%disc% §fto §6%player%§f!", 26 | "no-disc-given": "§fGiven disc to no player, something went wrong!", 27 | "no-fragment-given": "§fGiven fragments to no player, something went wrong!", 28 | "invalid-location": "§cמיקום לא תקין!", 29 | "wrong-number-format": "§cWrong number format for %param% parameter!", 30 | "cannot-find-player": "§cCannot find player: §6%player%§c.", 31 | "music-now-playing": "§fMusic $d%name% §fis now playing!", 32 | "played-music-to-multiple": "§fPlayed music §6%name% §fto §6%playercount% §fplayers!", 33 | "played-music-to": "§fPlayed music §6%name% §fto §6%player%§f!", 34 | "played-music-to-no-one": "§fPlayed music to no player, something went wrong!", 35 | "stopped-music": "§fStopped music §d%name%§f!", 36 | "stopped-all-music": "§fStopped all music!", 37 | "stopped-music-for-multiple": "§fStopped music for §6%playercount% §fplayers!", 38 | "stopped-music-for": "§fStopped music for §6%player%§f!", 39 | "stopped-music-for-no-one": "§fStopped music for no player, something went wrong!", 40 | "export-start": "§fExporting resourcepack to §6%file%§f...", 41 | "export-success": "§fResourcepack exported to §6%file%§f!", 42 | "export-fail": "§cResourcepack export failed!", 43 | "reloaded": "§cReloaded!", 44 | "error-missing-permission": "§cYou don't have permission to use this command!", 45 | "error-malformed-command": "§cMalformed command: expected '§e%expected%§c' arguments, got '§e%got%§c'.", 46 | "error-malformed-argument": "cMalformed argument: expected '§e%expected%§c' at '§e%at%§c'.", 47 | "error-insufficient-consumes": "§cError: Expected at least §e'%expected%'§c arguments at §e'%at%'§c, got '§e%got%§c'.", 48 | "error-invalid-sender-at": "§cError: Invalid sender '§e%sender%§c' at §e'%at%'§c'.", 49 | "error-no-players": "Error: No players found", 50 | "error-no-entities": "Error: No entities found", 51 | "error-invalid-sender": "§cThis command can only be run by §6%runner%§c.", 52 | "command-not-supported": "§cThe command §6%command% §cis not supported: §6%reason%§c.", 53 | "webui": "§aWeb UI available at §2%url%", 54 | "webui-disabled": "§cThe Web UI is currently disabled!", 55 | "usage": "[Usage]: §6/%command%", 56 | "playat-command-success": "§fPlaying music §d%disc% §fat location §6%location%§f in world §6%world%§f!" 57 | } 58 | -------------------------------------------------------------------------------- /src/main/resources/langs/af_ZA.json: -------------------------------------------------------------------------------- 1 | { 2 | "now-playing": "§6Now playing: §a%name%", 3 | "disc-name": "§a%author% §7- §a%title%", 4 | "disc-name-simple": "§a%title%", 5 | "update-available": "A new update is available!", 6 | "resource-pack-decline-kick-message": "Please enable resource pack for the music!", 7 | "failed-download-kick-message": "Resource pack download failed, please re-join to try again.", 8 | "jukebox": "§9Jukebox", 9 | "scroll-down": "§7Scroll down", 10 | "scroll-up": "§7Scroll up", 11 | "scroll-left": "§7Scroll left", 12 | "scroll-right": "§7Scroll right", 13 | "next-page": "§7Next page", 14 | "prev-page": "§7Previous page", 15 | "no-page": "§cThere aren't any more pages!", 16 | "cant-scroll-further": "§cCan't scroll further!", 17 | "disc-command-success": "§fObtained §d%disc% §fdisc!", 18 | "fragment-command-success": "§fObtained §d%amount% §ffragments of §d%disc%§f!", 19 | "disc-namespace-not-found": "§cDisc with namespace §6%namespace% §ccouldn't be found!", 20 | "disc-received": "§fReceived §d%disc% §fdisc!", 21 | "fragment-received": "§fReceived §d%amount% §ffragments of §d%disc%§f!", 22 | "disc-given-multiple": "§fGiven §6%disc% §fdisc to §6%playercount% §fplayers!", 23 | "fragment-given-multiple": "§fGiven §6%amount% §ffragments of §6%disc% §fto §6%playercount% §fplayers!", 24 | "disc-given": "§fGiven §6%disc% §fdisc to §6%player%§f!", 25 | "fragment-given": "§fGiven §6%amount% §ffragments of §6%disc% §fto §6%player%§f!", 26 | "no-disc-given": "§fGiven disc to no player, something went wrong!", 27 | "no-fragment-given": "§fGiven fragments to no player, something went wrong!", 28 | "invalid-location": "§cInvalid location!", 29 | "wrong-number-format": "§cWrong number format for %param% parameter!", 30 | "cannot-find-player": "§cCannot find player: §6%player%§c.", 31 | "music-now-playing": "§fMusic $d%name% §fis now playing!", 32 | "played-music-to-multiple": "§fPlayed music §6%name% §fto §6%playercount% §fplayers!", 33 | "played-music-to": "§fPlayed music §6%name% §fto §6%player%§f!", 34 | "played-music-to-no-one": "§fPlayed music to no player, something went wrong!", 35 | "stopped-music": "§fStopped music §d%name%§f!", 36 | "stopped-all-music": "§fStopped all music!", 37 | "stopped-music-for-multiple": "§fStopped music for §6%playercount% §fplayers!", 38 | "stopped-music-for": "§fStopped music for §6%player%§f!", 39 | "stopped-music-for-no-one": "§fStopped music for no player, something went wrong!", 40 | "export-start": "§fExporting resourcepack to §6%file%§f...", 41 | "export-success": "§fResourcepack exported to §6%file%§f!", 42 | "export-fail": "§cResourcepack export failed!", 43 | "reloaded": "§cReloaded!", 44 | "error-missing-permission": "§cYou don't have permission to use this command!", 45 | "error-malformed-command": "§cMalformed command: expected '§e%expected%§c' arguments, got '§e%got%§c'.", 46 | "error-malformed-argument": "cMalformed argument: expected '§e%expected%§c' at '§e%at%§c'.", 47 | "error-insufficient-consumes": "§cError: Expected at least §e'%expected%'§c arguments at §e'%at%'§c, got '§e%got%§c'.", 48 | "error-invalid-sender-at": "§cError: Invalid sender '§e%sender%§c' at §e'%at%'§c'.", 49 | "error-no-players": "Error: No players found", 50 | "error-no-entities": "Error: No entities found", 51 | "error-invalid-sender": "§cThis command can only be run by §6%runner%§c.", 52 | "command-not-supported": "§cThe command §6%command% §cis not supported: §6%reason%§c.", 53 | "webui": "§aWeb UI available at §2%url%", 54 | "webui-disabled": "§cThe Web UI is currently disabled!", 55 | "usage": "[Usage]: §6/%command%", 56 | "playat-command-success": "§fPlaying music §d%disc% §fat location §6%location%§f in world §6%world%§f!" 57 | } 58 | -------------------------------------------------------------------------------- /src/main/resources/langs/ca_ES.json: -------------------------------------------------------------------------------- 1 | { 2 | "now-playing": "§6Now playing: §a%name%", 3 | "disc-name": "§a%author% §7- §a%title%", 4 | "disc-name-simple": "§a%title%", 5 | "update-available": "A new update is available!", 6 | "resource-pack-decline-kick-message": "Please enable resource pack for the music!", 7 | "failed-download-kick-message": "Resource pack download failed, please re-join to try again.", 8 | "jukebox": "§9Jukebox", 9 | "scroll-down": "§7Scroll down", 10 | "scroll-up": "§7Scroll up", 11 | "scroll-left": "§7Scroll left", 12 | "scroll-right": "§7Scroll right", 13 | "next-page": "§7Next page", 14 | "prev-page": "§7Previous page", 15 | "no-page": "§cThere aren't any more pages!", 16 | "cant-scroll-further": "§cCan't scroll further!", 17 | "disc-command-success": "§fObtained §d%disc% §fdisc!", 18 | "fragment-command-success": "§fObtained §d%amount% §ffragments of §d%disc%§f!", 19 | "disc-namespace-not-found": "§cDisc with namespace §6%namespace% §ccouldn't be found!", 20 | "disc-received": "§fReceived §d%disc% §fdisc!", 21 | "fragment-received": "§fReceived §d%amount% §ffragments of §d%disc%§f!", 22 | "disc-given-multiple": "§fGiven §6%disc% §fdisc to §6%playercount% §fplayers!", 23 | "fragment-given-multiple": "§fGiven §6%amount% §ffragments of §6%disc% §fto §6%playercount% §fplayers!", 24 | "disc-given": "§fGiven §6%disc% §fdisc to §6%player%§f!", 25 | "fragment-given": "§fGiven §6%amount% §ffragments of §6%disc% §fto §6%player%§f!", 26 | "no-disc-given": "§fGiven disc to no player, something went wrong!", 27 | "no-fragment-given": "§fGiven fragments to no player, something went wrong!", 28 | "invalid-location": "§cInvalid location!", 29 | "wrong-number-format": "§cWrong number format for %param% parameter!", 30 | "cannot-find-player": "§cCannot find player: §6%player%§c.", 31 | "music-now-playing": "§fMusic $d%name% §fis now playing!", 32 | "played-music-to-multiple": "§fPlayed music §6%name% §fto §6%playercount% §fplayers!", 33 | "played-music-to": "§fPlayed music §6%name% §fto §6%player%§f!", 34 | "played-music-to-no-one": "§fPlayed music to no player, something went wrong!", 35 | "stopped-music": "§fStopped music §d%name%§f!", 36 | "stopped-all-music": "§fStopped all music!", 37 | "stopped-music-for-multiple": "§fStopped music for §6%playercount% §fplayers!", 38 | "stopped-music-for": "§fStopped music for §6%player%§f!", 39 | "stopped-music-for-no-one": "§fStopped music for no player, something went wrong!", 40 | "export-start": "§fExporting resourcepack to §6%file%§f...", 41 | "export-success": "§fResourcepack exported to §6%file%§f!", 42 | "export-fail": "§cResourcepack export failed!", 43 | "reloaded": "§cReloaded!", 44 | "error-missing-permission": "§cYou don't have permission to use this command!", 45 | "error-malformed-command": "§cMalformed command: expected '§e%expected%§c' arguments, got '§e%got%§c'.", 46 | "error-malformed-argument": "cMalformed argument: expected '§e%expected%§c' at '§e%at%§c'.", 47 | "error-insufficient-consumes": "§cError: Expected at least §e'%expected%'§c arguments at §e'%at%'§c, got '§e%got%§c'.", 48 | "error-invalid-sender-at": "§cError: Invalid sender '§e%sender%§c' at §e'%at%'§c'.", 49 | "error-no-players": "Error: No players found", 50 | "error-no-entities": "Error: No entities found", 51 | "error-invalid-sender": "§cThis command can only be run by §6%runner%§c.", 52 | "command-not-supported": "§cThe command §6%command% §cis not supported: §6%reason%§c.", 53 | "webui": "§aWeb UI available at §2%url%", 54 | "webui-disabled": "§cThe Web UI is currently disabled!", 55 | "usage": "[Usage]: §6/%command%", 56 | "playat-command-success": "§fPlaying music §d%disc% §fat location §6%location%§f in world §6%world%§f!" 57 | } 58 | -------------------------------------------------------------------------------- /src/main/resources/langs/en_US.json: -------------------------------------------------------------------------------- 1 | { 2 | "now-playing": "§6Now playing: §a%name%", 3 | "disc-name": "§a%author% §7- §a%title%", 4 | "disc-name-simple": "§a%title%", 5 | "update-available": "A new update is available!", 6 | "resource-pack-decline-kick-message": "Please enable resource pack for the music!", 7 | "failed-download-kick-message": "Resource pack download failed, please re-join to try again.", 8 | "jukebox": "§9Jukebox", 9 | "scroll-down": "§7Scroll down", 10 | "scroll-up": "§7Scroll up", 11 | "scroll-left": "§7Scroll left", 12 | "scroll-right": "§7Scroll right", 13 | "next-page": "§7Next page", 14 | "prev-page": "§7Previous page", 15 | "no-page": "§cThere aren't any more pages!", 16 | "cant-scroll-further": "§cCan't scroll further!", 17 | "disc-command-success": "§fObtained §d%disc% §fdisc!", 18 | "fragment-command-success": "§fObtained §d%amount% §ffragments of §d%disc%§f!", 19 | "disc-namespace-not-found": "§cDisc with namespace §6%namespace% §ccouldn't be found!", 20 | "disc-received": "§fReceived §d%disc% §fdisc!", 21 | "fragment-received": "§fReceived §d%amount% §ffragments of §d%disc%§f!", 22 | "disc-given-multiple": "§fGiven §6%disc% §fdisc to §6%playercount% §fplayers!", 23 | "fragment-given-multiple": "§fGiven §6%amount% §ffragments of §6%disc% §fto §6%playercount% §fplayers!", 24 | "disc-given": "§fGiven §6%disc% §fdisc to §6%player%§f!", 25 | "fragment-given": "§fGiven §6%amount% §ffragments of §6%disc% §fto §6%player%§f!", 26 | "no-disc-given": "§fGiven disc to no player, something went wrong!", 27 | "no-fragment-given": "§fGiven fragments to no player, something went wrong!", 28 | "invalid-location": "§cInvalid location!", 29 | "wrong-number-format": "§cWrong number format for %param% parameter!", 30 | "cannot-find-player": "§cCannot find player: §6%player%§c.", 31 | "music-now-playing": "§fMusic $d%name% §fis now playing!", 32 | "played-music-to-multiple": "§fPlayed music §6%name% §fto §6%playercount% §fplayers!", 33 | "played-music-to": "§fPlayed music §6%name% §fto §6%player%§f!", 34 | "played-music-to-no-one": "§fPlayed music to no player, something went wrong!", 35 | "stopped-music": "§fStopped music §d%name%§f!", 36 | "stopped-all-music": "§fStopped all music!", 37 | "stopped-music-for-multiple": "§fStopped music for §6%playercount% §fplayers!", 38 | "stopped-music-for": "§fStopped music for §6%player%§f!", 39 | "stopped-music-for-no-one": "§fStopped music for no player, something went wrong!", 40 | "export-start": "§fExporting resourcepack to §6%file%§f...", 41 | "export-success": "§fResourcepack exported to §6%file%§f!", 42 | "export-fail": "§cResourcepack export failed!", 43 | "reloaded": "§cReloaded!", 44 | "error-missing-permission": "§cYou don't have permission to use this command!", 45 | "error-malformed-command": "§cMalformed command: expected '§e%expected%§c' arguments, got '§e%got%§c'.", 46 | "error-malformed-argument": "cMalformed argument: expected '§e%expected%§c' at '§e%at%§c'.", 47 | "error-insufficient-consumes": "§cError: Expected at least §e'%expected%'§c arguments at §e'%at%'§c, got '§e%got%§c'.", 48 | "error-invalid-sender-at": "§cError: Invalid sender '§e%sender%§c' at §e'%at%'§c'.", 49 | "error-no-players": "Error: No players found", 50 | "error-no-entities": "Error: No entities found", 51 | "error-invalid-sender": "§cThis command can only be run by §6%runner%§c.", 52 | "command-not-supported": "§cThe command §6%command% §cis not supported: §6%reason%§c.", 53 | "webui": "§aWeb UI available at §2%url%", 54 | "webui-disabled": "§cThe Web UI is currently disabled!", 55 | "usage": "[Usage]: §6/%command%", 56 | "playat-command-success": "§fPlaying music §d%disc% §fat location §6%location%§f in world §6%world%§f!" 57 | } 58 | -------------------------------------------------------------------------------- /src/main/resources/langs/sr_SP.json: -------------------------------------------------------------------------------- 1 | { 2 | "now-playing": "§6Now playing: §a%name%", 3 | "disc-name": "§a%author% §7- §a%title%", 4 | "disc-name-simple": "§a%title%", 5 | "update-available": "A new update is available!", 6 | "resource-pack-decline-kick-message": "Please enable resource pack for the music!", 7 | "failed-download-kick-message": "Resource pack download failed, please re-join to try again.", 8 | "jukebox": "§9Jukebox", 9 | "scroll-down": "§7Scroll down", 10 | "scroll-up": "§7Scroll up", 11 | "scroll-left": "§7Scroll left", 12 | "scroll-right": "§7Scroll right", 13 | "next-page": "§7Next page", 14 | "prev-page": "§7Previous page", 15 | "no-page": "§cThere aren't any more pages!", 16 | "cant-scroll-further": "§cCan't scroll further!", 17 | "disc-command-success": "§fObtained §d%disc% §fdisc!", 18 | "fragment-command-success": "§fObtained §d%amount% §ffragments of §d%disc%§f!", 19 | "disc-namespace-not-found": "§cDisc with namespace §6%namespace% §ccouldn't be found!", 20 | "disc-received": "§fReceived §d%disc% §fdisc!", 21 | "fragment-received": "§fReceived §d%amount% §ffragments of §d%disc%§f!", 22 | "disc-given-multiple": "§fGiven §6%disc% §fdisc to §6%playercount% §fplayers!", 23 | "fragment-given-multiple": "§fGiven §6%amount% §ffragments of §6%disc% §fto §6%playercount% §fplayers!", 24 | "disc-given": "§fGiven §6%disc% §fdisc to §6%player%§f!", 25 | "fragment-given": "§fGiven §6%amount% §ffragments of §6%disc% §fto §6%player%§f!", 26 | "no-disc-given": "§fGiven disc to no player, something went wrong!", 27 | "no-fragment-given": "§fGiven fragments to no player, something went wrong!", 28 | "invalid-location": "§cInvalid location!", 29 | "wrong-number-format": "§cWrong number format for %param% parameter!", 30 | "cannot-find-player": "§cCannot find player: §6%player%§c.", 31 | "music-now-playing": "§fMusic $d%name% §fis now playing!", 32 | "played-music-to-multiple": "§fPlayed music §6%name% §fto §6%playercount% §fplayers!", 33 | "played-music-to": "§fPlayed music §6%name% §fto §6%player%§f!", 34 | "played-music-to-no-one": "§fPlayed music to no player, something went wrong!", 35 | "stopped-music": "§fStopped music §d%name%§f!", 36 | "stopped-all-music": "§fStopped all music!", 37 | "stopped-music-for-multiple": "§fStopped music for §6%playercount% §fplayers!", 38 | "stopped-music-for": "§fStopped music for §6%player%§f!", 39 | "stopped-music-for-no-one": "§fStopped music for no player, something went wrong!", 40 | "export-start": "§fExporting resourcepack to §6%file%§f...", 41 | "export-success": "§fResourcepack exported to §6%file%§f!", 42 | "export-fail": "§cResourcepack export failed!", 43 | "reloaded": "§cReloaded!", 44 | "error-missing-permission": "§cYou don't have permission to use this command!", 45 | "error-malformed-command": "§cMalformed command: expected '§e%expected%§c' arguments, got '§e%got%§c'.", 46 | "error-malformed-argument": "cMalformed argument: expected '§e%expected%§c' at '§e%at%§c'.", 47 | "error-insufficient-consumes": "§cError: Expected at least §e'%expected%'§c arguments at §e'%at%'§c, got '§e%got%§c'.", 48 | "error-invalid-sender-at": "§cError: Invalid sender '§e%sender%§c' at §e'%at%'§c'.", 49 | "error-no-players": "Error: No players found", 50 | "error-no-entities": "Error: No entities found", 51 | "error-invalid-sender": "§cThis command can only be run by §6%runner%§c.", 52 | "command-not-supported": "§cThe command §6%command% §cis not supported: §6%reason%§c.", 53 | "webui": "§aWeb UI available at §2%url%", 54 | "webui-disabled": "§cThe Web UI is currently disabled!", 55 | "usage": "[Usage]: §6/%command%", 56 | "playat-command-success": "§fPlaying music §d%disc% §fat location §6%location%§f in world §6%world%§f!" 57 | } 58 | -------------------------------------------------------------------------------- /src/main/resources/langs/cs_CZ.json: -------------------------------------------------------------------------------- 1 | { 2 | "now-playing": "§6Právě hraje: §a%name%", 3 | "disc-name": "§a%author% §7- §a%title%", 4 | "disc-name-simple": "§a%title%", 5 | "update-available": "Je k dispozici nová aktualizace!", 6 | "resource-pack-decline-kick-message": "Prosím povolte balíček zdrojů pro hudbu!", 7 | "failed-download-kick-message": "Stahování balíčku dokumentů se nezdařilo, zkuste to prosím znovu.", 8 | "jukebox": "§9Hráč", 9 | "scroll-down": "§7Posunout dolů", 10 | "scroll-up": "§7Posunout nahoru", 11 | "scroll-left": "§7Posunout vlevo", 12 | "scroll-right": "§7Posunout vpravo", 13 | "next-page": "§7Další stránka", 14 | "prev-page": "§7Předchozí stránka", 15 | "no-page": "§cNeexistují žádné další stránky!", 16 | "cant-scroll-further": "§cNelze posunout dále!", 17 | "disc-command-success": "§fZískáno §d%disc% §fdisc!", 18 | "fragment-command-success": "§fZískáno §d%amount% §ffragmentů §d%disc%§f!", 19 | "disc-namespace-not-found": "§cDisk s jmenným prostorem §6%namespace% nebyl nalezen!", 20 | "disc-received": "§fObdrženo §d%disc% §fdisku!", 21 | "fragment-received": "§fObdržel §d%amount% §ffragů §d%disc%§f!", 22 | "disc-given-multiple": "§fDaný §6%disc% §fdisk §6%playercount% §fplayers!", 23 | "fragment-given-multiple": "§fDaný §6%amount% §ffragmenty §6%disc% §fto §6%playercount% §fplayers!", 24 | "disc-given": "§fDaný §6%disc% §fdisk §6%player%§f!", 25 | "fragment-given": "§fDaný §6%amount% §ffragmenty §6%disc% §fto §6%player%§f!", 26 | "no-disc-given": "§fDaný disk žádnému hráči, něco se pokazilo!", 27 | "no-fragment-given": "§fDaný fragmenty žádnému hráči, něco se pokazilo!", 28 | "invalid-location": "§cNeplatná poloha!", 29 | "wrong-number-format": "§cNesprávný formát čísla pro parametr %param%!", 30 | "cannot-find-player": "§cHrac nenalezen: §6%player%§c.", 31 | "music-now-playing": "§fHudba $d%name% §fis hraje!", 32 | "played-music-to-multiple": "§fHrana hudba §6%name% §fto §6%playercount% §fplayers!", 33 | "played-music-to": "§fHrana hudba §6%name% §fto §6%player%§f!", 34 | "played-music-to-no-one": "§fHrál hudbu bez hráče, něco se pokazilo!", 35 | "stopped-music": "§fZastavena hudba §d%name%§f!", 36 | "stopped-all-music": "§fZastavil všechnu hudbu!", 37 | "stopped-music-for-multiple": "§fZastavena hudba pro §6%playercount% §fplayers!", 38 | "stopped-music-for": "§fZastavena hudba pro §6%player%§f!", 39 | "stopped-music-for-no-one": "§fZastavena hudba pro žádného hráče, něco se pokazilo!", 40 | "export-start": "§fExport zdrojů na §6%file%§f...", 41 | "export-success": "§fResourcepack exportován do §6%file%§f!", 42 | "export-fail": "§cExport zdroje selhal!", 43 | "reloaded": "§cZnovu přidáno!", 44 | "error-missing-permission": "§cNemáš oprávnění použít tento příkaz!", 45 | "error-malformed-command": "§cPoškozen prikaz: očekával '§e%expected%§c' argumenty, dostal '§e%got%§c'.", 46 | "error-malformed-argument": "cMalformed argument: expected '§e%expected%§c' at '§e%at%§c'.", 47 | "error-insufficient-consumes": "§cChyba: Očekáván alespoň §e'%expected%'§c argumenty na §e'%at%'§c, dostal '§e%got%§c'.", 48 | "error-invalid-sender-at": "§cChyba: Neplatný odesílatel '§e%sender%§c' na §e'%at%'§c'.", 49 | "error-no-players": "Chyba: Nebyli nalezeni žádní hráči", 50 | "error-no-entities": "Chyba: Nebyly nalezeny žádné entity", 51 | "error-invalid-sender": "§cTento příkaz může být spuštěn pouze podle §6%runner%§c.", 52 | "command-not-supported": "§cPříkaz §6%command% §cnení podporován: §6%reason%§c.", 53 | "webui": "§aWebové uživatelské rozhraní dostupné na §2%url%", 54 | "webui-disabled": "§cWebové uživatelské rozhraní je momentálně zakázáno!", 55 | "usage": "[Usage]: §6/%command%", 56 | "playat-command-success": "§fPřehrávání hudby §d%disc% §f lokace%location%ve světě §6%world%§f!" 57 | } 58 | -------------------------------------------------------------------------------- /src/main/resources/langs/da_DK.json: -------------------------------------------------------------------------------- 1 | { 2 | "now-playing": "§6Spiller nu: §a%name%", 3 | "disc-name": "§a%author% §7- §a%title%", 4 | "disc-name-simple": "§a%title%", 5 | "update-available": "En ny opdatering er tilgængelig!", 6 | "resource-pack-decline-kick-message": "Aktivér venligst ressourcepakke til musikken!", 7 | "failed-download-kick-message": "Ressourcepakke download mislykkedes. Forbind igen for at prøve igen.", 8 | "jukebox": "§9Jukebox", 9 | "scroll-down": "§7Rul ned", 10 | "scroll-up": "§7Rul op", 11 | "scroll-left": "§7Rul tilbage", 12 | "scroll-right": "§7Rul til højre", 13 | "next-page": "§7Næste side", 14 | "prev-page": "§7Forrige side", 15 | "no-page": "§cDer er ikke flere sider!", 16 | "cant-scroll-further": "§cKan ikke rulle yderligere!", 17 | "disc-command-success": "§fObtained §d%disc% §fdisc!", 18 | "fragment-command-success": "§fObtained §d%amount% §ffragments of §d%disc%§f!", 19 | "disc-namespace-not-found": "§cDisc med navneområde §6%namespace% §cckunne ikke findes!", 20 | "disc-received": "§fModtaget §d%disc% §fdisc!", 21 | "fragment-received": "§fModtaget §d%amount% §ffragments of §d%disc%§f!", 22 | "disc-given-multiple": "§fGivet §6%disc% §fdisc til §6%playercount% §fplayers!", 23 | "fragment-given-multiple": "§fGivet §6%amount% §ffragments of §6%disc% §fto §6%playercount% §fplayers!", 24 | "disc-given": "§fGivet §6%disc% §fdisc til §6%player%§f!", 25 | "fragment-given": "§fGivet §6%amount% §ffragments of §6%disc% §fto §6%player%§f!", 26 | "no-disc-given": "§fGivet disk til ingen spiller, noget gik galt!", 27 | "no-fragment-given": "§fGivet fragmenter til ingen spiller, noget gik galt!", 28 | "invalid-location": "§cUgyldig placering!", 29 | "wrong-number-format": "§cForkert nummerformat for %param% parameter!", 30 | "cannot-find-player": "§cKan ikke finde spilleren: §6%player%§c.", 31 | "music-now-playing": "§fMusik $d%name% §fis playing!", 32 | "played-music-to-multiple": "§fSpillet musik §6%name% §ftil §6%playercount% §fplayers!", 33 | "played-music-to": "§fSpillet musik §6%name% §ftil §6%player%§f!", 34 | "played-music-to-no-one": "§fAfspillede musik til ingen spiller, noget gik galt!", 35 | "stopped-music": "§fStoppet musik §d%name%§f!", 36 | "stopped-all-music": "§fStoppet al musik!", 37 | "stopped-music-for-multiple": "§fStoppet musik for §6%playercount% §fplayers!", 38 | "stopped-music-for": "§fStoppet musik for §6%player%§f!", 39 | "stopped-music-for-no-one": "§fStoppet musik for ingen spiller, noget gik galt!", 40 | "export-start": "§fEksporterer ressource til §6%file%§f...", 41 | "export-success": "§fResourcepack eksporteret til §6%file%§f!", 42 | "export-fail": "§cResourcepack eksport mislykkedes!", 43 | "reloaded": "§cGenindlæst!", 44 | "error-missing-permission": "§cDu har ikke tilladelse til at bruge denne kommando!", 45 | "error-malformed-command": "§cForkert udformet kommando: forventede '§e%expected%§c' argumenter, fik '§e%got%§c'.", 46 | "error-malformed-argument": "cMalformed argument: expected '§e%expected%§c' at '§e%at%§c'.", 47 | "error-insufficient-consumes": "§cFejl: Forventede mindst §e'%expected%'§c arguments at §e'%at%'§c, fik '§e%got%§c'.", 48 | "error-invalid-sender-at": "§cFejl: Ugyldig afsender '§e%sender%§c' i §e'%at%'§c'.", 49 | "error-no-players": "Fejl: Ingen spillere fundet", 50 | "error-no-entities": "Fejl: Ingen entiteter fundet", 51 | "error-invalid-sender": "§cDenne kommando kan kun køres af §6%runner%§c.", 52 | "command-not-supported": "§cKommandoen §6%command% §cis ikke understøttet: §6%reason%§c.", 53 | "webui": "§aWeb UI tilgængelig på §2%url%", 54 | "webui-disabled": "§cThe Web UI er i øjeblikket deaktiveret!", 55 | "usage": "[Usage]: §6/%command%", 56 | "playat-command-success": "§fPlaying music §d%disc% §fat location §6%location%§f in world §6%world%§f!" 57 | } 58 | -------------------------------------------------------------------------------- /src/main/resources/langs/vi_VN.json: -------------------------------------------------------------------------------- 1 | { 2 | "now-playing": "§6Đang phát: §a%name%", 3 | "disc-name": "§a%author% §7- §a%title%", 4 | "disc-name-simple": "§a%title%", 5 | "update-available": "Một phiên bản mới có thể cập nhật!", 6 | "resource-pack-decline-kick-message": "Vui lòng bật sử dụng gói tài nguyên âm nhạc!", 7 | "failed-download-kick-message": "Gói tài nguyên tải về bị lỗi, vui lòng thử thoát ra rồi vào lại.", 8 | "jukebox": "§9Máy phát nhạc", 9 | "scroll-down": "§7Kéo xuống", 10 | "scroll-up": "§7Kéo lên", 11 | "scroll-left": "§7Kéo sang trái", 12 | "scroll-right": "§7Kéo sang phải", 13 | "next-page": "§7Trang sau", 14 | "prev-page": "§7Trang trước", 15 | "no-page": "§cKhông còn thêm trang nào!", 16 | "cant-scroll-further": "§cKhông thể kéo thêm!", 17 | "disc-command-success": "§fThu được đĩa nhạc §d%disc%§f!", 18 | "fragment-command-success": "§fObtained §d%amount% §ffragments of §d%disc%§f!", 19 | "disc-namespace-not-found": "§cĐĩa nhạc với tên §6%namespace% §ckhông thể tìm thấy!", 20 | "disc-received": "§fĐã nhận đĩa nhạc §d%disc%§f!", 21 | "fragment-received": "§fReceived §d%amount% §ffragments of §d%disc%§f!", 22 | "disc-given-multiple": "§fĐã gửi §6%disc% §fđĩa nhạc cho người chơi §6%playercount%§f!", 23 | "fragment-given-multiple": "§fGiven §6%amount% §ffragments of §6%disc% §fto §6%playercount% §fplayers!", 24 | "disc-given": "§fĐã gửi đĩa nhạc §6%disc% §fcho người chơi §6%player%§f!", 25 | "fragment-given": "§fGiven §6%amount% §ffragments of §6%disc% §fto §6%player%§f!", 26 | "no-disc-given": "§fKhông thể gửi cho ai, có gì đó sai sót!", 27 | "no-fragment-given": "§fGiven fragments to no player, something went wrong!", 28 | "invalid-location": "§cToạ độ không chính xác!", 29 | "wrong-number-format": "§cĐịnh dạng số %param% không hợp lệ!", 30 | "cannot-find-player": "§cKhông tìm thấy người chơi: §6%player%§c.", 31 | "music-now-playing": "§fBài nhạc $d%name% §fđang được chạy!", 32 | "played-music-to-multiple": "§fĐã phát bài nhạc §6%name% §fcho người chơi §6%playercount%§f!", 33 | "played-music-to": "§fĐang phát bài nhạc §6%name% §fcho §6%player%§f!", 34 | "played-music-to-no-one": "§fKhông thể phát nhạc cho ai, có gì đó sai sót!", 35 | "stopped-music": "§fĐã tạm dừng bài hát §d%name%§f!", 36 | "stopped-all-music": "§fĐã tạm dừng tất cả bài hát!", 37 | "stopped-music-for-multiple": "§fĐã tạm dừng bài hát cho §6%playercount% §fngười chơi!", 38 | "stopped-music-for": "§fĐã tạm dừng bài hát cho §d%player%§f!", 39 | "stopped-music-for-no-one": "§fKhông thể phát nhạc cho ai, có gì đó sai sót!", 40 | "export-start": "§fĐang xuất gói tài nguyên tại §6%file%§f...", 41 | "export-success": "§fGói tài nguyên đã được xuất tại §6%file%§f!", 42 | "export-fail": "§cXuất gói tài nguyên thất bại!", 43 | "reloaded": "§cĐã nạp lại!", 44 | "error-missing-permission": "§cYou don't have permission to use this command!", 45 | "error-malformed-command": "§cMalformed command: expected '§e%expected%§c' arguments, got '§e%got%§c'.", 46 | "error-malformed-argument": "cMalformed argument: expected '§e%expected%§c' at '§e%at%§c'.", 47 | "error-insufficient-consumes": "§cError: Expected at least §e'%expected%'§c arguments at §e'%at%'§c, got '§e%got%§c'.", 48 | "error-invalid-sender-at": "§cError: Invalid sender '§e%sender%§c' at §e'%at%'§c'.", 49 | "error-no-players": "Error: No players found", 50 | "error-no-entities": "Error: No entities found", 51 | "error-invalid-sender": "§cThis command can only be run by §6%runner%§c.", 52 | "command-not-supported": "§cLệnh §6%command% §ckhông được hỗ trợ: §6%reason%§c.", 53 | "webui": "§aWeb UI có sẵn tại §2%url%", 54 | "webui-disabled": "§cWeb UI đã bị tắt!", 55 | "usage": "[Usage]: §6/%command%", 56 | "playat-command-success": "§fPlaying music §d%disc% §fat location §6%location%§f in world §6%world%§f!" 57 | } 58 | -------------------------------------------------------------------------------- /src/main/resources/langs/no_NO.json: -------------------------------------------------------------------------------- 1 | { 2 | "now-playing": "§6Nå spiller: §a%name%", 3 | "disc-name": "§a%author% §7- §a%title%", 4 | "disc-name-simple": "§a%title%", 5 | "update-available": "En ny oppdatering er tilgjengelig!", 6 | "resource-pack-decline-kick-message": "Vennligst aktiver ressurspakke for musikken!", 7 | "failed-download-kick-message": "Nedlasting av ressurspakke mislyktes, vennligst bli med igjen for å prøve på nytt.", 8 | "jukebox": "§9Jukebox", 9 | "scroll-down": "§7Scroll down", 10 | "scroll-up": "§7Scroll opp", 11 | "scroll-left": "§7Scroll til venstre", 12 | "scroll-right": "§7Scroll til høyre", 13 | "next-page": "§7Neste side", 14 | "prev-page": "§7Forrige side", 15 | "no-page": "§cDet er ingen flere sider!", 16 | "cant-scroll-further": "§cKan ikke bla videre før!", 17 | "disc-command-success": "§fObservert §d%disc% §fdisc!", 18 | "fragment-command-success": "§fObserverte §d%amount% §ffragmenter av §d%disc%§f!", 19 | "disc-namespace-not-found": "§cDisk med navneområde §6%namespace% §ccoube not be funnet!", 20 | "disc-received": "§fatt §d%disc% §fdisc!", 21 | "fragment-received": "§fKvittert §d%amount% §ffragmenter av §d%disc%§f!", 22 | "disc-given-multiple": "§fGiven §6%disc% §fdisc til §6%playercount% §fplayere!", 23 | "fragment-given-multiple": "§fGiven §6%amount% §ffragments of §6%disc% §fto §6%playercount% §fplayers!", 24 | "disc-given": "§fGiven §6%disc% §fdisc til §6%player%§f!", 25 | "fragment-given": "§fGiven §6%amount% §ffragments of §6%disc% §fto §6%player%§f!", 26 | "no-disc-given": "§fGitt disk til ingen spiller, noe gikk galt!", 27 | "no-fragment-given": "§fGiven fragments to no player, something gikk galt!", 28 | "invalid-location": "§cUgyldig plassering!", 29 | "wrong-number-format": "§cFeil tallformat for %param% parameter!", 30 | "cannot-find-player": "§cKan ikke finne spiller: §6%player%§c.", 31 | "music-now-playing": "§fMusikk $d%name% §fis nå spiller!", 32 | "played-music-to-multiple": "§fPlayed music §6%name% §fto §6%playercount% §fplayers!", 33 | "played-music-to": "§fPlayed music §6%name% §fto §6%player%§f!", 34 | "played-music-to-no-one": "§fSpilt musikk til ingen spiller, noe gikk galt!", 35 | "stopped-music": "§fStoppet musikk §d%name%§f!", 36 | "stopped-all-music": "§fStoppet all musikk!", 37 | "stopped-music-for-multiple": "§fStoppet musikk for §6%playercount% §fplayers!", 38 | "stopped-music-for": "§fStoppet musikk for §6%player%§f!", 39 | "stopped-music-for-no-one": "§fStoppet musikk for ingen spiller, noe gikk galt!", 40 | "export-start": "§fEksportere εcepack til §6%file%§f...", 41 | "export-success": "§fResourcepack eksportert til §6%file%§f!", 42 | "export-fail": "Eksport av §cResourcepack mislyktes!", 43 | "reloaded": "§cOppdatert!", 44 | "error-missing-permission": "§cDu har ikke tillatelse til å bruke denne kommandoen!", 45 | "error-malformed-command": "§cFeilformet kommando: forventet '§e%expected%§c' argumenter, fikk '§e%got%§c'.", 46 | "error-malformed-argument": "cMalformed argument: expected '§e%expected%§c' at '§e%at%§c'.", 47 | "error-insufficient-consumes": "§cFeil: Forventet minst §e'%expected%'§c arguments at §e'%at%'§c, fikk '§e%got%§c'.", 48 | "error-invalid-sender-at": "§cFeil: Ugyldig avsender '§e%sender%§c' ved §e'%at%'§c'.", 49 | "error-no-players": "Feil: Ingen spillere funnet", 50 | "error-no-entities": "Feil: Ingen enheter funnet", 51 | "error-invalid-sender": "§cDenne kommandoen kan bare kjøres av §6%runner%§c.", 52 | "command-not-supported": "§cKommandoen §6%command% §cer ikke støttet: §6%reason%§c.", 53 | "webui": "§aWeb UI er tilgjengelig ved §2%url%", 54 | "webui-disabled": "§cNett-brukergrensesnittet er deaktivert!", 55 | "usage": "[Usage]: §6/%command%", 56 | "playat-command-success": "§fPlaying music §d%disc% §fat plassering §6%location%§f i verden §6%world%§f!" 57 | } 58 | -------------------------------------------------------------------------------- /src/main/resources/langs/sv_SE.json: -------------------------------------------------------------------------------- 1 | { 2 | "now-playing": "§6Spelar nu: §a%name%", 3 | "disc-name": "§a%author% §7- §a%title%", 4 | "disc-name-simple": "§a%title%", 5 | "update-available": "En ny uppdatering finns tillgänglig!", 6 | "resource-pack-decline-kick-message": "Vänligen aktivera resurspaket för musiken!", 7 | "failed-download-kick-message": "Hämtning av resurspaket misslyckades, vänligen gå med igen för att försöka igen.", 8 | "jukebox": "§9Jukebox", 9 | "scroll-down": "§7Rulla nedåt", 10 | "scroll-up": "§7Rulla upp", 11 | "scroll-left": "§7Skrolla åt vänster", 12 | "scroll-right": "§7Skrolla till höger", 13 | "next-page": "§7Nästa sida", 14 | "prev-page": "§7Föregående sida", 15 | "no-page": "§cDet finns inga fler sidor!", 16 | "cant-scroll-further": "§cKan inte bläddra vidare!", 17 | "disc-command-success": "§fErhöll §d%disc% §fskiva!", 18 | "fragment-command-success": "§fErhållen §d%amount% §ffragment av §d%disc%§f!", 19 | "disc-namespace-not-found": "§cSkiva med namnrymd §6%namespace% §ccs kunde inte hittas!", 20 | "disc-received": "§fMottaget §d%disc% §fdisc!", 21 | "fragment-received": "§fMottaget §d%amount% §ffragment av §d%disc%§f!", 22 | "disc-given-multiple": "§fGavs §6%disc% §fdisc till §6%playercount% §fplayers!", 23 | "fragment-given-multiple": "§fGivet §6%amount% §ffragment av §6%disc% §fto §6%playercount% §fplayers!", 24 | "disc-given": "§fGav §6%disc% §fdisc till §6%player%§f!", 25 | "fragment-given": "§fGivet §6%amount% §ffragment av §6%disc% §fto §6%player%§f!", 26 | "no-disc-given": "§fGiven skiva till ingen spelare, något gick fel!", 27 | "no-fragment-given": "§fGiven fragment till ingen spelare, något gick fel!", 28 | "invalid-location": "§cOgiltig plats!", 29 | "wrong-number-format": "§cFel nummerformat för parametern %param%!", 30 | "cannot-find-player": "§cKan inte hitta spelare: §6%player%§c.", 31 | "music-now-playing": "§fMusic $d%name% §fis spelar nu!", 32 | "played-music-to-multiple": "§fSpelad musik §6%name% §ftill §6%playercount% §fplayers!", 33 | "played-music-to": "§fSpelade musik §6%name% §ftill §6%player%§f!", 34 | "played-music-to-no-one": "§fSpelad musik till ingen spelare, något gick fel!", 35 | "stopped-music": "§fStoppad musik §d%name%§f!", 36 | "stopped-all-music": "§fStoppade all musik!", 37 | "stopped-music-for-multiple": "§fStoppad musik för §6%playercount% §fplayers!", 38 | "stopped-music-for": "§fStoppad musik för §6%player%§f!", 39 | "stopped-music-for-no-one": "§fStoppad musik för ingen spelare, något gick fel!", 40 | "export-start": "§fExporterar resourcepack till §6%file%§f...", 41 | "export-success": "§fResourcepack exporterad till §6%file%§f!", 42 | "export-fail": "Exporten av Resourcepack misslyckades!", 43 | "reloaded": "§cOmladdad!", 44 | "error-missing-permission": "§cDu har inte tillåtelse att använda detta kommando!", 45 | "error-malformed-command": "§cFelformaterat kommando: förväntade '§e%expected%§c' argument, fick '§e%got%§c'.", 46 | "error-malformed-argument": "cMalformed argument: expected '§e%expected%§c' at '§e%at%§c'.", 47 | "error-insufficient-consumes": "§cFel: Förväntade sig minst §'%expected%'§c arguments at §e'%at%'§c, got '§e%got%§c'.", 48 | "error-invalid-sender-at": "§cFel: Ogiltig avsändare '§e%sender%§c' på §e'%at%'§c'.", 49 | "error-no-players": "Fel: Inga spelare hittades", 50 | "error-no-entities": "Fel: Inga enheter hittades", 51 | "error-invalid-sender": "§cDet här kommandot kan bara köras av §6%runner%§c.", 52 | "command-not-supported": "§cKommandot §6%command% §cstöds inte: §6%reason%§c.", 53 | "webui": "§aWebbgränssnitt tillgängligt på §2%url%", 54 | "webui-disabled": "§cWebbgränssnittet är för närvarande inaktiverat!", 55 | "usage": "[Usage]: §6/%command%", 56 | "playat-command-success": "§fSpela upp musik §d%disc% §fat plats §6%location%§f i världen §6%world%§f!" 57 | } 58 | --------------------------------------------------------------------------------