├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yaml │ └── feature_request.yaml └── workflows │ ├── ci.yml │ └── publish-api.yml ├── .gitignore ├── LICENSE.md ├── README.md ├── api ├── build.gradle └── src │ └── main │ └── java │ └── com │ └── rexcantor64 │ └── triton │ └── api │ ├── Triton.java │ ├── TritonAPI.java │ ├── config │ ├── FeatureSyntax.java │ └── TritonConfig.java │ ├── events │ ├── PlayerChangeLanguageBungeeEvent.java │ └── PlayerChangeLanguageSpigotEvent.java │ ├── language │ ├── Language.java │ ├── LanguageManager.java │ ├── LanguageParser.java │ ├── Localized.java │ └── SignLocation.java │ ├── players │ ├── LanguagePlayer.java │ └── PlayerManager.java │ └── wrappers │ └── EntityType.java ├── build.gradle ├── config_chinese.yml ├── core ├── build.gradle └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── rexcantor64 │ │ │ └── triton │ │ │ ├── BungeeMLP.java │ │ │ ├── SpigotMLP.java │ │ │ ├── Triton.java │ │ │ ├── VelocityMLP.java │ │ │ ├── api │ │ │ └── TritonAPI.java │ │ │ ├── banners │ │ │ ├── Banner.java │ │ │ ├── Colors.java │ │ │ └── Patterns.java │ │ │ ├── bridge │ │ │ ├── BridgeSerializer.java │ │ │ ├── BungeeBridgeManager.java │ │ │ ├── SpigotBridgeManager.java │ │ │ └── VelocityBridgeManager.java │ │ │ ├── commands │ │ │ ├── DatabaseCommand.java │ │ │ ├── GetFlagCommand.java │ │ │ ├── HelpCommand.java │ │ │ ├── InfoCommand.java │ │ │ ├── LogLevelCommand.java │ │ │ ├── OpenSelectorCommand.java │ │ │ ├── ReloadCommand.java │ │ │ ├── SetLanguageCommand.java │ │ │ ├── SignCommand.java │ │ │ ├── TwinCommand.java │ │ │ └── handler │ │ │ │ ├── BungeeCommand.java │ │ │ │ ├── BungeeCommandHandler.java │ │ │ │ ├── BungeeSender.java │ │ │ │ ├── Command.java │ │ │ │ ├── CommandEvent.java │ │ │ │ ├── CommandHandler.java │ │ │ │ ├── NoPermissionException.java │ │ │ │ ├── Sender.java │ │ │ │ ├── SpigotCommandHandler.java │ │ │ │ ├── SpigotSender.java │ │ │ │ ├── VelocityCommandHandler.java │ │ │ │ └── VelocitySender.java │ │ │ ├── config │ │ │ ├── MainConfig.java │ │ │ ├── MessagesConfig.java │ │ │ └── interfaces │ │ │ │ ├── Configuration.java │ │ │ │ ├── ConfigurationProvider.java │ │ │ │ └── YamlConfiguration.java │ │ │ ├── guiapi │ │ │ ├── Gui.java │ │ │ ├── GuiButton.java │ │ │ ├── GuiButtonClickEvent.java │ │ │ ├── GuiManager.java │ │ │ ├── OpenGuiInfo.java │ │ │ └── ScrollableGui.java │ │ │ ├── language │ │ │ ├── ExecutableCommand.java │ │ │ ├── Language.java │ │ │ ├── LanguageManager.java │ │ │ ├── LanguageParser.java │ │ │ ├── item │ │ │ │ ├── Collection.java │ │ │ │ ├── LanguageItem.java │ │ │ │ ├── LanguageSign.java │ │ │ │ ├── LanguageText.java │ │ │ │ ├── SignLocation.java │ │ │ │ ├── TWINData.java │ │ │ │ └── serializers │ │ │ │ │ ├── CollectionSerializer.java │ │ │ │ │ ├── LanguageItemSerializer.java │ │ │ │ │ ├── LanguageSignSerializer.java │ │ │ │ │ └── LanguageTextSerializer.java │ │ │ ├── localized │ │ │ │ └── StringLocale.java │ │ │ └── parser │ │ │ │ └── AdvancedComponent.java │ │ │ ├── listeners │ │ │ ├── BukkitListener.java │ │ │ ├── BungeeListener.java │ │ │ └── VelocityListener.java │ │ │ ├── logger │ │ │ ├── JavaLogger.java │ │ │ ├── SLF4JLogger.java │ │ │ └── TritonLogger.java │ │ │ ├── migration │ │ │ └── LanguageMigration.java │ │ │ ├── packetinterceptor │ │ │ ├── BungeeDecoder.java │ │ │ ├── BungeeListener.java │ │ │ ├── PacketInterceptor.java │ │ │ ├── PreLoginBungeeEncoder.java │ │ │ ├── ProtocolLibListener.java │ │ │ └── protocollib │ │ │ │ ├── AdvancementsPacketHandler.java │ │ │ │ ├── BossBarPacketHandler.java │ │ │ │ ├── EntitiesPacketHandler.java │ │ │ │ ├── HandlerFunction.java │ │ │ │ ├── MotdPacketHandler.java │ │ │ │ ├── PacketHandler.java │ │ │ │ └── SignPacketHandler.java │ │ │ ├── placeholderapi │ │ │ └── TritonPlaceholderHook.java │ │ │ ├── player │ │ │ ├── BungeeLanguagePlayer.java │ │ │ ├── LanguagePlayer.java │ │ │ ├── PlayerManager.java │ │ │ ├── SpigotLanguagePlayer.java │ │ │ └── VelocityLanguagePlayer.java │ │ │ ├── plugin │ │ │ ├── BungeePlugin.java │ │ │ ├── PluginLoader.java │ │ │ ├── SpigotPlugin.java │ │ │ └── VelocityPlugin.java │ │ │ ├── storage │ │ │ ├── IpCache.java │ │ │ ├── LocalStorage.java │ │ │ ├── MysqlStorage.java │ │ │ └── Storage.java │ │ │ ├── terminal │ │ │ ├── BungeeTerminalFormatter.java │ │ │ ├── BungeeTerminalManager.java │ │ │ ├── ChatColorTerminalReplacer.java │ │ │ ├── Log4jInjector.java │ │ │ ├── SpigotTerminalFormatter.java │ │ │ ├── TranslatablePrintStream.java │ │ │ └── TritonTerminalRewrite.java │ │ │ ├── utils │ │ │ ├── AppenderRefFactory.java │ │ │ ├── ComponentUtils.java │ │ │ ├── EntityTypeUtils.java │ │ │ ├── FileUtils.java │ │ │ ├── ItemStackTranslationUtils.java │ │ │ ├── ModernComponentGetters.java │ │ │ ├── NMSUtils.java │ │ │ ├── NbtUtils.java │ │ │ ├── ScoreboardUtils.java │ │ │ ├── SocketUtils.java │ │ │ ├── StringUtils.java │ │ │ └── YAMLUtils.java │ │ │ ├── web │ │ │ ├── TwinManager.java │ │ │ └── TwinParser.java │ │ │ └── wrappers │ │ │ ├── AdventureComponentWrapper.java │ │ │ ├── HoverComponentWrapper.java │ │ │ ├── MaterialWrapperManager.java │ │ │ ├── WrappedAdvancement.java │ │ │ ├── WrappedAdvancementDisplay.java │ │ │ ├── WrappedAdvancementHolder.java │ │ │ ├── WrappedClientConfiguration.java │ │ │ ├── WrappedPlayerChatMessage.java │ │ │ └── items │ │ │ ├── ItemStackParser.java │ │ │ ├── WrappedFilterable.java │ │ │ ├── WrappedItemContainerContents.java │ │ │ ├── WrappedItemLore.java │ │ │ ├── WrappedPatchedDataComponentMap.java │ │ │ └── WrappedWrittenBookContent.java │ └── resources │ │ ├── bungee_config.yml │ │ ├── config.yml │ │ └── messages.yml │ └── test │ ├── .gitkeep │ └── java │ └── com │ └── rexcantor64 │ └── triton │ ├── language │ └── parser │ │ └── AdvancedComponentTest.java │ └── utils │ └── ComponentUtilsTest.java ├── examples ├── README.md ├── languages.json └── languages_bungee.json ├── gradle.properties ├── gradle ├── publish.gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── spigot-legacy ├── build.gradle └── src └── main └── java └── com └── rexcantor64 └── triton └── wrappers └── legacy └── HoverComponentWrapper.java /.github/ISSUE_TEMPLATE/bug_report.yaml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Have you found a bug while using Triton? Report it here and help us improve! 3 | labels: ["type:bug", "triage"] 4 | assignees: 5 | - diogotcorreia 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: Thank you for opening a bug report! Please fill out the following information so it can be fixed as soon as possible. 10 | - type: textarea 11 | id: bug_description 12 | attributes: 13 | label: Describe the bug 14 | description: A clear and concise description of what the bug is. Please include screenshots if possible! 15 | validations: 16 | required: true 17 | - type: textarea 18 | id: how_to_reproduce 19 | attributes: 20 | label: How can the bug be reproduced? 21 | description: >- 22 | Give a list of step-by-step instructions on how to reproduce this problem. 23 | Include configs for related plugins if possible. 24 | placeholder: | 25 | 1. Install plugin X 26 | 2. Change Y on `config.yml` 27 | 3. Run command `/z` 28 | 4. ... 29 | validations: 30 | required: false 31 | - type: textarea 32 | id: expected 33 | attributes: 34 | label: Expected behaviour 35 | description: >- 36 | What should have happened instead? 37 | placeholder: | 38 | Instead of plugin X showing message Y, it should've showed message Z. 39 | validations: 40 | required: false 41 | - type: dropdown 42 | id: spigot_fork 43 | attributes: 44 | label: Which Spigot fork are you using? 45 | options: 46 | - "Vanilla Spigot (from BuildTools)" 47 | - "Vanilla Spigot (from a JAR mirroring website)" 48 | - "PaperMC" 49 | - "Purpur" 50 | - "Airplane" 51 | - "Other (specify below)" 52 | validations: 53 | required: true 54 | - type: input 55 | id: spigot_version 56 | attributes: 57 | label: What's your server version? 58 | description: Run `/version` on your server to get an accurate version 59 | placeholder: "3551-Spigot-14a2382-9a8e080 (MC: 1.19)" 60 | validations: 61 | required: true 62 | - type: dropdown 63 | id: proxy_fork 64 | attributes: 65 | label: Which proxy are you using? 66 | options: 67 | - "I'm not using a proxy" 68 | - "BungeeCord" 69 | - "Waterfall" 70 | - "FlameCord" 71 | - "Aegis" 72 | - "Velocity" 73 | - "Other (specify below)" 74 | validations: 75 | required: true 76 | - type: input 77 | id: proxy_version 78 | attributes: 79 | label: What's your proxy version? 80 | description: It probably appears when you start the proxy. Otherwise, put the maximum MC version supported. 81 | placeholder: "git:BungeeCord-Bootstrap:1.19-R0.1-SNAPSHOT:12e4514:1654" 82 | validations: 83 | required: false 84 | - type: input 85 | id: client_version 86 | attributes: 87 | label: What's the MC version of your client? 88 | description: The Minecraft version of the client (player) you're using. 89 | placeholder: "1.19" 90 | validations: 91 | required: true 92 | - type: input 93 | id: triton_version 94 | attributes: 95 | label: What's the Triton version you're using? 96 | description: Before submitting a bug report, check if it is the latest one. 97 | placeholder: "3.8.0" 98 | validations: 99 | required: true 100 | - type: textarea 101 | id: additional_information 102 | attributes: 103 | label: Additional Information 104 | description: >- 105 | If you responded "other" to any of the questions above, this is where you should specify. 106 | 107 | You should also place versions of related plugins here. 108 | validations: 109 | required: false 110 | - type: markdown 111 | attributes: 112 | value: Thank you for taking the time to fill out this form. Click the "Submit new issue" button below. 113 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yaml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Do you have an idea for a new feature? This is where you should request it! 3 | labels: ["type:feature", "triage"] 4 | assignees: 5 | - diogotcorreia 6 | body: 7 | - type: markdown 8 | attributes: 9 | value: Thank you for opening a feature request! Please fill out the following information so it can be implemented as soon as possible. 10 | - type: textarea 11 | id: feature_description 12 | attributes: 13 | label: Describe the Feature 14 | description: | 15 | Please describe the feature you want as clearly as possible. 16 | When helpful, include screenshots or other information that could help us understand the feature. 17 | validations: 18 | required: true 19 | - type: markdown 20 | attributes: 21 | value: Thank you for taking the time to fill out this form. Click the "Submit new issue" button below. 22 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Java CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - name: Checkout repo 11 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 12 | 13 | - name: Set up JDK 17 14 | uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 15 | with: 16 | java-version: '17' 17 | distribution: 'adopt' 18 | 19 | - name: Setup Gradle 20 | uses: gradle/actions/setup-gradle@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0 21 | 22 | - name: Run Gradle build 23 | run: ./gradlew build 24 | -------------------------------------------------------------------------------- /.github/workflows/publish-api.yml: -------------------------------------------------------------------------------- 1 | name: Publish package to the Maven Central Repository and GitHub Packages 2 | on: 3 | release: 4 | types: [created] 5 | jobs: 6 | publish: 7 | runs-on: ubuntu-latest 8 | permissions: 9 | contents: read 10 | steps: 11 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 12 | - name: Set up Java 13 | uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 14 | with: 15 | java-version: '17' 16 | distribution: 'adopt' 17 | - name: Setup Gradle 18 | uses: gradle/actions/setup-gradle@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0 19 | 20 | - name: Publish package 21 | run: ./gradlew publish 22 | env: 23 | ORG_GRADLE_PROJECT_diogotcRepositoryUsername: github-ci-triton 24 | ORG_GRADLE_PROJECT_diogotcRepositoryPassword: ${{ secrets.MAVEN_REPO_SECRET }} 25 | 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.ear 17 | *.zip 18 | *.tar.gz 19 | *.rar 20 | 21 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 22 | hs_err_pid* 23 | 24 | # Gradle 25 | .gradle 26 | **/build/ 27 | !src/**/build/ 28 | 29 | # Ignore Gradle GUI config 30 | gradle-app.setting 31 | 32 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 33 | !gradle-wrapper.jar 34 | 35 | # Cache of project 36 | .gradletasknamecache 37 | 38 | # IntelliJ files and folders 39 | *.iml 40 | .idea/ 41 | out/ 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Triton 2 | 3 | [![Spigot](https://img.shields.io/badge/dynamic/json?color=blue&label=Spigot&prefix=v&query=%24.current_version&url=https%3A%2F%2Fapi.spigotmc.org%2Fsimple%2F0.2%2Findex.php%3Faction%3DgetResource%26id%3D30331)](https://www.spigotmc.org/resources/triton-translate-your-server.30331/) 4 | [![Spigot Rating](https://img.shields.io/spiget/rating/30331?color=orange)](https://www.spigotmc.org/resources/triton-translate-your-server.30331/) 5 | [![Release](https://jitpack.io/v/diogotcorreia/Triton.svg)](https://jitpack.io/#tritonmc/Triton) 6 | 7 | _Translate your server! Sends the same message in different languages... Hooks into all plugins!_ 8 | This repository was previously called MultiLanguagePlugin. 9 | 10 | Triton is a Minecraft plugin for Spigot and BungeeCord that helps you translate your Minecraft server! 11 | 12 | Purchase the plugin on [Spigot](https://spigotmc.org/resources/triton.30331/) 13 | or [Polymart](https://polymart.org/resource/triton.38)! 14 | 15 | ## Using the API 16 | 17 | The recommended way to use Triton's API is through the Gradle/Maven artifact. 18 | Note that the Maven repository mentioned below is only available since 19 | Triton v3.11.2. 20 | 21 |
22 | Gradle (Groovy) Instructions 23 | 24 | Firstly, add the following repository to your project: 25 | 26 | ```groovy 27 | repositories { 28 | maven { 29 | url "https://repo.diogotc.com/releases" 30 | } 31 | } 32 | ``` 33 | 34 | Then, you should be able to add the Triton API dependency. 35 | Make sure to NOT shade it into your plugin by using `compileOnly`. 36 | 37 | ```groovy 38 | dependencies { 39 | // change the version to whatever the latest one is 40 | compileOnly "com.rexcantor64.triton:triton-api:3.11.2" 41 | } 42 | ``` 43 |
44 | 45 |
46 | Maven Instructions 47 | 48 | Firstly, add the following repository to your project: 49 | 50 | ```xml 51 | 52 | diogotc-repository-releases 53 | Diogo Correia's Releases Repository 54 | https://repo.diogotc.com/releases 55 | 56 | ``` 57 | 58 | Then, you should be able to add the Triton API dependency. 59 | Make sure to NOT shade it into your plugin by setting the appropriate `scope`. 60 | 61 | ```xml 62 | 63 | com.rexcantor64.triton 64 | triton-api 65 | 66 | 3.11.2 67 | provided 68 | 69 | ``` 70 |
71 | 72 | Need help developing? Take a look at the 73 | [wiki](https://github.com/tritonmc/Triton/wiki), 74 | [JavaDocs](https://triton.rexcantor64.com/javadocs) or join 75 | our [Discord](https://triton.rexcantor64.com/discord)! 76 | 77 | Looking for older API versions? Take a look at 78 | the [download page](https://github.com/diogotcorreia/Triton/wiki/Downloads) 79 | or [JitPack](https://jitpack.io/#tritonmc/triton/). 80 | 81 | ## Compiling from Source 82 | 83 | Triton is still a premium plugin and if you're going to use it, 84 | it's advised that you purchase it from Spigot or Polymart, as stated above. 85 | Nevertheless, you're still free to compile it yourself if you have the skills to do so. 86 | No support will be given to self-compiled versions. 87 | 88 | To compile, clone this repository and run the following command: 89 | 90 | ```sh 91 | ./gradlew shadowJar 92 | ``` 93 | -------------------------------------------------------------------------------- /api/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'maven-publish' 4 | } 5 | 6 | group 'com.rexcantor64.triton' 7 | 8 | apply from: '../gradle/publish.gradle' 9 | 10 | dependencies { 11 | compileOnly 'org.spigotmc:spigot-api:1.21-R0.1-SNAPSHOT' 12 | compileOnly 'net.md-5:bungeecord-api:1.21-R0.3-SNAPSHOT' 13 | } 14 | 15 | java { 16 | withJavadocJar() 17 | withSourcesJar() 18 | } 19 | 20 | tasks { 21 | jar { 22 | manifest { 23 | attributes 'Automatic-Module-Name': 'com.rexcantor64.triton.api' 24 | } 25 | } 26 | 27 | javadoc { 28 | options.encoding = 'UTF-8' 29 | options.charSet = 'UTF-8' 30 | options.source = '8' 31 | options.links( 32 | 'https://hub.spigotmc.org/javadocs/spigot/', 33 | 'https://jd.papermc.io/waterfall/1.20/', // Use Waterfall's JavaDocs instead of Bungee's since those are broken 34 | 'https://docs.oracle.com/en/java/javase/21/docs/api/', 35 | ) 36 | 37 | // Disable the crazy super-strict doclint tool in Java 8 38 | options.addStringOption('Xdoclint:none', '-quiet') 39 | 40 | // Mark sources as Java 8 source compatible 41 | options.source = '8' 42 | } 43 | } 44 | 45 | publishing { 46 | publications { 47 | tritonApi(MavenPublication) { 48 | from components.java 49 | 50 | artifactId = 'triton-api' 51 | 52 | pom { 53 | name.set('Triton API') 54 | description.set('Translate your server! Sends the same message in different languages... Hooks into all plugins!') 55 | url.set('https://triton.rexcantor64.com') 56 | scm { 57 | url.set('https://github.com/tritonmc/Triton') 58 | connection.set('scm:git:https://github.com/tritonmc/Triton.git') 59 | developerConnection.set('scm:git:https://github.com/tritonmc/Triton.git') 60 | } 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /api/src/main/java/com/rexcantor64/triton/api/Triton.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.api; 2 | 3 | import com.rexcantor64.triton.api.config.TritonConfig; 4 | import com.rexcantor64.triton.api.language.LanguageManager; 5 | import com.rexcantor64.triton.api.language.LanguageParser; 6 | import com.rexcantor64.triton.api.players.LanguagePlayer; 7 | import com.rexcantor64.triton.api.players.PlayerManager; 8 | 9 | /** 10 | * Represents the plugin's main class. 11 | * 12 | * @since 1.0.0 13 | */ 14 | public interface Triton { 15 | 16 | /** 17 | * Get the {@link TritonConfig config}. 18 | * 19 | * @return The {@link TritonConfig config}. 20 | * @since 1.0.0 21 | */ 22 | TritonConfig getConf(); 23 | 24 | /** 25 | * Get the {@link TritonConfig config}. 26 | * Alias of {@link Triton#getConf()} 27 | * 28 | * @return The {@link TritonConfig config}. 29 | * @since 3.0.0 30 | */ 31 | TritonConfig getConfig(); 32 | 33 | /** 34 | * Get the {@link LanguageManager language manager}. 35 | * 36 | * @return The {@link LanguageManager language manager}. 37 | * @since 1.0.0 38 | */ 39 | LanguageManager getLanguageManager(); 40 | 41 | /** 42 | * Get the {@link LanguageParser language parser}. 43 | * 44 | * @return The {@link LanguageParser language parser}. 45 | * @since 3.0.0 46 | */ 47 | LanguageParser getLanguageParser(); 48 | 49 | /** 50 | * Get the {@link PlayerManager player manager}. 51 | * 52 | * @return The {@link PlayerManager player manager}. 53 | * @since 1.0.0 54 | */ 55 | PlayerManager getPlayerManager(); 56 | 57 | /** 58 | * Open the language selection GUI on a specific {@link LanguagePlayer player}. 59 | * 60 | * @param player The {@link LanguagePlayer player} that will see the GUI. 61 | * @since 1.0.0 62 | */ 63 | void openLanguagesSelectionGUI(LanguagePlayer player); 64 | 65 | /** 66 | * Reload the config, messages, translations and player data. 67 | * Should not be abused. 68 | * 69 | * @since 2.5.0 70 | */ 71 | void reload(); 72 | 73 | } 74 | -------------------------------------------------------------------------------- /api/src/main/java/com/rexcantor64/triton/api/TritonAPI.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.api; 2 | 3 | /** 4 | * The entry point of the API 5 | * 6 | * @since 1.0.0 7 | */ 8 | public class TritonAPI { 9 | 10 | /** 11 | * Get the instance of the {@link Triton plugin}. 12 | * 13 | * @return The instance of the {@link Triton plugin}. 14 | * @since 1.0.0 15 | */ 16 | public static Triton getInstance() { 17 | return null; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /api/src/main/java/com/rexcantor64/triton/api/config/FeatureSyntax.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.api.config; 2 | 3 | /** 4 | * Represents how Triton should look for placeholders. 5 | * @since 1.0.0 6 | */ 7 | public interface FeatureSyntax { 8 | 9 | /** 10 | * Get the key of the tag that starts/ends the entire placeholder for a specific feature (chat, scoreboard, titles, etc). 11 | * Default is "lang". 12 | * @return The key of the tag that starts/end the entire placeholder 13 | * @since 1.0.0 14 | */ 15 | String getLang(); 16 | 17 | /** 18 | * Get the key of the tag that starts/ends the variables of a placeholder for a specific feature (chat, scoreboard, titles, etc). 19 | * Default is "args". 20 | * @return The key of the tag that starts/end the variables of a placeholder 21 | * @since 1.0.0 22 | */ 23 | String getArgs(); 24 | 25 | /** 26 | * Get the key of the tag that starts/ends a variable of a placeholder for a specific feature (chat, scoreboard, titles, etc). 27 | * Default is "arg". 28 | * @return The key of the tag that starts/end a variable of a placeholder 29 | * @since 1.0.0 30 | */ 31 | String getArg(); 32 | } 33 | -------------------------------------------------------------------------------- /api/src/main/java/com/rexcantor64/triton/api/events/PlayerChangeLanguageBungeeEvent.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.api.events; 2 | 3 | import com.rexcantor64.triton.api.language.Language; 4 | import com.rexcantor64.triton.api.players.LanguagePlayer; 5 | import net.md_5.bungee.api.plugin.Event; 6 | 7 | /** 8 | * This event is triggered when a player changes their language. 9 | * To be used with BungeeCord. 10 | * 11 | * @since 2.4.0 12 | */ 13 | public class PlayerChangeLanguageBungeeEvent extends Event { 14 | private final LanguagePlayer languagePlayer; 15 | private final Language oldLanguage; 16 | private Language newLanguage; 17 | private boolean isCancelled; 18 | 19 | public PlayerChangeLanguageBungeeEvent(LanguagePlayer languagePlayer, Language oldLanguage, Language newLanguage) { 20 | this.languagePlayer = languagePlayer; 21 | this.oldLanguage = oldLanguage; 22 | this.newLanguage = newLanguage; 23 | } 24 | 25 | /** 26 | * Check if the event is cancelled. 27 | * 28 | * @return whether the event is cancelled or not. 29 | */ 30 | public boolean isCancelled() { 31 | return isCancelled; 32 | } 33 | 34 | /** 35 | * Cancel the event. 36 | * If cancelled, the language of the player isn't changed. 37 | * 38 | * @param cancelled Set whether the event is cancelled or not. 39 | */ 40 | public void setCancelled(boolean cancelled) { 41 | isCancelled = cancelled; 42 | } 43 | 44 | /** 45 | * Get the player that is changing languages. 46 | * 47 | * @return the player that is changing languages. 48 | */ 49 | public LanguagePlayer getLanguagePlayer() { 50 | return languagePlayer; 51 | } 52 | 53 | /** 54 | * Get the language the player is switching from. 55 | * 56 | * @return the language the player is switching from. 57 | */ 58 | public Language getOldLanguage() { 59 | return oldLanguage; 60 | } 61 | 62 | /** 63 | * Get the language the player is switching to. 64 | * 65 | * @return the language the player is switching to. 66 | */ 67 | public Language getNewLanguage() { 68 | return newLanguage; 69 | } 70 | 71 | /** 72 | * Set the language the player is switching to. 73 | * 74 | * @param newLanguage the language the player is switching to. 75 | */ 76 | public void setNewLanguage(Language newLanguage) { 77 | this.newLanguage = newLanguage; 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /api/src/main/java/com/rexcantor64/triton/api/events/PlayerChangeLanguageSpigotEvent.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.api.events; 2 | 3 | import com.rexcantor64.triton.api.language.Language; 4 | import com.rexcantor64.triton.api.players.LanguagePlayer; 5 | import org.bukkit.event.Cancellable; 6 | import org.bukkit.event.Event; 7 | import org.bukkit.event.HandlerList; 8 | 9 | /** 10 | * This event is triggered when a player changes their language. 11 | * To be used with Spigot. 12 | * 13 | * @since 2.4.0 14 | */ 15 | public class PlayerChangeLanguageSpigotEvent extends Event implements Cancellable { 16 | private static final HandlerList HANDLERS = new HandlerList(); 17 | private final LanguagePlayer languagePlayer; 18 | private final Language oldLanguage; 19 | private Language newLanguage; 20 | private boolean isCancelled; 21 | 22 | public PlayerChangeLanguageSpigotEvent(LanguagePlayer languagePlayer, Language oldLanguage, Language newLanguage) { 23 | this.languagePlayer = languagePlayer; 24 | this.oldLanguage = oldLanguage; 25 | this.newLanguage = newLanguage; 26 | } 27 | 28 | public static HandlerList getHandlerList() { 29 | return HANDLERS; 30 | } 31 | 32 | /** 33 | * Check if the event is cancelled. 34 | * 35 | * @return whether the event is cancelled or not. 36 | */ 37 | @Override 38 | public boolean isCancelled() { 39 | return isCancelled; 40 | } 41 | 42 | /** 43 | * Cancel the event. 44 | * If cancelled, the language of the player isn't changed. 45 | * 46 | * @param cancelled Set whether the event is cancelled or not. 47 | */ 48 | @Override 49 | public void setCancelled(boolean cancelled) { 50 | isCancelled = cancelled; 51 | } 52 | 53 | public HandlerList getHandlers() { 54 | return HANDLERS; 55 | } 56 | 57 | /** 58 | * Get the player that is changing languages. 59 | * 60 | * @return the player that is changing languages. 61 | */ 62 | public LanguagePlayer getLanguagePlayer() { 63 | return languagePlayer; 64 | } 65 | 66 | /** 67 | * Get the language the player is switching from. 68 | * 69 | * @return the language the player is switching from. 70 | */ 71 | public Language getOldLanguage() { 72 | return oldLanguage; 73 | } 74 | 75 | /** 76 | * Get the language the player is switching to. 77 | * 78 | * @return the language the player is switching to. 79 | */ 80 | public Language getNewLanguage() { 81 | return newLanguage; 82 | } 83 | 84 | /** 85 | * Set the language the player is switching to. 86 | * 87 | * @param newLanguage the language the player is switching to. 88 | */ 89 | public void setNewLanguage(Language newLanguage) { 90 | this.newLanguage = newLanguage; 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /api/src/main/java/com/rexcantor64/triton/api/language/Language.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.api.language; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * This represents a language in config. 7 | * 8 | * @since 1.0.0 9 | */ 10 | public interface Language extends Localized { 11 | 12 | /** 13 | * @return The name of the language. 14 | * @since 1.0.0 15 | */ 16 | String getName(); 17 | 18 | /** 19 | * @return The locale codes of the language. 20 | * @since 1.0.0 21 | */ 22 | List getMinecraftCodes(); 23 | 24 | /** 25 | * @return The display name of the language in the GUIs and chat with translated color codes (§). 26 | * @since 1.0.0 27 | */ 28 | String getDisplayName(); 29 | 30 | /** 31 | * @return The display name of the language in the GUIs and chat without the translated color codes, just (&). 32 | * @since 1.0.0 33 | */ 34 | String getRawDisplayName(); 35 | 36 | /** 37 | * @return The code of the flag. 38 | * @since 1.0.0 39 | */ 40 | String getFlagCode(); 41 | 42 | /** 43 | * Get the list of languages to fall back to if this language does not have the requested key. 44 | * The main language is always a fall back language even if not in this list. 45 | * 46 | * @return The languages to fall back to if this language does not have the requested key. 47 | * @since 3.7.0 48 | */ 49 | List getFallbackLanguages(); 50 | 51 | } 52 | -------------------------------------------------------------------------------- /api/src/main/java/com/rexcantor64/triton/api/language/LanguageParser.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.api.language; 2 | 3 | import com.rexcantor64.triton.api.config.FeatureSyntax; 4 | import net.md_5.bungee.api.chat.BaseComponent; 5 | 6 | /** 7 | * The class responsible by translating messages with placeholders 8 | */ 9 | public interface LanguageParser { 10 | 11 | /** 12 | * Parses Triton's placeholders in a string and returns the result 13 | * 14 | * @param language The {@link Language#getName() name of the language} to use. If invalid, this will fallback 15 | * to the main language without warning. 16 | * @param syntax The {@link FeatureSyntax} that'll be used for Triton's placeholders syntax. 17 | * @param input The input {@link String}. 18 | * @return The input but with Triton's placeholders replaced by the message in the provided language. 19 | */ 20 | String parseString(String language, FeatureSyntax syntax, String input); 21 | 22 | /** 23 | * Parses Triton's placeholders in a {@link BaseComponent} array and returns the result 24 | * 25 | * @param language The {@link Language#getName() name of the language} to use. If invalid, this will fallback 26 | * to the main language without warning. 27 | * @param syntax The {@link FeatureSyntax} that'll be used for Triton's placeholders syntax. 28 | * @param input The input {@link BaseComponent}. 29 | * @return The input but with Triton's placeholders replaced by the message in the provided language. 30 | */ 31 | BaseComponent[] parseComponent(String language, FeatureSyntax syntax, BaseComponent... input); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /api/src/main/java/com/rexcantor64/triton/api/language/Localized.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.api.language; 2 | 3 | /** 4 | * Represents something that has a language. 5 | * It can be a player or even a language itself. 6 | * 7 | * @since 3.8.0 8 | */ 9 | public interface Localized { 10 | 11 | /** 12 | * Get the string identifier of the language of this object. 13 | * Depending on the underlying implementation, it can get it from a 14 | * player's current language, a language object or even a string itself. 15 | * 16 | * @return The string identifier of the language of this object. 17 | * @since 3.8.0 18 | */ 19 | default String getLanguageId() { 20 | final Language language = this.getLanguage(); 21 | if (language == null) { 22 | return null; 23 | } 24 | return this.getLanguage().getName(); 25 | } 26 | 27 | /** 28 | * Get the language of this object. 29 | * Depending on the underlying implementation, it can get it from a 30 | * player's current language, a language object or derive it from its string id. 31 | * 32 | * @return The language of this object. 33 | * @since 3.8.0 34 | */ 35 | Language getLanguage(); 36 | 37 | } 38 | -------------------------------------------------------------------------------- /api/src/main/java/com/rexcantor64/triton/api/language/SignLocation.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.api.language; 2 | 3 | /** 4 | * Represents the location of a sign 5 | * 6 | * @since 1.0.0 7 | */ 8 | public interface SignLocation { 9 | 10 | /** 11 | * Bungee only. 12 | * 13 | * @return Get the server where this sign is. 14 | * @since 1.0.0 15 | */ 16 | String getServer(); 17 | 18 | /** 19 | * @return Get the world where the sign is. 20 | * @since 1.0.0 21 | */ 22 | String getWorld(); 23 | 24 | /** 25 | * @return Get the X position where the sign is. 26 | * @since 1.0.0 27 | */ 28 | int getX(); 29 | 30 | /** 31 | * @return Get the Y position where the sign is. 32 | * @since 1.0.0 33 | */ 34 | int getY(); 35 | 36 | /** 37 | * @return Get the Z position where the sign is. 38 | * @since 1.0.0 39 | */ 40 | int getZ(); 41 | 42 | } 43 | -------------------------------------------------------------------------------- /api/src/main/java/com/rexcantor64/triton/api/players/LanguagePlayer.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.api.players; 2 | 3 | import com.rexcantor64.triton.api.language.Language; 4 | import com.rexcantor64.triton.api.language.Localized; 5 | 6 | import java.util.UUID; 7 | 8 | /** 9 | * Represents a player that has a language. 10 | * You can get the instance of a player by using {@link PlayerManager#get(UUID)}. 11 | * 12 | * @since 1.0.0 13 | */ 14 | public interface LanguagePlayer extends Localized { 15 | 16 | /** 17 | * Get the {@link Language language} of the player. 18 | * 19 | * @return The {@link Language language} of the player. 20 | * @since 1.0.0 21 | */ 22 | Language getLang(); 23 | 24 | /** 25 | * Set the player's {@link Language language}. 26 | * 27 | * @param language The {@link Language language} to set on the player. 28 | * @since 1.0.0 29 | */ 30 | void setLang(Language language); 31 | 32 | /** 33 | * Refresh the player's translated messages. This includes scoreboard, tab, bossbars, entities, etc. Does not 34 | * affect chat. 35 | * This is automatically invoked when using {@link #setLang(Language)}. 36 | * 37 | * @since 1.0.0 38 | */ 39 | void refreshAll(); 40 | 41 | /** 42 | * Get the UUID of the player. 43 | * 44 | * @return The UUID of the player. 45 | * @since 2.4.0 46 | */ 47 | UUID getUUID(); 48 | } 49 | -------------------------------------------------------------------------------- /api/src/main/java/com/rexcantor64/triton/api/players/PlayerManager.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.api.players; 2 | 3 | import java.util.UUID; 4 | 5 | /** 6 | * This class manages the instances of {@link LanguagePlayer LanguagePlayer}. 7 | * 8 | * @since 1.0.0 9 | */ 10 | public interface PlayerManager { 11 | 12 | /** 13 | * Get an instance of {@link LanguagePlayer LanguagePlayer} for the provided UUID. 14 | * 15 | * @param uuid The UUID of the player to get the {@link LanguagePlayer LanguagePlayer} instance from. 16 | * @return The instance of {@link LanguagePlayer LanguagePlayer} for the provided UUID. 17 | * @since 1.0.0 18 | */ 19 | LanguagePlayer get(UUID uuid); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /api/src/main/java/com/rexcantor64/triton/api/wrappers/EntityType.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.api.wrappers; 2 | 3 | public enum EntityType { 4 | 5 | // https://github.com/PaperMC/Paper/blob/main/paper-api/src/main/java/org/bukkit/entity/EntityType.java 6 | ACACIA_BOAT, 7 | ACACIA_CHEST_BOAT, 8 | ALLAY, 9 | AREA_EFFECT_CLOUD, 10 | ARMADILLO, 11 | ARMOR_STAND, 12 | ARROW, 13 | AXOLOTL, 14 | BAMBOO_CHEST_RAFT, 15 | BAMBOO_RAFT, 16 | BAT, 17 | BEE, 18 | BIRCH_BOAT, 19 | BIRCH_CHEST_BOAT, 20 | BLAZE, 21 | BLOCK_DISPLAY, 22 | BOGGED, 23 | BREEZE, 24 | BREEZE_WIND_CHARGE, 25 | CAMEL, 26 | CAT, 27 | CAVE_SPIDER, 28 | CHERRY_BOAT, 29 | CHERRY_CHEST_BOAT, 30 | CHEST_MINECART, 31 | CHICKEN, 32 | COD, 33 | COMMAND_BLOCK_MINECART, 34 | COW, 35 | CREAKING, 36 | CREEPER, 37 | DARK_OAK_BOAT, 38 | DARK_OAK_CHEST_BOAT, 39 | DOLPHIN, 40 | DONKEY, 41 | DRAGON_FIREBALL, 42 | DROWNED, 43 | EGG, 44 | ELDER_GUARDIAN, 45 | END_CRYSTAL, 46 | ENDER_DRAGON, 47 | ENDER_PEARL, 48 | ENDERMAN, 49 | ENDERMITE, 50 | EVOKER, 51 | EVOKER_FANGS, 52 | EXPERIENCE_BOTTLE, 53 | EXPERIENCE_ORB, 54 | EYE_OF_ENDER, 55 | FALLING_BLOCK, 56 | FIREBALL, 57 | FIREWORK_ROCKET, 58 | FISHING_BOBBER, 59 | FOX, 60 | FROG, 61 | FURNACE_MINECART, 62 | GHAST, 63 | GIANT, 64 | GLOW_ITEM_FRAME, 65 | GLOW_SQUID, 66 | GOAT, 67 | GUARDIAN, 68 | HOGLIN, 69 | HOPPER_MINECART, 70 | HORSE, 71 | HUSK, 72 | ILLUSIONER, 73 | INTERACTION, 74 | IRON_GOLEM, 75 | ITEM, 76 | ITEM_DISPLAY, 77 | ITEM_FRAME, 78 | JUNGLE_BOAT, 79 | JUNGLE_CHEST_BOAT, 80 | LEASH_KNOT, 81 | LIGHTNING_BOLT, 82 | LINGERING_POTION, 83 | LLAMA, 84 | LLAMA_SPIT, 85 | MAGMA_CUBE, 86 | MANGROVE_BOAT, 87 | MANGROVE_CHEST_BOAT, 88 | MARKER, 89 | MINECART, 90 | MOOSHROOM, 91 | MULE, 92 | OAK_BOAT, 93 | OAK_CHEST_BOAT, 94 | OCELOT, 95 | OMINOUS_ITEM_SPAWNER, 96 | PAINTING, 97 | PALE_OAK_BOAT, 98 | PALE_OAK_CHEST_BOAT, 99 | PANDA, 100 | PARROT, 101 | PHANTOM, 102 | PIG, 103 | PIGLIN, 104 | PIGLIN_BRUTE, 105 | PILLAGER, 106 | PLAYER, 107 | POLAR_BEAR, 108 | PUFFERFISH, 109 | RABBIT, 110 | RAVAGER, 111 | SALMON, 112 | SHEEP, 113 | SHULKER, 114 | SHULKER_BULLET, 115 | SILVERFISH, 116 | SKELETON, 117 | SKELETON_HORSE, 118 | SLIME, 119 | SMALL_FIREBALL, 120 | SNIFFER, 121 | SNOW_GOLEM, 122 | SNOWBALL, 123 | SPAWNER_MINECART, 124 | SPECTRAL_ARROW, 125 | SPIDER, 126 | SPLASH_POTION, 127 | SPRUCE_BOAT, 128 | SPRUCE_CHEST_BOAT, 129 | SQUID, 130 | STRAY, 131 | STRIDER, 132 | TADPOLE, 133 | TEXT_DISPLAY, 134 | TNT, 135 | TNT_MINECART, 136 | TRADER_LLAMA, 137 | TRIDENT, 138 | TROPICAL_FISH, 139 | TURTLE, 140 | VEX, 141 | VILLAGER, 142 | VINDICATOR, 143 | WANDERING_TRADER, 144 | WARDEN, 145 | WIND_CHARGE, 146 | WITCH, 147 | WITHER, 148 | WITHER_SKELETON, 149 | WITHER_SKULL, 150 | WOLF, 151 | ZOGLIN, 152 | ZOMBIE, 153 | ZOMBIE_HORSE, 154 | ZOMBIE_VILLAGER, 155 | ZOMBIFIED_PIGLIN, 156 | // legacy (renamed) 157 | // https://github.com/PaperMC/Paper/blob/main/paper-server/src/main/java/org/bukkit/craftbukkit/legacy/FieldRename.java 158 | BOAT, 159 | CHEST_BOAT, 160 | COMMANDBLOCK_MINECART, 161 | DROPPED_ITEM, 162 | ENDER_CRYSTAL, 163 | ENDER_SIGNAL, 164 | EVOCATION_FANGS, 165 | EVOCATION_ILLAGER, 166 | EYE_OF_ENDER_SIGNAL, 167 | FIREWORK, 168 | FIREWORKS_ROCKET, 169 | FISHING_HOOK, 170 | ILLUSION_ILLAGER, 171 | LEASH_HITCH, 172 | LIGHTNING, 173 | MINECART_CHEST, 174 | MINECART_COMMAND, 175 | MINECART_FURNACE, 176 | MINECART_HOPPER, 177 | MINECART_MOB_SPAWNER, 178 | MINECART_TNT, 179 | MUSHROOM_COW, 180 | PIG_ZOMBIE, 181 | POTION, 182 | PRIMED_TNT, 183 | SNOWMAN, 184 | THROWN_EXP_BOTTLE, 185 | VILLAGER_GOLEM, 186 | VINDICATION_ILLAGER, 187 | XP_BOTTLE, 188 | XP_ORB, 189 | ZOMBIE_PIGMAN, 190 | // unknown 191 | UNKNOWN; 192 | 193 | public static EntityType fromBukkit(Object type) { 194 | if (type == null) return UNKNOWN; 195 | for (EntityType t : values()) 196 | if (t.toString().equals(type.toString())) return t; 197 | return UNKNOWN; 198 | } 199 | 200 | } 201 | -------------------------------------------------------------------------------- /core/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id "net.kyori.blossom" version "1.3.0" 4 | } 5 | 6 | apply plugin: 'net.kyori.blossom' 7 | group 'com.rexcantor64.triton' 8 | 9 | test { 10 | useJUnitPlatform() 11 | } 12 | 13 | dependencies { 14 | compileOnly 'org.spigotmc:spigot-api:1.21-R0.1-SNAPSHOT' 15 | compileOnly 'net.md-5:bungeecord-api:1.21-R0.3-SNAPSHOT' 16 | compileOnly 'net.md-5:bungeecord-proxy:1.21-R0.3-SNAPSHOT' 17 | compileOnly 'com.velocitypowered:velocity-api:3.1.0' 18 | annotationProcessor 'com.velocitypowered:velocity-api:3.1.0' 19 | compileOnly 'com.comphenix.protocol:ProtocolLib:5.4.0-SNAPSHOT' 20 | 21 | // Adventure / MiniMessage 22 | compileOnly 'net.kyori:adventure-text-serializer-gson:4.9.3' 23 | implementation('net.kyori:adventure-text-minimessage:4.2.0-SNAPSHOT') { 24 | exclude group: 'net.kyori', module: 'adventure-api' 25 | exclude group: 'net.kyori', module: 'adventure-key' 26 | exclude group: 'net.kyori', module: 'adventure-text-serialize-gson' 27 | exclude group: 'net.kyori', module: 'adventure-text-serialize-legacy' 28 | exclude group: 'net.kyori', module: 'adventure-text-serialize-plain' 29 | } 30 | 31 | // Paper 1.21.3+ does not have this anymore, so shade and relocate it 32 | implementation 'org.fusesource.jansi:jansi:2.4.0' 33 | 34 | compileOnly 'me.clip:placeholderapi:2.11.6' 35 | compileOnly 'org.apache.logging.log4j:log4j-core:2.17.1' 36 | implementation 'org.slf4j:slf4j-nop:1.7.36' 37 | implementation 'com.zaxxer:HikariCP:4.0.3' 38 | implementation('com.tananaev:json-patch:1.1') { 39 | exclude group: 'com.google.code.gson', module: 'gson' 40 | } 41 | compileOnly 'com.google.code.gson:gson:2.9.0' 42 | implementation 'org.bstats:bstats-bukkit:2.2.1' 43 | implementation 'org.bstats:bstats-bungeecord:2.2.1' 44 | implementation project(path: ":api") 45 | implementation project(path: ":spigot-legacy") 46 | implementation project(path: ":v1_13") 47 | 48 | // Test dependencies 49 | testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' 50 | testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2' 51 | 52 | testImplementation 'net.md-5:bungeecord-api:1.21-R0.3-SNAPSHOT' 53 | } 54 | 55 | blossom { 56 | replaceToken '@version@', project.version 57 | } 58 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/VelocityMLP.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton; 2 | 3 | import com.rexcantor64.triton.bridge.VelocityBridgeManager; 4 | import com.rexcantor64.triton.commands.handler.VelocityCommandHandler; 5 | import com.rexcantor64.triton.listeners.VelocityListener; 6 | import com.rexcantor64.triton.plugin.PluginLoader; 7 | import com.rexcantor64.triton.plugin.VelocityPlugin; 8 | import com.rexcantor64.triton.storage.LocalStorage; 9 | import com.velocitypowered.api.proxy.ProxyServer; 10 | import com.velocitypowered.api.proxy.messages.ChannelIdentifier; 11 | import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier; 12 | import com.velocitypowered.api.scheduler.ScheduledTask; 13 | import lombok.Getter; 14 | import lombok.val; 15 | 16 | import java.io.File; 17 | import java.util.UUID; 18 | import java.util.concurrent.TimeUnit; 19 | 20 | public class VelocityMLP extends Triton { 21 | 22 | @Getter 23 | private VelocityBridgeManager bridgeManager; 24 | @Getter 25 | private ChannelIdentifier bridgeChannelIdentifier; 26 | private ScheduledTask configRefreshTask; 27 | 28 | public VelocityMLP(PluginLoader loader) { 29 | super.loader = loader; 30 | } 31 | 32 | public VelocityPlugin getLoader() { 33 | return (VelocityPlugin) this.loader; 34 | } 35 | 36 | @Override 37 | public void onEnable() { 38 | instance = this; 39 | super.onEnable(); 40 | 41 | this.bridgeManager = new VelocityBridgeManager(); 42 | getLoader().getServer().getEventManager().register(getLoader(), new VelocityListener()); 43 | getLoader().getServer().getEventManager().register(getLoader(), bridgeManager); 44 | 45 | this.bridgeChannelIdentifier = MinecraftChannelIdentifier.create("triton", "main"); 46 | getLoader().getServer().getChannelRegistrar().register(this.bridgeChannelIdentifier); 47 | 48 | if (getStorage() instanceof LocalStorage) 49 | bridgeManager.sendConfigToEveryone(); 50 | 51 | val commandHandler = new VelocityCommandHandler(); 52 | val commandManager = getLoader().getServer().getCommandManager(); 53 | commandManager.register(commandManager.metaBuilder("triton") 54 | .aliases(getConfig().getCommandAliases().toArray(new String[0])).build(), commandHandler); 55 | commandManager.register(commandManager.metaBuilder("twin").build(), commandHandler); 56 | } 57 | 58 | @Override 59 | public void reload() { 60 | super.reload(); 61 | if (bridgeManager != null) 62 | bridgeManager.sendConfigToEveryone(); 63 | } 64 | 65 | @Override 66 | protected void startConfigRefreshTask() { 67 | if (configRefreshTask != null) configRefreshTask.cancel(); 68 | if (getConf().getConfigAutoRefresh() <= 0) return; 69 | configRefreshTask = getLoader().getServer().getScheduler().buildTask(getLoader(), this::reload) 70 | .delay(getConfig().getConfigAutoRefresh(), TimeUnit.SECONDS).schedule(); 71 | } 72 | 73 | 74 | public File getDataFolder() { 75 | return getLoader().getDataDirectory().toFile(); 76 | } 77 | 78 | @Override 79 | public String getVersion() { 80 | return "@version@"; 81 | } 82 | 83 | @Override 84 | public void runAsync(Runnable runnable) { 85 | getLoader().getServer().getScheduler().buildTask(getLoader(), runnable).schedule(); 86 | } 87 | 88 | public ProxyServer getVelocity() { 89 | return getLoader().getServer(); 90 | } 91 | 92 | @Override 93 | public UUID getPlayerUUIDFromString(String input) { 94 | val player = getVelocity().getPlayer(input); 95 | if (player.isPresent()) return player.get().getUniqueId(); 96 | 97 | try { 98 | return UUID.fromString(input); 99 | } catch (IllegalArgumentException e) { 100 | return null; 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/api/TritonAPI.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.api; 2 | 3 | public class TritonAPI { 4 | 5 | public static Triton getInstance() { 6 | return com.rexcantor64.triton.Triton.get(); 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/banners/Banner.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.banners; 2 | 3 | import com.rexcantor64.triton.Triton; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public class Banner { 9 | 10 | private List layers = new ArrayList<>(); 11 | private String displayName; 12 | 13 | public Banner(String encoded, String displayName) { 14 | this.displayName = displayName; 15 | List strings = new ArrayList<>(); 16 | int index = 0; 17 | while (index < encoded.length()) { 18 | strings.add(encoded.substring(index, Math.min(index + 2, encoded.length()))); 19 | index += 2; 20 | } 21 | for (String s : strings) { 22 | if (s.length() != 2) { 23 | Triton.get().getLogger() 24 | .logError("Can't load layer %1 for banner %2 because it has an invalid format!", s, encoded); 25 | continue; 26 | } 27 | Colors color = Colors.getByCode(s.charAt(0)); 28 | Patterns type = Patterns.getByCode(s.charAt(1)); 29 | if (color == null) { 30 | Triton.get().getLogger() 31 | .logError("Can't load layer %1 for banner %2 because the color is invalid!", s, encoded); 32 | continue; 33 | } 34 | if (type == null) { 35 | Triton.get().getLogger() 36 | .logError("Can't load layer %1 for banner %2 because the pattern is invalid!", s, encoded); 37 | continue; 38 | } 39 | layers.add(new Layer(color, type)); 40 | } 41 | } 42 | 43 | public List getLayers() { 44 | return layers; 45 | } 46 | 47 | public String getDisplayName() { 48 | return displayName; 49 | } 50 | 51 | public class Layer { 52 | private final Colors color; 53 | private final Patterns pattern; 54 | 55 | private Layer(Colors color, Patterns pattern) { 56 | this.color = color; 57 | this.pattern = pattern; 58 | } 59 | 60 | public Colors getColor() { 61 | return color; 62 | } 63 | 64 | public Patterns getPattern() { 65 | return pattern; 66 | } 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/banners/Colors.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.banners; 2 | 3 | public enum Colors { 4 | BLACK("BLACK", 'a'), RED("RED", 'b'), GREEN("GREEN", 'c'), BROWN("BROWN", 'd'), BLUE( 5 | "BLUE", 'e'), PURPLE("PURPLE", 'f'), CYAN("CYAN", 'g'), GRAY("LIGHT_GRAY", 6 | 'h'), DARK_GRAY("GRAY", 'i'), PINK("PINK", 'j'), LIME("LIME", 'k'), YELLOW( 7 | "YELLOW", 'l'), LIGHT_BLUE("LIGHT_BLUE", 'm'), MAGENTA("MAGENTA", 8 | 'n'), ORANGE("ORANGE", 'o'), WHITE("WHITE", 'p'); 9 | 10 | private final String color; 11 | private final char code; 12 | 13 | Colors(String color, char code) { 14 | this.color = color; 15 | this.code = code; 16 | } 17 | 18 | public static Colors getByCode(char code) { 19 | for (Colors c : values()) 20 | if (c.getCode() == code) 21 | return c; 22 | return null; 23 | } 24 | 25 | public char getCode() { 26 | return code; 27 | } 28 | 29 | public String getColor() { 30 | return color; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/banners/Patterns.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.banners; 2 | 3 | public enum Patterns { 4 | BASE("BASE", 'a'), BL("SQUARE_BOTTOM_LEFT", 'b'), BO("BORDER", 'c'), BR( 5 | "SQUARE_BOTTOM_RIGHT", 6 | 'd'), BRI("BRICKS", 'e'), BS("STRIPE_BOTTOM", 'f'), BT("TRIANGLE_BOTTOM", 7 | 'g'), BTS("TRIANGLES_BOTTOM", 'h'), CBO("CURLY_BORDER", 'i'), CR( 8 | "CROSS", 9 | 'j'), CRE("CREEPER", 'k'), CS("STRIPE_CENTER", 'l'), DLS( 10 | "STRIPE_DOWNLEFT", 11 | 'm'), DRS("STRIPE_DOWNRIGHT", 'n'), FLO("FLOWER", 'o'), GRA( 12 | "GRADIENT", 13 | 'p'), HH("HALF_HORIZONTAL", 'q'), LD("DIAGONAL_LEFT", 14 | 'r'), LS("STRIPE_LEFT", 's'), MC( 15 | "CIRCLE_MIDDLE", 16 | 't'), MOJ("MOJANG", 'u'), MR( 17 | "RHOMBUS_MIDDLE", 18 | 'v'), MS("STRIPE_MIDDLE", 'w'), RD( 19 | "DIAGONAL_RIGHT", 20 | 'x'), RS("STRIPE_RIGHT", 'y'), SC( 21 | "STRAIGHT_CROSS", 22 | 'z'), SKU("SKULL", 23 | 'A'), SS( 24 | "STRIPE_SMALL", 25 | 'B'), TL( 26 | "SQUARE_TOP_LEFT", 27 | 'C'), TR( 28 | "SQUARE_TOP_RIGHT", 29 | 'D'), TS( 30 | "STRIPE_TOP", 31 | 'E'), TT( 32 | "TRIANGLE_TOP", 33 | 'F'), TTS( 34 | "TRIANGLES_TOP", 35 | 'G'), VH( 36 | "HALF_VERTICAL", 37 | 'H'), LUD( 38 | "DIAGONAL_LEFT_MIRROR", 39 | 'I'), RUD( 40 | "DIAGONAL_RIGHT_MIRROR", 41 | 'J'), GRU( 42 | "GRADIENT_UP", 43 | 'K'), HHB( 44 | "HALF_HORIZONTAL_MIRROR", 45 | 'L'), VHR( 46 | "HALF_VERTICAL_MIRROR", 47 | 'M'); 48 | 49 | private final String type; 50 | private final char code; 51 | 52 | Patterns(String type, char code) { 53 | this.type = type; 54 | this.code = code; 55 | } 56 | 57 | public static Patterns getByCode(char code) { 58 | for (Patterns p : values()) 59 | if (p.getCode() == code) 60 | return p; 61 | return null; 62 | } 63 | 64 | public char getCode() { 65 | return code; 66 | } 67 | 68 | public String getType() { 69 | return type; 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/commands/DatabaseCommand.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.commands; 2 | 3 | import com.rexcantor64.triton.Triton; 4 | import com.rexcantor64.triton.commands.handler.Command; 5 | import com.rexcantor64.triton.commands.handler.CommandEvent; 6 | import com.rexcantor64.triton.storage.LocalStorage; 7 | import lombok.val; 8 | 9 | import java.util.Collections; 10 | import java.util.List; 11 | import java.util.stream.Collectors; 12 | import java.util.stream.Stream; 13 | 14 | public class DatabaseCommand implements Command { 15 | 16 | @Override 17 | public boolean handleCommand(CommandEvent event) { 18 | val sender = event.getSender(); 19 | 20 | sender.assertPermission("triton.database"); 21 | 22 | if (event.getEnvironment() == CommandEvent.Environment.SPIGOT && Triton.get().getConfig().isBungeecord()) { 23 | sender.sendMessageFormatted("error.not-available-on-spigot"); 24 | return true; 25 | } 26 | 27 | val args = event.getArgs(); 28 | if (args.length == 0) { 29 | sender.sendMessageFormatted("help.database", event.getLabel()); 30 | return true; 31 | } 32 | 33 | val storage = Triton.get().getStorage(); 34 | if (storage instanceof LocalStorage) { 35 | sender.sendMessageFormatted("error.database-not-supported"); 36 | return true; 37 | } 38 | 39 | val mode = args[0]; 40 | switch (mode.toLowerCase()) { 41 | case "upload": 42 | case "u": 43 | sender.sendMessageFormatted("other.database-loading"); 44 | Triton.get().runAsync(() -> { 45 | val localStorage = new LocalStorage(); 46 | val collections = localStorage.downloadFromStorage(); 47 | 48 | Triton.get().getStorage().uploadToStorage(collections); 49 | Triton.get().getStorage().setCollections(collections); 50 | 51 | Triton.get().getLanguageManager().setup(); 52 | if (Triton.isBungee()) 53 | Triton.asBungee().getBridgeManager().sendConfigToEveryone(); 54 | if (Triton.isVelocity()) 55 | Triton.asVelocity().getBridgeManager().sendConfigToEveryone(); 56 | Triton.get().refreshPlayers(); 57 | 58 | sender.sendMessageFormatted("success.database"); 59 | }); 60 | break; 61 | case "download": 62 | case "d": 63 | sender.sendMessageFormatted("other.database-loading"); 64 | Triton.get().runAsync(() -> { 65 | val localStorage = new LocalStorage(); 66 | localStorage.uploadToStorage(Triton.get().getStorage().getCollections()); 67 | 68 | sender.sendMessageFormatted("success.database"); 69 | }); 70 | break; 71 | default: 72 | sender.sendMessageFormatted("error.database-invalid-mode", mode); 73 | break; 74 | } 75 | return true; 76 | } 77 | 78 | @Override 79 | public List handleTabCompletion(CommandEvent event) { 80 | if (event.getArgs().length > 1) 81 | return Collections.emptyList(); 82 | return Stream.of("download", "upload") 83 | .filter(v -> v.toLowerCase().startsWith(event.getArgs()[0].toLowerCase())) 84 | .collect(Collectors.toList()); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/commands/GetFlagCommand.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.commands; 2 | 3 | import com.rexcantor64.triton.Triton; 4 | import com.rexcantor64.triton.api.language.Language; 5 | import com.rexcantor64.triton.commands.handler.Command; 6 | import com.rexcantor64.triton.commands.handler.CommandEvent; 7 | import com.rexcantor64.triton.wrappers.items.ItemStackParser; 8 | import lombok.val; 9 | import org.bukkit.Bukkit; 10 | 11 | import java.util.Collections; 12 | import java.util.List; 13 | import java.util.Objects; 14 | import java.util.stream.Collectors; 15 | 16 | public class GetFlagCommand implements Command { 17 | 18 | @Override 19 | public boolean handleCommand(CommandEvent event) { 20 | val sender = event.getSender(); 21 | val uuid = sender.getUUID(); 22 | 23 | if (uuid == null) { 24 | sender.sendMessage("Only players"); 25 | return true; 26 | } 27 | 28 | if (event.getEnvironment() != CommandEvent.Environment.SPIGOT) 29 | return false; 30 | 31 | sender.assertPermission("triton.getflag", "multilanguageplugin.getflag"); 32 | 33 | if (event.getArgs().length == 0) { 34 | sender.sendMessageFormatted("help.getflag", event.getLabel()); 35 | return true; 36 | } 37 | 38 | val lang = Triton.get().getLanguageManager().getLanguageByName(event.getArgs()[0], false); 39 | if (lang == null) { 40 | sender.sendMessageFormatted("error.lang-not-found", event.getArgs()[0]); 41 | return true; 42 | } 43 | 44 | Objects.requireNonNull(Bukkit.getPlayer(uuid)) 45 | .getInventory() 46 | .addItem(ItemStackParser.bannerToItemStack(lang.getBanner(), false)); 47 | sender.sendMessageFormatted("success.getflag", lang.getDisplayName()); 48 | 49 | return true; 50 | } 51 | 52 | @Override 53 | public List handleTabCompletion(CommandEvent event) { 54 | event.getSender().assertPermission("triton.getflag", "multilanguageplugin.getflag"); 55 | 56 | if (event.getArgs().length != 1) return Collections.emptyList(); 57 | 58 | return Triton.get().getLanguageManager().getAllLanguages().stream().map(Language::getName) 59 | .filter(name -> name.toLowerCase().startsWith(event.getArgs()[0])).collect(Collectors.toList()); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/commands/HelpCommand.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.commands; 2 | 3 | import com.rexcantor64.triton.Triton; 4 | import com.rexcantor64.triton.commands.handler.Command; 5 | import com.rexcantor64.triton.commands.handler.CommandEvent; 6 | import com.rexcantor64.triton.commands.handler.CommandHandler; 7 | import lombok.val; 8 | 9 | import java.util.Collections; 10 | import java.util.List; 11 | 12 | public class HelpCommand implements Command { 13 | 14 | @Override 15 | public boolean handleCommand(CommandEvent event) { 16 | event.getSender().assertPermission("triton.help", "multilanguageplugin.help"); 17 | 18 | for (val str : Triton.get().getMessagesConfig().getMessageList("help.menu")) 19 | if (str.equalsIgnoreCase("%1")) 20 | for (String command : CommandHandler.commands.keySet()) 21 | event.getSender().sendMessageFormatted("help.menu-item", event.getLabel(), command, Triton.get() 22 | .getMessagesConfig() 23 | .getMessage("command." + command)); 24 | else 25 | event.getSender().sendMessage(str); 26 | 27 | return true; 28 | } 29 | 30 | @Override 31 | public List handleTabCompletion(CommandEvent event) { 32 | return Collections.emptyList(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/commands/InfoCommand.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.commands; 2 | 3 | import com.rexcantor64.triton.Triton; 4 | import com.rexcantor64.triton.commands.handler.Command; 5 | import com.rexcantor64.triton.commands.handler.CommandEvent; 6 | import lombok.val; 7 | 8 | import java.util.Collections; 9 | import java.util.List; 10 | 11 | public class InfoCommand implements Command { 12 | @Override 13 | public boolean handleCommand(CommandEvent event) { 14 | val sender = event.getSender(); 15 | sender.assertPermission("triton.info"); 16 | 17 | Triton.get().getMessagesConfig().getMessageList("info-command").forEach((msg) -> sender 18 | .sendMessage(handleArguments(msg, 19 | Triton.get().getVersion(), 20 | "Rexcantor64 (Diogo Correia)", 21 | Triton.get().getStorage().toString(), 22 | event.getEnvironment().isProxy() || Triton.get().getConfig().isBungeecord() 23 | ))); 24 | Triton.get().getLogger().logTrace("Current config: %1", Triton.get().getConfig()); 25 | 26 | return true; 27 | } 28 | 29 | @Override 30 | public List handleTabCompletion(CommandEvent event) { 31 | return Collections.emptyList(); 32 | } 33 | 34 | private String handleArguments(String s, Object... args) { 35 | for (int i = 0; i < args.length; i++) 36 | if (args[i] != null) 37 | s = s.replace("%" + (i + 1), args[i].toString()); 38 | return s; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/commands/LogLevelCommand.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.commands; 2 | 3 | import com.rexcantor64.triton.Triton; 4 | import com.rexcantor64.triton.commands.handler.Command; 5 | import com.rexcantor64.triton.commands.handler.CommandEvent; 6 | import lombok.val; 7 | 8 | import java.util.Arrays; 9 | import java.util.Collections; 10 | import java.util.List; 11 | import java.util.stream.Collectors; 12 | 13 | public class LogLevelCommand implements Command { 14 | @Override 15 | public boolean handleCommand(CommandEvent event) { 16 | val sender = event.getSender(); 17 | sender.assertPermission("triton.loglevel"); 18 | 19 | if (sender.getUUID() != null) { 20 | sender.sendMessageFormatted("error.only-console"); 21 | return true; 22 | } 23 | 24 | if (event.getArgs().length == 0) { 25 | sender.sendMessageFormatted("success.current-loglevel", Triton.get().getConfig().getLogLevel()); 26 | return true; 27 | } 28 | 29 | int newLogLevel; 30 | try { 31 | newLogLevel = Integer.parseInt(event.getArgs()[0]); 32 | } catch (NumberFormatException e) { 33 | sender.sendMessageFormatted("help.loglevel", event.getLabel()); 34 | return true; 35 | } 36 | 37 | Triton.get().getConfig().setLogLevel(newLogLevel); 38 | 39 | sender.sendMessageFormatted("success.set-loglevel", newLogLevel); 40 | return true; 41 | } 42 | 43 | @Override 44 | public List handleTabCompletion(CommandEvent event) { 45 | event.getSender().assertPermission("triton.loglevel"); 46 | 47 | val possibilities = new String[]{"0", "1", "2"}; 48 | 49 | if (event.getArgs().length > 1) return Collections.emptyList(); 50 | 51 | if (event.getArgs().length == 0) { 52 | return Arrays.asList(possibilities); 53 | } 54 | 55 | return Arrays.stream(possibilities) 56 | .filter(logLevel -> logLevel.toLowerCase().startsWith(event.getArgs()[0])) 57 | .collect(Collectors.toList()); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/commands/OpenSelectorCommand.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.commands; 2 | 3 | import com.rexcantor64.triton.Triton; 4 | import com.rexcantor64.triton.commands.handler.Command; 5 | import com.rexcantor64.triton.commands.handler.CommandEvent; 6 | import lombok.val; 7 | 8 | import java.util.Collections; 9 | import java.util.List; 10 | 11 | public class OpenSelectorCommand implements Command { 12 | 13 | @Override 14 | public boolean handleCommand(CommandEvent event) { 15 | val sender = event.getSender(); 16 | val uuid = sender.getUUID(); 17 | 18 | if (event.getEnvironment() != CommandEvent.Environment.SPIGOT && uuid != null) 19 | return false; 20 | 21 | sender.assertPermission("triton.openselector", "multilanguageplugin.openselector"); 22 | 23 | if (uuid == null) 24 | sender.sendMessage("Only players"); 25 | else 26 | Triton.asSpigot().openLanguagesSelectionGUI(Triton.get().getPlayerManager().get(uuid)); 27 | 28 | return true; 29 | } 30 | 31 | @Override 32 | public List handleTabCompletion(CommandEvent event) { 33 | return Collections.emptyList(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/commands/ReloadCommand.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.commands; 2 | 3 | import com.rexcantor64.triton.Triton; 4 | import com.rexcantor64.triton.commands.handler.Command; 5 | import com.rexcantor64.triton.commands.handler.CommandEvent; 6 | import lombok.val; 7 | 8 | import java.util.Collections; 9 | import java.util.List; 10 | import java.util.stream.Collectors; 11 | import java.util.stream.Stream; 12 | 13 | public class ReloadCommand implements Command { 14 | 15 | @Override 16 | public boolean handleCommand(CommandEvent event) { 17 | val sender = event.getSender(); 18 | val isBungee = event.getEnvironment() == CommandEvent.Environment.BUNGEE; 19 | 20 | sender.assertPermission("triton.reload", "multilanguageplugin.reload"); 21 | 22 | if (isBungee) { 23 | val action = event.getArgs().length >= 1 && sender.getUUID() != null ? event.getArgs()[0] : "bungee"; 24 | 25 | switch (action) { 26 | case "server": 27 | case "s": 28 | Triton.asBungee().getBridgeManager().forwardCommand(event); 29 | return true; 30 | case "all": 31 | case "a": 32 | Triton.asBungee().getBridgeManager().forwardCommand(event); 33 | break; 34 | case "bungee": 35 | case "b": 36 | break; 37 | default: 38 | sender.sendMessageFormatted("error.bungee-reload-invalid-mode", action); 39 | return true; 40 | } 41 | } 42 | 43 | Triton.get().reload(); 44 | sender.sendMessageFormatted(isBungee ? "success.bungee-reload" : "success.reload"); 45 | return true; 46 | } 47 | 48 | @Override 49 | public List handleTabCompletion(CommandEvent event) { 50 | if (event.getArgs().length > 1 || (!Triton.isProxy() && !Triton.get().getConfig().isBungeecord())) 51 | return Collections.emptyList(); 52 | return Stream.of("server", "all", "bungee") 53 | .filter(v -> v.toLowerCase().startsWith(event.getArgs()[0].toLowerCase())) 54 | .collect(Collectors.toList()); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/commands/SetLanguageCommand.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.commands; 2 | 3 | import com.rexcantor64.triton.Triton; 4 | import com.rexcantor64.triton.api.language.Language; 5 | import com.rexcantor64.triton.commands.handler.Command; 6 | import com.rexcantor64.triton.commands.handler.CommandEvent; 7 | import com.velocitypowered.api.proxy.Player; 8 | import lombok.val; 9 | import net.md_5.bungee.api.connection.ProxiedPlayer; 10 | 11 | import java.util.List; 12 | import java.util.Locale; 13 | import java.util.UUID; 14 | import java.util.stream.Collectors; 15 | 16 | public class SetLanguageCommand implements Command { 17 | 18 | @Override 19 | public boolean handleCommand(CommandEvent event) { 20 | val sender = event.getSender(); 21 | val args = event.getArgs(); 22 | 23 | sender.assertPermission("triton.setlanguage", "multilanguageplugin.setlanguage"); 24 | 25 | if (args.length == 0) { 26 | sender.sendMessageFormatted("help.setlanguage", event.getLabel()); 27 | return true; 28 | } 29 | 30 | UUID target; 31 | val langName = args[0]; 32 | 33 | if (args.length >= 2) { 34 | sender.assertPermission("triton.setlanguage.others", "multilanguageplugin.setlanguage.others"); 35 | 36 | target = Triton.get().getPlayerUUIDFromString(args[1]); 37 | 38 | if (target == null) { 39 | sender.sendMessageFormatted("error.player-not-found-use-uuid", args[1]); 40 | return true; 41 | } 42 | } else if (sender.getUUID() != null) { 43 | target = sender.getUUID(); 44 | } else { 45 | sender.sendMessage("Only players"); 46 | return true; 47 | } 48 | 49 | val lang = Triton.get().getLanguageManager().getLanguageByName(langName, false); 50 | if (lang == null) { 51 | sender.sendMessageFormatted("error.lang-not-found", langName); 52 | return true; 53 | } 54 | 55 | if (Triton.get().getPlayerManager().hasPlayer(target)) 56 | Triton.get().getPlayerManager().get(target).setLang(lang); 57 | else { 58 | if (event.getEnvironment() == CommandEvent.Environment.SPIGOT && Triton.get().getConfig().isBungeecord()) { 59 | sender.sendMessage("Changing the language of offline players must be done through the proxy " + 60 | "console."); 61 | return true; 62 | } 63 | Triton.get().getStorage().setLanguage(target, null, lang); 64 | } 65 | 66 | if (target.equals(sender.getUUID())) 67 | sender.sendMessageFormatted("success.setlanguage", lang.getDisplayName()); 68 | else 69 | sender.sendMessageFormatted("success.setlanguage-others", args[1], lang.getDisplayName()); 70 | return true; 71 | } 72 | 73 | @Override 74 | public List handleTabCompletion(CommandEvent event) { 75 | val sender = event.getSender(); 76 | sender.assertPermission("triton.setlanguage", "multilanguageplugin.setlanguage"); 77 | 78 | if (event.getArgs().length == 2) { 79 | if (Triton.isSpigot()) { 80 | // returning "null" triggers the player list 81 | return null; 82 | } else if (Triton.isBungee()) { 83 | val partialName = event.getArgs()[1].toLowerCase(Locale.ROOT); 84 | return Triton.asBungee().getLoader().getProxy().getPlayers().stream() 85 | .map(ProxiedPlayer::getName) 86 | .filter(name -> name.toLowerCase(Locale.ROOT).startsWith(partialName)) 87 | .collect(Collectors.toList()); 88 | } else if (Triton.isVelocity()) { 89 | val partialName = event.getArgs()[1].toLowerCase(Locale.ROOT); 90 | return Triton.asVelocity().getLoader().getServer().getAllPlayers().stream() 91 | .map(Player::getUsername) 92 | .filter(name -> name.toLowerCase(Locale.ROOT).startsWith(partialName)) 93 | .collect(Collectors.toList()); 94 | } 95 | } 96 | 97 | return Triton.get().getLanguageManager().getAllLanguages().stream().map(Language::getName) 98 | .filter(name -> name.toLowerCase().startsWith(event.getArgs()[0])).collect(Collectors.toList()); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/commands/handler/BungeeCommand.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.commands.handler; 2 | 3 | import net.md_5.bungee.api.CommandSender; 4 | import net.md_5.bungee.api.plugin.Command; 5 | import net.md_5.bungee.api.plugin.TabExecutor; 6 | 7 | public class BungeeCommand extends Command implements TabExecutor { 8 | private final BungeeCommandHandler handler; 9 | 10 | 11 | public BungeeCommand(BungeeCommandHandler handler, String name, String... aliases) { 12 | super(name, null, aliases); 13 | this.handler = handler; 14 | } 15 | 16 | @Override 17 | public void execute(CommandSender sender, String[] args) { 18 | this.handler.onCommand(getName(), sender, args); 19 | } 20 | 21 | @Override 22 | public Iterable onTabComplete(CommandSender sender, String[] args) { 23 | return this.handler.onTabComplete(getName(), sender, args); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/commands/handler/BungeeCommandHandler.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.commands.handler; 2 | 3 | import lombok.val; 4 | import net.md_5.bungee.api.CommandSender; 5 | 6 | import java.util.Arrays; 7 | 8 | public class BungeeCommandHandler extends CommandHandler { 9 | 10 | void onCommand(String label, CommandSender sender, String[] args) { 11 | val commandEvent = this.buildCommandEvent(label, sender, args); 12 | super.handleCommand(commandEvent); 13 | } 14 | 15 | Iterable onTabComplete(String label, CommandSender sender, String[] args) { 16 | val commandEvent = this.buildCommandEvent(label, sender, args); 17 | return super.handleTabCompletion(commandEvent); 18 | } 19 | 20 | private CommandEvent buildCommandEvent(String label, CommandSender sender, String[] args) { 21 | val subCommand = args.length >= 1 ? args[0] : null; 22 | val subArgs = args.length >= 2 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]; 23 | 24 | return new CommandEvent( 25 | new BungeeSender(sender), 26 | subCommand, 27 | subArgs, 28 | label, 29 | CommandEvent.Environment.BUNGEE 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/commands/handler/BungeeSender.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.commands.handler; 2 | 3 | import com.rexcantor64.triton.Triton; 4 | import lombok.AllArgsConstructor; 5 | import lombok.val; 6 | import net.md_5.bungee.api.ChatColor; 7 | import net.md_5.bungee.api.CommandSender; 8 | import net.md_5.bungee.api.chat.TextComponent; 9 | import net.md_5.bungee.api.connection.ProxiedPlayer; 10 | 11 | import java.util.UUID; 12 | 13 | @AllArgsConstructor 14 | public class BungeeSender implements Sender { 15 | private final CommandSender handler; 16 | 17 | @Override 18 | public void sendMessage(String message) { 19 | handler.sendMessage(TextComponent.fromLegacyText(ChatColor.translateAlternateColorCodes('&', message))); 20 | } 21 | 22 | @Override 23 | public void sendMessageFormatted(String code, Object... args) { 24 | sendMessage(Triton.get().getMessagesConfig().getMessage(code, args)); 25 | } 26 | 27 | @Override 28 | public void assertPermission(String... permissions) { 29 | if (permissions.length == 0) throw new NoPermissionException(""); 30 | 31 | for (val permission : permissions) 32 | if (hasPermission(permission)) return; 33 | throw new NoPermissionException(permissions[0]); 34 | } 35 | 36 | @Override 37 | public boolean hasPermission(String permission) { 38 | return handler.hasPermission(permission); 39 | } 40 | 41 | @Override 42 | public UUID getUUID() { 43 | if (handler instanceof ProxiedPlayer) 44 | return ((ProxiedPlayer) handler).getUniqueId(); 45 | return null; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/commands/handler/Command.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.commands.handler; 2 | 3 | import java.util.List; 4 | 5 | public interface Command { 6 | 7 | boolean handleCommand(CommandEvent event); 8 | 9 | List handleTabCompletion(CommandEvent event); 10 | 11 | 12 | } 13 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/commands/handler/CommandEvent.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.commands.handler; 2 | 3 | import lombok.Data; 4 | import lombok.Getter; 5 | import lombok.RequiredArgsConstructor; 6 | 7 | import java.util.Arrays; 8 | import java.util.StringJoiner; 9 | 10 | @Data 11 | public class CommandEvent { 12 | private final Sender sender; 13 | private final String subCommand; 14 | private final String[] args; 15 | private final String label; 16 | private final Environment environment; 17 | 18 | /** 19 | * Join the sub command with the arguments, 20 | * in order to recreate the original sub command typed by the player. 21 | *

22 | * If the player typed "/triton setlanguage en_GB Player1", 23 | * this will return "setlanguage en_GB Player1" 24 | * 25 | * @return The sub command and arguments joined by a space. 26 | */ 27 | public String getFullSubCommand() { 28 | StringJoiner joiner = new StringJoiner(" "); 29 | joiner.setEmptyValue(""); 30 | if (this.subCommand != null) { 31 | joiner.add(this.subCommand); 32 | } 33 | if (this.args != null) { 34 | Arrays.stream(this.args).forEach(joiner::add); 35 | } 36 | return joiner.toString(); 37 | } 38 | 39 | /** 40 | * Join the arguments by a space. 41 | * 42 | * @return The arguments joined by a space. 43 | */ 44 | public String argumentsToString() { 45 | return String.join(" ", this.args); 46 | } 47 | 48 | @RequiredArgsConstructor 49 | @Getter 50 | public enum Environment { 51 | SPIGOT(false), BUNGEE(true), VELOCITY(true); 52 | 53 | private final boolean proxy; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/commands/handler/CommandHandler.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.commands.handler; 2 | 3 | import com.rexcantor64.triton.Triton; 4 | import com.rexcantor64.triton.commands.*; 5 | import lombok.val; 6 | 7 | import java.util.Collections; 8 | import java.util.HashMap; 9 | import java.util.List; 10 | import java.util.stream.Collectors; 11 | 12 | public abstract class CommandHandler { 13 | 14 | public static HashMap commands = new HashMap<>(); 15 | 16 | static { 17 | commands.put("help", new HelpCommand()); 18 | commands.put("info", new InfoCommand()); 19 | commands.put("openselector", new OpenSelectorCommand()); 20 | commands.put("getflag", new GetFlagCommand()); 21 | commands.put("setlanguage", new SetLanguageCommand()); 22 | commands.put("reload", new ReloadCommand()); 23 | commands.put("sign", new SignCommand()); 24 | commands.put("database", new DatabaseCommand()); 25 | commands.put("twin", new TwinCommand()); 26 | commands.put("loglevel", new LogLevelCommand()); 27 | } 28 | 29 | public void handleCommand(CommandEvent event) { 30 | if (event.getLabel().equalsIgnoreCase("twin")) 31 | event = new CommandEvent(event.getSender(), 32 | "twin", 33 | event.getSubCommand() == null ? 34 | new String[0] : 35 | mergeSubcommandWithArgs(event.getSubCommand(), event.getArgs()), 36 | "twin", 37 | event.getEnvironment() 38 | ); 39 | 40 | try { 41 | // No args given 42 | Command command = commands.get(event.getSubCommand() == null ? "openselector" : event.getSubCommand()); 43 | 44 | if (command == null) 45 | command = commands.get("help"); 46 | 47 | if (!command.handleCommand(event)) { 48 | if (Triton.isBungee()) 49 | Triton.asBungee().getBridgeManager().forwardCommand(event); 50 | if (Triton.isVelocity()) 51 | Triton.asVelocity().getBridgeManager().forwardCommand(event); 52 | } 53 | 54 | } catch (NoPermissionException e) { 55 | event.getSender().sendMessageFormatted("error.no-permission", e.getPermission()); 56 | } 57 | } 58 | 59 | protected List handleTabCompletion(CommandEvent event) { 60 | if (event.getLabel().equalsIgnoreCase("twin")) { 61 | return Collections.emptyList(); 62 | } 63 | 64 | val subCommand = event.getSubCommand(); 65 | try { 66 | if (subCommand == null || event.getArgs().length == 0) { 67 | return commands.keySet().stream().filter(cmd -> subCommand != null && cmd.startsWith(subCommand)) 68 | .collect(Collectors.toList()); 69 | } 70 | 71 | val command = commands.get(subCommand); 72 | if (command == null) { 73 | return Collections.emptyList(); 74 | } 75 | 76 | return command.handleTabCompletion(event); 77 | } catch (NoPermissionException e) { 78 | return Collections.emptyList(); 79 | } 80 | } 81 | 82 | private String[] mergeSubcommandWithArgs(String subCommand, String[] args) { 83 | val newLength = args.length + 1; 84 | val newArray = new String[newLength]; 85 | newArray[0] = subCommand; 86 | for (int i = 0; i < args.length; ++i) 87 | newArray[i + 1] = args[i]; 88 | return newArray; 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/commands/handler/NoPermissionException.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.commands.handler; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | @AllArgsConstructor 7 | public class NoPermissionException extends RuntimeException { 8 | @Getter 9 | private final String permission; 10 | } 11 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/commands/handler/Sender.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.commands.handler; 2 | 3 | import java.util.UUID; 4 | 5 | public interface Sender { 6 | 7 | void sendMessage(String message); 8 | 9 | void sendMessageFormatted(String code, Object... args); 10 | 11 | void assertPermission(String... permissions); 12 | 13 | boolean hasPermission(String permission); 14 | 15 | UUID getUUID(); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/commands/handler/SpigotCommandHandler.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.commands.handler; 2 | 3 | import lombok.val; 4 | import org.bukkit.command.Command; 5 | import org.bukkit.command.CommandExecutor; 6 | import org.bukkit.command.CommandSender; 7 | import org.bukkit.command.TabCompleter; 8 | 9 | import java.util.Arrays; 10 | import java.util.List; 11 | 12 | public class SpigotCommandHandler extends CommandHandler implements CommandExecutor, TabCompleter { 13 | 14 | @Override 15 | public boolean onCommand(CommandSender commandSender, Command command, String s, String[] strings) { 16 | val subCommand = strings.length >= 1 ? strings[0] : null; 17 | val args = strings.length >= 2 ? Arrays.copyOfRange(strings, 1, strings.length) : new String[0]; 18 | super.handleCommand(new CommandEvent(new SpigotSender(commandSender), subCommand, args, s, 19 | CommandEvent.Environment.SPIGOT)); 20 | return true; 21 | } 22 | 23 | @Override 24 | public List onTabComplete(CommandSender commandSender, Command command, String s, String[] strings) { 25 | val subCommand = strings.length >= 1 ? strings[0] : null; 26 | val args = strings.length >= 2 ? Arrays.copyOfRange(strings, 1, strings.length) : new String[0]; 27 | return super.handleTabCompletion(new CommandEvent(new SpigotSender(commandSender), subCommand, args, s, 28 | CommandEvent.Environment.SPIGOT)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/commands/handler/SpigotSender.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.commands.handler; 2 | 3 | import com.rexcantor64.triton.Triton; 4 | import lombok.AllArgsConstructor; 5 | import lombok.val; 6 | import net.md_5.bungee.api.ChatColor; 7 | import org.bukkit.command.CommandSender; 8 | import org.bukkit.entity.Player; 9 | 10 | import java.util.UUID; 11 | 12 | @AllArgsConstructor 13 | public class SpigotSender implements Sender { 14 | private final CommandSender handler; 15 | 16 | @Override 17 | public void sendMessage(String message) { 18 | handler.sendMessage(ChatColor.translateAlternateColorCodes('&', message)); 19 | } 20 | 21 | @Override 22 | public void sendMessageFormatted(String code, Object... args) { 23 | sendMessage(Triton.get().getMessagesConfig().getMessage(code, args)); 24 | } 25 | 26 | @Override 27 | public void assertPermission(String... permissions) { 28 | if (permissions.length == 0) throw new NoPermissionException(""); 29 | 30 | for (val permission : permissions) 31 | if (hasPermission(permission)) return; 32 | throw new NoPermissionException(permissions[0]); 33 | } 34 | 35 | @Override 36 | public boolean hasPermission(String permission) { 37 | return handler.hasPermission(permission); 38 | } 39 | 40 | @Override 41 | public UUID getUUID() { 42 | if (handler instanceof Player) 43 | return ((Player) handler).getUniqueId(); 44 | return null; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/commands/handler/VelocityCommandHandler.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.commands.handler; 2 | 3 | import com.velocitypowered.api.command.SimpleCommand; 4 | import lombok.val; 5 | 6 | import java.util.Arrays; 7 | import java.util.List; 8 | 9 | public class VelocityCommandHandler extends CommandHandler implements SimpleCommand { 10 | 11 | @Override 12 | public void execute(Invocation invocation) { 13 | super.handleCommand(buildCommandEvent(invocation, null)); 14 | } 15 | 16 | @Override 17 | public List suggest(Invocation invocation) { 18 | return super.handleTabCompletion(buildCommandEvent(invocation, "")); 19 | } 20 | 21 | private CommandEvent buildCommandEvent(Invocation invocation, String defaultSubcommand) { 22 | val args = invocation.arguments(); 23 | val subCommand = args.length >= 1 ? args[0] : defaultSubcommand; 24 | val subArgs = args.length >= 2 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]; 25 | return new CommandEvent(new VelocitySender(invocation.source()), subCommand, subArgs, invocation 26 | .alias(), CommandEvent.Environment.VELOCITY); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/commands/handler/VelocitySender.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.commands.handler; 2 | 3 | import com.rexcantor64.triton.Triton; 4 | import com.velocitypowered.api.command.CommandSource; 5 | import com.velocitypowered.api.proxy.Player; 6 | import lombok.AllArgsConstructor; 7 | import lombok.val; 8 | import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; 9 | 10 | import java.util.UUID; 11 | 12 | @AllArgsConstructor 13 | public class VelocitySender implements Sender { 14 | private final CommandSource handler; 15 | private final LegacyComponentSerializer serializer = LegacyComponentSerializer.builder().character('&') 16 | .extractUrls().build(); 17 | 18 | @Override 19 | public void sendMessage(String message) { 20 | handler.sendMessage(serializer.deserialize(message)); 21 | } 22 | 23 | @Override 24 | public void sendMessageFormatted(String code, Object... args) { 25 | sendMessage(Triton.get().getMessagesConfig().getMessage(code, args)); 26 | } 27 | 28 | @Override 29 | public void assertPermission(String... permissions) { 30 | if (permissions.length == 0) throw new NoPermissionException(""); 31 | 32 | for (val permission : permissions) 33 | if (hasPermission(permission)) return; 34 | throw new NoPermissionException(permissions[0]); 35 | } 36 | 37 | @Override 38 | public boolean hasPermission(String permission) { 39 | return handler.hasPermission(permission); 40 | } 41 | 42 | @Override 43 | public UUID getUUID() { 44 | if (handler instanceof Player) 45 | return ((Player) handler).getUniqueId(); 46 | return null; 47 | } 48 | } -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/config/MessagesConfig.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.config; 2 | 3 | import com.rexcantor64.triton.Triton; 4 | import com.rexcantor64.triton.config.interfaces.ConfigurationProvider; 5 | import com.rexcantor64.triton.config.interfaces.YamlConfiguration; 6 | import com.rexcantor64.triton.utils.YAMLUtils; 7 | import lombok.val; 8 | import lombok.var; 9 | 10 | import java.util.Collections; 11 | import java.util.HashMap; 12 | import java.util.List; 13 | import java.util.Objects; 14 | import java.util.stream.Collectors; 15 | 16 | public class MessagesConfig { 17 | 18 | private HashMap messages = new HashMap<>(); 19 | private HashMap defaultMessages = new HashMap<>(); 20 | 21 | public void setup() { 22 | val conf = Triton.get().loadYAML("messages", "messages"); 23 | messages = YAMLUtils.deepToMap(conf, ""); 24 | 25 | val defaultConf = ConfigurationProvider.getProvider(YamlConfiguration.class) 26 | .load(Triton.get().getLoader().getResourceAsStream("messages.yml")); 27 | defaultMessages = YAMLUtils.deepToMap(defaultConf, ""); 28 | 29 | if (messages.size() != defaultMessages.size()) { 30 | Triton.get().getLogger() 31 | .logWarning("It seems like your messages.yml file is outdated"); 32 | Triton.get().getLogger() 33 | .logWarning("You can get an up-to-date copy at https://triton.rexcantor64.com/messagesyml"); 34 | } 35 | } 36 | 37 | private String getString(String code) { 38 | Object msg = messages.get(code); 39 | if (msg == null) 40 | msg = defaultMessages.get(code); 41 | 42 | return Objects.toString(msg, "Unknown message"); 43 | } 44 | 45 | public String getMessage(String code, Object... args) { 46 | String s = getString(code); 47 | for (int i = 0; i < args.length; i++) 48 | if (args[i] != null) 49 | s = s.replace("%" + (i + 1), args[i].toString()); 50 | return s; 51 | } 52 | 53 | public List getMessageList(String code) { 54 | Object list = messages.get(code); 55 | if (list == null) 56 | list = defaultMessages.get(code); 57 | 58 | if (list instanceof List) 59 | return ((List) list).stream().map(Objects::toString).collect(Collectors.toList()); 60 | return Collections.singletonList("Unknown message"); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/config/interfaces/ConfigurationProvider.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.config.interfaces; 2 | 3 | import java.io.*; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | public abstract class ConfigurationProvider { 8 | 9 | private static final Map, ConfigurationProvider> providers = new HashMap<>(); 10 | 11 | static { 12 | providers.put(YamlConfiguration.class, new YamlConfiguration()); 13 | } 14 | 15 | public static ConfigurationProvider getProvider(Class provider) { 16 | return providers.get(provider); 17 | } 18 | 19 | /*------------------------------------------------------------------------*/ 20 | public abstract void save(Configuration config, File file) throws IOException; 21 | 22 | public abstract void save(Configuration config, Writer writer); 23 | 24 | public abstract Configuration load(File file) throws IOException; 25 | 26 | public abstract Configuration load(File file, Configuration defaults) throws IOException; 27 | 28 | public abstract Configuration load(Reader reader); 29 | 30 | public abstract Configuration load(Reader reader, Configuration defaults); 31 | 32 | public abstract Configuration load(InputStream is); 33 | 34 | public abstract Configuration load(InputStream is, Configuration defaults); 35 | 36 | public abstract Configuration load(String string); 37 | 38 | public abstract Configuration load(String string, Configuration defaults); 39 | } 40 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/guiapi/Gui.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.guiapi; 2 | 3 | import com.google.common.collect.Maps; 4 | import com.rexcantor64.triton.Triton; 5 | import org.bukkit.Bukkit; 6 | import org.bukkit.ChatColor; 7 | import org.bukkit.entity.Player; 8 | import org.bukkit.inventory.Inventory; 9 | import org.bukkit.inventory.InventoryHolder; 10 | 11 | import java.util.HashMap; 12 | import java.util.Map.Entry; 13 | 14 | public class Gui implements InventoryHolder { 15 | 16 | int rows; 17 | String title; 18 | boolean blocked = true; 19 | int currentPage; 20 | int maxPages; 21 | Inventory inv; 22 | private HashMap items = Maps.newHashMap(); 23 | 24 | public Gui(int rows, String title) { 25 | this.rows = rows; 26 | this.title = title; 27 | } 28 | 29 | public Gui(int rows) { 30 | this(rows, ""); 31 | } 32 | 33 | public Gui(String title) { 34 | this(1, title); 35 | } 36 | 37 | public Gui() { 38 | this(1, ""); 39 | } 40 | 41 | public int nextIndex() { 42 | for (int i = 0; i < getSize(); i++) { 43 | if (!items.containsKey(i)) 44 | return i; 45 | } 46 | return -1; 47 | } 48 | 49 | public void addButton(GuiButton button) { 50 | int index = nextIndex(); 51 | 52 | if (index == -1) 53 | throw new RuntimeException("Inventory cannot be full!"); 54 | setButton(index, button); 55 | } 56 | 57 | public void setButton(int position, GuiButton button) { 58 | if (position > getSize()) 59 | throw new IllegalArgumentException("Position cannot be bigger than the size!"); 60 | items.put(position, button); 61 | } 62 | 63 | public void removeButton(int position) { 64 | items.remove(position); 65 | } 66 | 67 | public void clearGuiWithoutButtons() { 68 | for (int i = 0; i < getSize() - 9; i++) { 69 | items.remove(i); 70 | } 71 | } 72 | 73 | public GuiButton getButton(int position) { 74 | return items.get(position); 75 | } 76 | 77 | public int getSize() { 78 | return rows * 9; 79 | } 80 | 81 | public void open(Player p) { 82 | Inventory inv = Bukkit.createInventory(this, getSize(), ChatColor.translateAlternateColorCodes('&', title)); 83 | for (Entry entry : items.entrySet()) 84 | inv.setItem(entry.getKey(), entry.getValue().getItemStack()); 85 | p.openInventory(inv); 86 | Triton.get().getGuiManager().add(inv, new OpenGuiInfo(this)); 87 | this.inv = inv; 88 | } 89 | 90 | public boolean isBlocked() { 91 | return blocked; 92 | } 93 | 94 | public void setBlocked(boolean blocked) { 95 | this.blocked = blocked; 96 | } 97 | 98 | public void setTitle(String title) { 99 | this.title = title; 100 | } 101 | 102 | public int getCurrentPage() { 103 | return this.currentPage; 104 | } 105 | 106 | public void setCurrentPage(int current) { 107 | this.currentPage = current; 108 | } 109 | 110 | public int getMaxPages() { 111 | return this.maxPages; 112 | } 113 | 114 | public void setMaxPages(int max) { 115 | this.maxPages = max; 116 | } 117 | 118 | @Override 119 | public Inventory getInventory() { 120 | return inv; 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/guiapi/GuiButton.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.guiapi; 2 | 3 | import org.bukkit.event.inventory.InventoryClickEvent; 4 | import org.bukkit.inventory.ItemStack; 5 | 6 | import java.util.function.Consumer; 7 | 8 | public class GuiButton { 9 | 10 | private ItemStack is; 11 | private GuiButtonClickEvent event; 12 | 13 | public GuiButton(ItemStack is) { 14 | this.is = is; 15 | } 16 | 17 | public GuiButton setListener(final Consumer event) { 18 | this.event = new GuiButtonClickEvent() { 19 | 20 | @Override 21 | public void onClick(InventoryClickEvent e) { 22 | event.accept(e); 23 | } 24 | }; 25 | return this; 26 | } 27 | 28 | public ItemStack getItemStack() { 29 | return is; 30 | } 31 | 32 | public GuiButtonClickEvent getEvent() { 33 | return event; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/guiapi/GuiButtonClickEvent.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.guiapi; 2 | 3 | import org.bukkit.event.inventory.InventoryClickEvent; 4 | 5 | public abstract class GuiButtonClickEvent { 6 | 7 | public abstract void onClick(InventoryClickEvent event); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/guiapi/GuiManager.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.guiapi; 2 | 3 | import com.google.common.collect.Maps; 4 | import org.bukkit.entity.Player; 5 | import org.bukkit.event.EventHandler; 6 | import org.bukkit.event.Listener; 7 | import org.bukkit.event.inventory.InventoryClickEvent; 8 | import org.bukkit.event.inventory.InventoryCloseEvent; 9 | import org.bukkit.inventory.Inventory; 10 | 11 | import java.util.HashMap; 12 | 13 | public class GuiManager implements Listener { 14 | 15 | private HashMap open = Maps.newHashMap(); 16 | 17 | public void add(Inventory inv, OpenGuiInfo gui) { 18 | open.put(inv, gui); 19 | } 20 | 21 | @EventHandler 22 | public void onClick(InventoryClickEvent e) { 23 | OpenGuiInfo guiInfo = open.get(e.getInventory()); 24 | if (guiInfo == null) return; 25 | Gui gui = guiInfo.getGui(); 26 | if (gui.isBlocked()) 27 | e.setCancelled(true); 28 | if (e.getClickedInventory() == null) 29 | return; 30 | GuiButton btn = gui.getButton(e.getRawSlot()); 31 | if (gui instanceof ScrollableGui) { 32 | ScrollableGui sGui = (ScrollableGui) gui; 33 | if (sGui.getMaxPages() != 1) 34 | if (e.getRawSlot() >= 45) { 35 | if (e.getRawSlot() == 45) 36 | if (guiInfo.getCurrentPage() > 1) 37 | sGui.open((Player) e.getWhoClicked(), guiInfo.getCurrentPage() - 1); 38 | else if (e.getRawSlot() == 54) 39 | if (guiInfo.getCurrentPage() < sGui.getMaxPages()) 40 | sGui.open((Player) e.getWhoClicked(), guiInfo.getCurrentPage() + 1); 41 | return; 42 | } 43 | btn = sGui.getButton(e.getRawSlot(), guiInfo.getCurrentPage()); 44 | } 45 | if (btn == null) 46 | return; 47 | btn.getEvent().onClick(e); 48 | } 49 | 50 | @EventHandler 51 | public void onClose(InventoryCloseEvent e) { 52 | open.remove(e.getInventory()); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/guiapi/OpenGuiInfo.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.guiapi; 2 | 3 | public class OpenGuiInfo { 4 | 5 | private Gui gui; 6 | private int currentPage; 7 | 8 | public OpenGuiInfo(Gui gui) { 9 | this.gui = gui; 10 | this.currentPage = -1; 11 | } 12 | 13 | public OpenGuiInfo(ScrollableGui gui, int currentPage) { 14 | this.gui = gui; 15 | this.currentPage = currentPage; 16 | } 17 | 18 | public Gui getGui() { 19 | return gui; 20 | } 21 | 22 | public int getCurrentPage() { 23 | return currentPage; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/language/ExecutableCommand.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.language; 2 | 3 | import com.rexcantor64.triton.Triton; 4 | import com.rexcantor64.triton.utils.StringUtils; 5 | import lombok.Getter; 6 | import lombok.RequiredArgsConstructor; 7 | import lombok.ToString; 8 | 9 | import java.util.ArrayList; 10 | import java.util.Arrays; 11 | import java.util.List; 12 | 13 | @Getter 14 | @RequiredArgsConstructor 15 | @ToString 16 | public class ExecutableCommand { 17 | 18 | private final String cmd; 19 | private final Type type; 20 | private boolean universal = true; 21 | private List servers = new ArrayList<>(); 22 | 23 | private ExecutableCommand(String cmd, Type type, List servers) { 24 | this.cmd = cmd; 25 | this.type = type; 26 | this.universal = false; 27 | this.servers = servers; 28 | } 29 | 30 | public static ExecutableCommand parse(String input) { 31 | String[] inputSplit = input.split(":"); 32 | if (inputSplit.length < 2) { 33 | Triton.get() 34 | .getLogger() 35 | .logWarning("Language command '%1' doesn't have a type. Using type 'PLAYER' by default.", input); 36 | return new ExecutableCommand(input, Type.PLAYER); 37 | } 38 | Type type = null; 39 | for (Type t : Type.values()) { 40 | if (inputSplit[0].equals(t.name())) { 41 | type = t; 42 | break; 43 | } 44 | } 45 | if (type == null) { 46 | Triton.get().getLogger() 47 | .logWarning("Language command '%1' has invalid type '%2'. Using the default type 'PLAYER'.", 48 | input, inputSplit[0]); 49 | type = Type.PLAYER; 50 | } 51 | if (inputSplit.length < 3 || Triton.isSpigot()) 52 | return new ExecutableCommand(StringUtils 53 | .join(":", Arrays.copyOfRange(inputSplit, 1, inputSplit.length)), type); 54 | if (inputSplit[1].isEmpty()) 55 | return new ExecutableCommand(StringUtils 56 | .join(":", Arrays.copyOfRange(inputSplit, 2, inputSplit.length)), type); 57 | 58 | return new ExecutableCommand(StringUtils 59 | .join(":", Arrays.copyOfRange(inputSplit, 2, inputSplit.length)), type, Arrays 60 | .asList(inputSplit[1].split(","))); 61 | } 62 | 63 | public enum Type { 64 | PLAYER, SERVER, BUNGEE, BUNGEE_PLAYER 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/language/Language.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.language; 2 | 3 | import com.rexcantor64.triton.banners.Banner; 4 | import lombok.Data; 5 | import lombok.ToString; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Collections; 9 | import java.util.List; 10 | import java.util.Objects; 11 | 12 | @Data 13 | public class Language implements com.rexcantor64.triton.api.language.Language { 14 | 15 | private String name; 16 | private List minecraftCodes; 17 | private String rawDisplayName; 18 | private List fallbackLanguages = Collections.emptyList(); 19 | private transient String displayName; 20 | @ToString.Exclude 21 | private transient Banner banner; 22 | private String flagCode; 23 | private List cmds = new ArrayList<>(); 24 | 25 | public Language(String name, String flagCode, List minecraftCodes, String displayName, List fallbackLanguages, List cmds) { 26 | this.name = name; 27 | this.rawDisplayName = displayName; 28 | this.minecraftCodes = minecraftCodes; 29 | this.flagCode = flagCode; 30 | if (fallbackLanguages != null) { 31 | this.fallbackLanguages = Collections.unmodifiableList(fallbackLanguages); 32 | } 33 | if (cmds != null) 34 | for (String cmd : cmds) 35 | this.cmds.add(ExecutableCommand.parse(cmd)); 36 | computeProperties(); 37 | } 38 | 39 | public void computeProperties() { 40 | this.displayName = this.rawDisplayName; 41 | this.banner = new Banner(flagCode, this.displayName); 42 | // If loading with Gson, this might be set to null 43 | if (fallbackLanguages == null) { 44 | fallbackLanguages = Collections.emptyList(); 45 | } 46 | } 47 | 48 | @Override 49 | public boolean equals(Object o) { 50 | if (this == o) return true; 51 | if (o == null || getClass() != o.getClass()) return false; 52 | Language language = (Language) o; 53 | return Objects.equals(name, language.name); 54 | } 55 | 56 | @Override 57 | public int hashCode() { 58 | return Objects.hash(name, minecraftCodes, rawDisplayName, displayName, banner, flagCode, cmds); 59 | } 60 | 61 | @Override 62 | public Language getLanguage() { 63 | return this; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/language/item/Collection.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.language.item; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | @Data 9 | public class Collection { 10 | private CollectionMetadata metadata = new CollectionMetadata(); 11 | private List items = new ArrayList<>(); 12 | 13 | @Data 14 | public static class CollectionMetadata { 15 | private boolean blacklist = true; 16 | private List servers = new ArrayList<>(); 17 | } 18 | } 19 | 20 | 21 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/language/item/LanguageItem.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.language.item; 2 | 3 | import lombok.Data; 4 | import lombok.Getter; 5 | import lombok.RequiredArgsConstructor; 6 | 7 | @Data 8 | public abstract class LanguageItem { 9 | 10 | private String key; 11 | private TWINData twinData = null; 12 | 13 | public abstract LanguageItemType getType(); 14 | 15 | @RequiredArgsConstructor 16 | @Getter 17 | public enum LanguageItemType { 18 | TEXT("text"), SIGN("sign"); 19 | 20 | private final String name; 21 | 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/language/item/LanguageSign.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.language.item; 2 | 3 | import lombok.Data; 4 | import lombok.EqualsAndHashCode; 5 | 6 | import java.util.HashMap; 7 | import java.util.List; 8 | 9 | @Data 10 | @EqualsAndHashCode(callSuper = true) 11 | public class LanguageSign extends LanguageItem { 12 | 13 | private List locations; 14 | private HashMap lines; 15 | 16 | @Override 17 | public LanguageItemType getType() { 18 | return LanguageItemType.SIGN; 19 | } 20 | 21 | 22 | public boolean hasLocation(com.rexcantor64.triton.api.language.SignLocation loc) { 23 | return hasLocation((SignLocation) loc, false); 24 | } 25 | 26 | public boolean hasLocation(SignLocation loc, boolean checkServer) { 27 | if (loc != null && locations != null) 28 | for (SignLocation l : locations) 29 | if (checkServer ? loc.equalsWithServer(l) : loc.equals(l)) return true; 30 | return false; 31 | } 32 | 33 | public String[] getLines(String languageName) { 34 | return lines.get(languageName); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/language/item/LanguageText.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.language.item; 2 | 3 | import lombok.Data; 4 | import lombok.EqualsAndHashCode; 5 | import lombok.val; 6 | 7 | import java.util.HashMap; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.regex.Pattern; 11 | 12 | @Data 13 | @EqualsAndHashCode(callSuper = true) 14 | public class LanguageText extends LanguageItem { 15 | 16 | private static final Pattern PATTERN = Pattern.compile("%(\\d+)"); 17 | private final HashMap languagesRegex = new HashMap<>(); 18 | private HashMap languages; 19 | private Boolean blacklist = null; 20 | private List servers = null; 21 | private List patterns; 22 | 23 | @Override 24 | public LanguageItemType getType() { 25 | return LanguageItemType.TEXT; 26 | } 27 | 28 | public String getMessage(String languageName) { 29 | return languages == null ? null : languages.get(languageName); 30 | } 31 | 32 | public String getMessageRegex(String languageName) { 33 | return languagesRegex.get(languageName); 34 | } 35 | 36 | public void generateRegexStrings() { 37 | languagesRegex.clear(); 38 | if (languages != null) { 39 | for (Map.Entry entry : languages.entrySet()) { 40 | if (entry.getValue() == null) continue; 41 | 42 | languagesRegex 43 | .put(entry.getKey(), PATTERN.matcher(entry.getValue().replace("$", "\\$")).replaceAll("\\$$1")); 44 | } 45 | } 46 | } 47 | 48 | public boolean belongsToServer(Collection.CollectionMetadata metadata, String serverName) { 49 | val blacklist = this.blacklist == null ? metadata.isBlacklist() : this.blacklist; 50 | val servers = this.servers == null ? metadata.getServers() : this.servers; 51 | if (blacklist) return !servers.contains(serverName); 52 | return servers.contains(serverName); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/language/item/SignLocation.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.language.item; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | 7 | import java.util.Objects; 8 | 9 | @Data 10 | @AllArgsConstructor 11 | public class SignLocation implements com.rexcantor64.triton.api.language.SignLocation { 12 | @EqualsAndHashCode.Exclude 13 | private String server; 14 | private String world; 15 | private int x; 16 | private int y; 17 | private int z; 18 | 19 | public SignLocation(String name, int x, int y, int z) { 20 | this(null, name, x, y, z); 21 | } 22 | 23 | public boolean equalsWithServer(Object that) { 24 | return this.equals(that) && Objects.equals(server, ((SignLocation) that).server); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/language/item/TWINData.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.language.item; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.UUID; 6 | 7 | @Data 8 | public class TWINData { 9 | private UUID id; 10 | private long dateCreated; 11 | private long dateUpdated; 12 | private boolean archived; 13 | private String[] tags; 14 | private String description; 15 | 16 | /** 17 | * Returns true if something changed, otherwise returns false 18 | */ 19 | public boolean ensureValid() { 20 | if (id != null && dateCreated > 0 && dateUpdated > 0 && tags != null) return false; 21 | if (id == null) id = UUID.randomUUID(); 22 | if (dateCreated <= 0) dateCreated = System.currentTimeMillis(); 23 | if (dateUpdated <= 0) dateUpdated = System.currentTimeMillis(); 24 | if (tags == null) tags = new String[0]; 25 | return true; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/language/item/serializers/CollectionSerializer.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.language.item.serializers; 2 | 3 | import com.google.gson.*; 4 | import com.rexcantor64.triton.language.item.Collection; 5 | import com.rexcantor64.triton.language.item.LanguageItem; 6 | import com.rexcantor64.triton.language.item.LanguageSign; 7 | import com.rexcantor64.triton.language.item.LanguageText; 8 | import lombok.val; 9 | 10 | import java.io.Reader; 11 | import java.lang.reflect.Type; 12 | import java.util.Arrays; 13 | import java.util.Objects; 14 | import java.util.stream.Collectors; 15 | 16 | public class CollectionSerializer implements JsonDeserializer { 17 | 18 | private static final Gson gson = new GsonBuilder() 19 | .setPrettyPrinting() 20 | .registerTypeAdapter(Collection.class, new CollectionSerializer()) 21 | .registerTypeAdapter(LanguageItem.class, new LanguageItemSerializer()) 22 | .registerTypeAdapter(LanguageText.class, new LanguageTextSerializer()) 23 | .registerTypeAdapter(LanguageSign.class, new LanguageSignSerializer()) 24 | .create(); 25 | 26 | public static Collection parse(Reader json) { 27 | return gson.fromJson(json, Collection.class); 28 | } 29 | 30 | public static void toJson(Collection collection, Appendable reader) { 31 | gson.toJson(collection, reader); 32 | } 33 | 34 | @Override 35 | public Collection deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException { 36 | val collection = new Collection(); 37 | 38 | JsonArray items; 39 | if (json.isJsonArray()) { 40 | items = json.getAsJsonArray(); 41 | } else if (json.isJsonObject()) { 42 | items = json.getAsJsonObject().getAsJsonArray("items"); 43 | final Collection.CollectionMetadata metadata = context.deserialize( 44 | json.getAsJsonObject().get("metadata"), 45 | Collection.CollectionMetadata.class 46 | ); 47 | if (metadata != null) { 48 | collection.setMetadata(metadata); 49 | } 50 | } else { 51 | throw new JsonParseException("Invalid JSON while deserializing Collection"); 52 | } 53 | 54 | collection.setItems(Arrays.stream((LanguageItem[]) context.deserialize(items, LanguageItem[].class)) 55 | .filter(Objects::nonNull).collect(Collectors.toList())); 56 | 57 | return collection; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/language/item/serializers/LanguageItemSerializer.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.language.item.serializers; 2 | 3 | import com.google.gson.*; 4 | import com.rexcantor64.triton.language.item.LanguageItem; 5 | import com.rexcantor64.triton.language.item.LanguageSign; 6 | import com.rexcantor64.triton.language.item.LanguageText; 7 | import com.rexcantor64.triton.language.item.TWINData; 8 | import lombok.val; 9 | 10 | import java.lang.reflect.Type; 11 | 12 | public class LanguageItemSerializer implements JsonDeserializer { 13 | 14 | static void deserialize(JsonObject json, LanguageItem item, JsonDeserializationContext context) throws JsonParseException { 15 | val key = json.get("key"); 16 | if (key == null || !key.isJsonPrimitive()) throw new JsonParseException("Translation requires a key"); 17 | item.setKey(key.getAsString()); 18 | 19 | val twinData = json.get("_twin"); 20 | if (twinData != null && twinData.isJsonObject()) 21 | item.setTwinData(context.deserialize(twinData, TWINData.class)); 22 | } 23 | 24 | static void serialize(LanguageItem item, JsonObject json, JsonSerializationContext context) { 25 | json.addProperty("key", item.getKey()); 26 | json.addProperty("type", item.getType().getName()); 27 | 28 | if (item.getTwinData() != null) 29 | json.add("_twin", context.serialize(item.getTwinData(), TWINData.class)); 30 | } 31 | 32 | @Override 33 | public LanguageItem deserialize(JsonElement json, Type t, JsonDeserializationContext context) throws JsonParseException { 34 | val obj = json.getAsJsonObject(); 35 | val typeElement = obj.get("type"); 36 | if (typeElement == null || !typeElement.isJsonPrimitive()) throw new JsonParseException("Translation type is not present: " + json); 37 | val type = typeElement.getAsString(); 38 | 39 | if (type.equalsIgnoreCase("text")) 40 | return context.deserialize(json, LanguageText.class); 41 | if (type.equalsIgnoreCase("sign")) 42 | return context.deserialize(json, LanguageSign.class); 43 | 44 | throw new JsonParseException("Invalid translation type: " + type); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/language/item/serializers/LanguageSignSerializer.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.language.item.serializers; 2 | 3 | import com.google.gson.*; 4 | import com.google.gson.reflect.TypeToken; 5 | import com.rexcantor64.triton.language.item.LanguageSign; 6 | import com.rexcantor64.triton.language.item.SignLocation; 7 | import lombok.val; 8 | 9 | import java.lang.reflect.Type; 10 | import java.util.HashMap; 11 | import java.util.List; 12 | 13 | public class LanguageSignSerializer implements JsonSerializer, JsonDeserializer { 14 | private static final Type LANGUAGES_TYPE = new TypeToken>() { 15 | }.getType(); 16 | private static final Type LOCATIONS_TYPE = new TypeToken>() { 17 | }.getType(); 18 | 19 | @Override 20 | public LanguageSign deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException { 21 | val item = new LanguageSign(); 22 | val obj = json.getAsJsonObject(); 23 | 24 | LanguageItemSerializer.deserialize(obj, item, context); 25 | 26 | if (obj.has("lines")) 27 | item.setLines(context.deserialize(obj.get("lines"), LANGUAGES_TYPE)); 28 | 29 | if (obj.has("locations")) 30 | item.setLocations(context.deserialize(obj.get("locations"), LOCATIONS_TYPE)); 31 | 32 | return item; 33 | } 34 | 35 | @Override 36 | public JsonElement serialize(LanguageSign item, Type type, JsonSerializationContext context) { 37 | val json = new JsonObject(); 38 | 39 | LanguageItemSerializer.serialize(item, json, context); 40 | 41 | if (item.getLines() != null) 42 | json.add("lines", context.serialize(item.getLines(), LANGUAGES_TYPE)); 43 | 44 | if (item.getLocations() != null) 45 | json.add("locations", context.serialize(item.getLocations(), LOCATIONS_TYPE)); 46 | 47 | return json; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/language/item/serializers/LanguageTextSerializer.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.language.item.serializers; 2 | 3 | import com.google.gson.*; 4 | import com.google.gson.reflect.TypeToken; 5 | import com.rexcantor64.triton.language.item.LanguageText; 6 | import lombok.val; 7 | 8 | import java.lang.reflect.Type; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | 12 | public class LanguageTextSerializer implements JsonSerializer, JsonDeserializer { 13 | private static final Type TEXT_TYPE = new TypeToken>() { 14 | }.getType(); 15 | private static final Type STRING_LIST_TYPE = new TypeToken>() { 16 | }.getType(); 17 | 18 | @Override 19 | public LanguageText deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException { 20 | val item = new LanguageText(); 21 | val obj = json.getAsJsonObject(); 22 | 23 | LanguageItemSerializer.deserialize(obj, item, context); 24 | 25 | if (obj.has("languages")) 26 | item.setLanguages(context.deserialize(obj.get("languages"), TEXT_TYPE)); 27 | if (obj.has("patterns")) 28 | item.setPatterns(context.deserialize(obj.get("patterns"), STRING_LIST_TYPE)); 29 | 30 | if (obj.has("blacklist")) 31 | item.setBlacklist(obj.get("blacklist").getAsBoolean()); 32 | if (obj.has("servers")) 33 | item.setServers(context.deserialize(obj.get("servers"), STRING_LIST_TYPE)); 34 | 35 | item.generateRegexStrings(); 36 | return item; 37 | } 38 | 39 | @Override 40 | public JsonElement serialize(LanguageText item, Type type, JsonSerializationContext context) { 41 | val json = new JsonObject(); 42 | 43 | LanguageItemSerializer.serialize(item, json, context); 44 | 45 | if (item.getLanguages() != null) 46 | json.add("languages", context.serialize(item.getLanguages(), TEXT_TYPE)); 47 | if (item.getPatterns() != null && item.getPatterns().size() > 0) 48 | json.add("patterns", context.serialize(item.getPatterns(), STRING_LIST_TYPE)); 49 | 50 | if (item.getBlacklist() != null) 51 | json.addProperty("blacklist", item.getBlacklist()); 52 | if (item.getServers() != null && (item.getServers().size() > 0 || item.getBlacklist() == Boolean.FALSE)) 53 | json.add("servers", context.serialize(item.getServers(), STRING_LIST_TYPE)); 54 | 55 | return json; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/language/localized/StringLocale.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.language.localized; 2 | 3 | import com.rexcantor64.triton.Triton; 4 | import com.rexcantor64.triton.api.language.Language; 5 | import com.rexcantor64.triton.api.language.Localized; 6 | import lombok.Data; 7 | import lombok.RequiredArgsConstructor; 8 | 9 | @Data 10 | @RequiredArgsConstructor 11 | public class StringLocale implements Localized { 12 | 13 | private final String languageId; 14 | private Language cachedLanguage; 15 | 16 | @Override 17 | public Language getLanguage() { 18 | if (cachedLanguage == null) { 19 | cachedLanguage = Triton.get().getLanguageManager().getLanguageByName(languageId, true); 20 | } 21 | return cachedLanguage; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/listeners/BukkitListener.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.listeners; 2 | 3 | import com.rexcantor64.triton.Triton; 4 | import com.rexcantor64.triton.language.LanguageParser; 5 | import com.rexcantor64.triton.player.SpigotLanguagePlayer; 6 | import lombok.val; 7 | import org.bukkit.ChatColor; 8 | import org.bukkit.event.EventHandler; 9 | import org.bukkit.event.EventPriority; 10 | import org.bukkit.event.Listener; 11 | import org.bukkit.event.player.AsyncPlayerChatEvent; 12 | import org.bukkit.event.player.AsyncPlayerPreLoginEvent; 13 | import org.bukkit.event.player.PlayerChangedWorldEvent; 14 | import org.bukkit.event.player.PlayerLoginEvent; 15 | import org.bukkit.event.player.PlayerQuitEvent; 16 | 17 | public class BukkitListener implements Listener { 18 | 19 | @EventHandler 20 | public void onLeave(PlayerQuitEvent e) { 21 | Triton.get().getPlayerManager().unregisterPlayer(e.getPlayer().getUniqueId()); 22 | } 23 | 24 | @EventHandler(priority = EventPriority.MONITOR) 25 | public void onLogin(AsyncPlayerPreLoginEvent e) { 26 | val lp = new SpigotLanguagePlayer(e.getUniqueId()); 27 | Triton.get().getPlayerManager().registerPlayer(lp); 28 | if (e.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) 29 | e.setKickMessage(Triton.get().getLanguageParser() 30 | .replaceLanguages(e.getKickMessage(), lp, Triton.get().getConf().getKickSyntax())); 31 | } 32 | 33 | @EventHandler(priority = EventPriority.MONITOR) 34 | public void onLoginSync(PlayerLoginEvent e) { 35 | val lp = Triton.get().getPlayerManager().get(e.getPlayer().getUniqueId()); 36 | if (e.getResult() != PlayerLoginEvent.Result.ALLOWED) 37 | e.setKickMessage(Triton.get().getLanguageParser() 38 | .replaceLanguages(e.getKickMessage(), lp, Triton.get().getConf().getKickSyntax())); 39 | } 40 | 41 | @EventHandler 42 | public void onChat(AsyncPlayerChatEvent e) { 43 | if (!Triton.get().getConfig().isPreventPlaceholdersInChat()) return; 44 | 45 | String msg = e.getMessage(); 46 | val indexes = LanguageParser.getPatternIndexArray(msg, Triton.get().getConfig().getChatSyntax().getLang()); 47 | for (int i = 0; i < indexes.size(); ++i) { 48 | val index = indexes.get(i); 49 | msg = msg.substring(0, index[0] + 1 + i) + ChatColor.RESET + msg.substring(index[0] + 1 + i); 50 | } 51 | e.setMessage(msg); 52 | } 53 | 54 | @EventHandler 55 | public void onChangeWorld(PlayerChangedWorldEvent e) { 56 | val lp = (SpigotLanguagePlayer) Triton.get().getPlayerManager().get(e.getPlayer().getUniqueId()); 57 | 58 | lp.onWorldChange(); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/listeners/VelocityListener.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.listeners; 2 | 3 | import com.rexcantor64.triton.Triton; 4 | import com.rexcantor64.triton.player.VelocityLanguagePlayer; 5 | import com.velocitypowered.api.event.PostOrder; 6 | import com.velocitypowered.api.event.Subscribe; 7 | import com.velocitypowered.api.event.connection.LoginEvent; 8 | import com.velocitypowered.api.event.player.PlayerSettingsChangedEvent; 9 | import com.velocitypowered.api.event.player.ServerConnectedEvent; 10 | import com.velocitypowered.api.event.player.ServerPostConnectEvent; 11 | import lombok.val; 12 | 13 | public class VelocityListener { 14 | 15 | @Subscribe 16 | public void afterServerConnect(ServerPostConnectEvent e) { 17 | e.getPlayer().getCurrentServer().ifPresent(serverConnection -> { 18 | Triton.asVelocity().getBridgeManager().executeQueue(serverConnection.getServer()); 19 | 20 | val lp = (VelocityLanguagePlayer) Triton.get().getPlayerManager().get(e.getPlayer().getUniqueId()); 21 | Triton.asVelocity().getBridgeManager().sendPlayerLanguage(lp); 22 | 23 | if (Triton.get().getConf().isRunLanguageCommandsOnLogin()) { 24 | lp.executeCommands(serverConnection.getServer()); 25 | } 26 | }); 27 | } 28 | 29 | @Subscribe(order = PostOrder.FIRST) 30 | public void onPlayerLogin(LoginEvent e) { 31 | val player = e.getPlayer(); 32 | val lp = new VelocityLanguagePlayer(player); 33 | Triton.get().getPlayerManager().registerPlayer(lp); 34 | } 35 | 36 | @Subscribe 37 | public void onPlayerSettingsUpdate(PlayerSettingsChangedEvent event) { 38 | val lp = Triton.get().getPlayerManager().get(event.getPlayer().getUniqueId()); 39 | if (lp.isWaitingForClientLocale()) { 40 | lp.setLang(Triton.get().getLanguageManager().getLanguageByLocale(event.getPlayerSettings().getLocale().toString(), true)); 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/logger/JavaLogger.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.logger; 2 | 3 | import lombok.NonNull; 4 | import lombok.RequiredArgsConstructor; 5 | import lombok.Setter; 6 | 7 | import java.util.logging.Level; 8 | 9 | @RequiredArgsConstructor 10 | public class JavaLogger implements TritonLogger { 11 | private final java.util.logging.Logger logger; 12 | @Setter 13 | private int logLevel = 0; 14 | 15 | public void logTrace(String message, Object... arguments) { 16 | if (logLevel < 2) return; 17 | logger.log(Level.INFO, "[TRACE] " + parseMessage(message, arguments)); 18 | } 19 | 20 | public void logDebug(String message, Object... arguments) { 21 | if (logLevel < 1) return; 22 | logger.log(Level.INFO, "[DEBUG] " + parseMessage(message, arguments)); 23 | } 24 | 25 | public void logInfo(String message, Object... arguments) { 26 | logger.log(Level.INFO, parseMessage(message, arguments)); 27 | } 28 | 29 | public void logWarning(String message, Object... arguments) { 30 | logger.log(Level.WARNING, parseMessage(message, arguments)); 31 | } 32 | 33 | public void logError(String message, Object... arguments) { 34 | logger.log(Level.SEVERE, parseMessage(message, arguments)); 35 | } 36 | 37 | public void logError(Throwable error, String message, Object... arguments) { 38 | logger.log(Level.SEVERE, parseMessage(message, arguments), error); 39 | } 40 | 41 | private String parseMessage(@NonNull String message, @NonNull Object... arguments) { 42 | for (int i = 0; i < arguments.length; ++i) 43 | message = message.replace("%" + (i + 1), String.valueOf(arguments[i])); 44 | return message; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/logger/SLF4JLogger.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.logger; 2 | 3 | import lombok.NonNull; 4 | import lombok.RequiredArgsConstructor; 5 | import lombok.Setter; 6 | import org.slf4j.Logger; 7 | 8 | @RequiredArgsConstructor 9 | public class SLF4JLogger implements TritonLogger { 10 | private final Logger logger; 11 | @Setter 12 | private int logLevel = 1000; 13 | 14 | public void logTrace(String message, Object... arguments) { 15 | if (logLevel < 2) return; 16 | logger.info("[TRACE] " + parseMessage(message, arguments)); 17 | } 18 | 19 | public void logDebug(String message, Object... arguments) { 20 | if (logLevel < 1) return; 21 | logger.info("[DEBUG] " + parseMessage(message, arguments)); 22 | } 23 | 24 | public void logInfo(String message, Object... arguments) { 25 | logger.info(parseMessage(message, arguments)); 26 | } 27 | 28 | public void logWarning(String message, Object... arguments) { 29 | logger.warn(parseMessage(message, arguments)); 30 | } 31 | 32 | public void logError(String message, Object... arguments) { 33 | logger.error(parseMessage(message, arguments)); 34 | } 35 | 36 | public void logError(Throwable error, String message, Object... arguments) { 37 | logger.error(parseMessage(message, arguments), error); 38 | } 39 | 40 | private String parseMessage(@NonNull String message, @NonNull Object... arguments) { 41 | for (int i = 0; i < arguments.length; ++i) 42 | message = message.replace("%" + (i + 1), String.valueOf(arguments[i])); 43 | return message; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/logger/TritonLogger.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.logger; 2 | 3 | public interface TritonLogger { 4 | 5 | void logTrace(String message, Object... arguments); 6 | 7 | void logDebug(String message, Object... arguments); 8 | 9 | void logInfo(String message, Object... arguments); 10 | 11 | void logWarning(String message, Object... arguments); 12 | 13 | void logError(String message, Object... arguments); 14 | 15 | void logError(Throwable error, String message, Object... arguments); 16 | 17 | void setLogLevel(int level); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/packetinterceptor/BungeeDecoder.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.packetinterceptor; 2 | 3 | import com.rexcantor64.triton.Triton; 4 | import com.rexcantor64.triton.player.BungeeLanguagePlayer; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.handler.codec.MessageToMessageDecoder; 7 | import net.md_5.bungee.protocol.PacketWrapper; 8 | import net.md_5.bungee.protocol.packet.ClientSettings; 9 | 10 | import java.util.List; 11 | 12 | public class BungeeDecoder extends MessageToMessageDecoder { 13 | 14 | private final BungeeLanguagePlayer lp; 15 | 16 | public BungeeDecoder(BungeeLanguagePlayer lp) { 17 | this.lp = lp; 18 | } 19 | 20 | @Override 21 | protected void decode(ChannelHandlerContext chx, PacketWrapper wrapper, List out) throws Exception { 22 | try { 23 | if (wrapper.packet instanceof ClientSettings) { 24 | ClientSettings packet = (ClientSettings) wrapper.packet; 25 | if (lp.isWaitingForClientLocale()) 26 | lp.setLang(Triton.get().getLanguageManager().getLanguageByLocale(packet.getLocale(), true)); 27 | } 28 | out.add(wrapper); 29 | } catch (NullPointerException e) { 30 | Triton.get().getLogger().logError(e, "Failed to fetch client locale for player %1!", this.lp.getUUID()); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/packetinterceptor/PacketInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.packetinterceptor; 2 | 3 | import com.rexcantor64.triton.language.item.SignLocation; 4 | import com.rexcantor64.triton.player.SpigotLanguagePlayer; 5 | import org.bukkit.entity.Player; 6 | 7 | import java.util.UUID; 8 | 9 | public interface PacketInterceptor { 10 | 11 | void refreshSigns(SpigotLanguagePlayer player); 12 | 13 | void refreshEntities(SpigotLanguagePlayer player); 14 | 15 | void refreshTabHeaderFooter(SpigotLanguagePlayer player, String header, String footer); 16 | 17 | void refreshBossbar(SpigotLanguagePlayer player, UUID uuid, String text); 18 | 19 | void refreshScoreboard(SpigotLanguagePlayer player); 20 | 21 | void resetSign(Player p, SignLocation location); 22 | 23 | void refreshAdvancements(SpigotLanguagePlayer languagePlayer); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/packetinterceptor/PreLoginBungeeEncoder.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.packetinterceptor; 2 | 3 | import com.rexcantor64.triton.Triton; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.MessageToMessageEncoder; 6 | import lombok.RequiredArgsConstructor; 7 | import net.md_5.bungee.api.chat.BaseComponent; 8 | import net.md_5.bungee.api.chat.TextComponent; 9 | import net.md_5.bungee.api.chat.TranslatableComponent; 10 | import net.md_5.bungee.protocol.DefinedPacket; 11 | import net.md_5.bungee.protocol.packet.Kick; 12 | 13 | import java.util.List; 14 | 15 | @RequiredArgsConstructor 16 | public class PreLoginBungeeEncoder extends MessageToMessageEncoder { 17 | 18 | private final String ip; 19 | private String lang; 20 | 21 | @Override 22 | protected void encode(ChannelHandlerContext ctx, DefinedPacket packet, 23 | List out) { 24 | try { 25 | if (Triton.get().getConf().isKick() && packet instanceof Kick) { 26 | Kick kick = (Kick) packet; 27 | 28 | if (this.lang == null) 29 | this.lang = Triton.get().getStorage().getLanguageFromIp(ip).getName(); 30 | 31 | kick.setMessage(nullOrTranslatable(Triton.get().getLanguageParser().parseComponent(this.lang, 32 | Triton.get().getConf().getKickSyntax(), kick.getMessage()))); 33 | } 34 | } catch (Exception | Error e) { 35 | e.printStackTrace(); 36 | } 37 | out.add(packet); 38 | } 39 | 40 | private BaseComponent nullOrTranslatable(BaseComponent... bc) { 41 | if (bc == null) { 42 | return new TranslatableComponent(""); 43 | } 44 | return TextComponent.fromArray(bc); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/packetinterceptor/protocollib/HandlerFunction.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.packetinterceptor.protocollib; 2 | 3 | import com.comphenix.protocol.events.PacketEvent; 4 | import com.rexcantor64.triton.player.SpigotLanguagePlayer; 5 | import lombok.AccessLevel; 6 | import lombok.Getter; 7 | import lombok.RequiredArgsConstructor; 8 | 9 | import java.util.function.BiConsumer; 10 | 11 | /** 12 | * Wrapper for a {@link PacketEvent} handler along whether it can be 13 | * handled async or not. 14 | * 15 | * @since 3.8.1 16 | */ 17 | @RequiredArgsConstructor(access = AccessLevel.PRIVATE) 18 | @Getter 19 | public class HandlerFunction { 20 | private final BiConsumer handlerFunction; 21 | private final HandlerType handlerType; 22 | 23 | /** 24 | * Build a handler function that can be run both synchronously and asynchronously. 25 | * 26 | * @param handlerFunction The handler function. 27 | * @return A {@link HandlerFunction} wrapping the handler function. 28 | * @since 3.8.1 29 | */ 30 | public static HandlerFunction asAsync(BiConsumer handlerFunction) { 31 | return new HandlerFunction(handlerFunction, HandlerType.ASYNC); 32 | } 33 | 34 | /** 35 | * Build a handler function that can only be run synchronously. 36 | * 37 | * @param handlerFunction The handler function. 38 | * @return A {@link HandlerFunction} wrapping the handler function. 39 | * @since 3.8.1 40 | */ 41 | public static HandlerFunction asSync(BiConsumer handlerFunction) { 42 | return new HandlerFunction(handlerFunction, HandlerType.SYNC); 43 | } 44 | 45 | public enum HandlerType { 46 | SYNC, ASYNC 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/packetinterceptor/protocollib/PacketHandler.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.packetinterceptor.protocollib; 2 | 3 | import com.comphenix.protocol.PacketType; 4 | import com.comphenix.protocol.ProtocolLibrary; 5 | import com.comphenix.protocol.events.PacketContainer; 6 | import com.rexcantor64.triton.SpigotMLP; 7 | import com.rexcantor64.triton.Triton; 8 | import com.rexcantor64.triton.config.MainConfig; 9 | import com.rexcantor64.triton.language.LanguageManager; 10 | import com.rexcantor64.triton.language.LanguageParser; 11 | import com.rexcantor64.triton.logger.TritonLogger; 12 | import org.bukkit.entity.Player; 13 | 14 | import java.util.Map; 15 | 16 | public abstract class PacketHandler { 17 | 18 | public abstract void registerPacketTypes(Map registry); 19 | 20 | protected SpigotMLP getMain() { 21 | return Triton.asSpigot(); 22 | } 23 | 24 | protected MainConfig getConfig() { 25 | return getMain().getConfig(); 26 | } 27 | 28 | protected TritonLogger logger() { 29 | return getMain().getLogger(); 30 | } 31 | 32 | protected LanguageManager getLanguageManager() { 33 | return getMain().getLanguageManager(); 34 | } 35 | 36 | protected LanguageParser getLanguageParser() { 37 | return getMain().getLanguageParser(); 38 | } 39 | 40 | /** 41 | * Wrapper for {@link com.comphenix.protocol.ProtocolManager#sendServerPacket(Player, PacketContainer, boolean)}. 42 | * 43 | * @param bukkitPlayer The player to send the packet to. 44 | * @param packet The packet itself. 45 | * @param filters Whether to pass the packet through registered packet listeners. 46 | * @since 3.8.0 47 | */ 48 | protected void sendPacket(Player bukkitPlayer, PacketContainer packet, boolean filters) { 49 | ProtocolLibrary.getProtocolManager().sendServerPacket(bukkitPlayer, packet, filters); 50 | } 51 | 52 | /** 53 | * Wrapper for {@link com.comphenix.protocol.ProtocolManager#createPacket(PacketType)}. 54 | * 55 | * @param packetType The type of the packet to create. 56 | * @return The created packet. 57 | * @since 3.8.0 58 | */ 59 | protected PacketContainer createPacket(PacketType packetType) { 60 | return ProtocolLibrary.getProtocolManager().createPacket(packetType); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/placeholderapi/TritonPlaceholderHook.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.placeholderapi; 2 | 3 | import com.rexcantor64.triton.SpigotMLP; 4 | import me.clip.placeholderapi.expansion.PlaceholderExpansion; 5 | import me.clip.placeholderapi.expansion.Relational; 6 | import org.bukkit.entity.Player; 7 | 8 | public class TritonPlaceholderHook extends PlaceholderExpansion implements Relational { 9 | 10 | private final SpigotMLP triton; 11 | 12 | public TritonPlaceholderHook(SpigotMLP triton) { 13 | this.triton = triton; 14 | } 15 | 16 | @Override 17 | public String getIdentifier() { 18 | return "triton"; 19 | } 20 | 21 | @Override 22 | public String getAuthor() { 23 | return "Rexcantor64"; 24 | } 25 | 26 | @Override 27 | public String getVersion() { 28 | return triton.getLoader().getDescription().getVersion(); 29 | } 30 | 31 | @Override 32 | public boolean persist() { 33 | return true; 34 | } 35 | 36 | @Override 37 | public String onPlaceholderRequest(Player p, String params) { 38 | if (params == null) return null; 39 | if (p == null) return triton.getLanguageManager().getTextFromMain(params); 40 | return triton.getLanguageManager().getText(triton.getPlayerManager().get(p.getUniqueId()), params); 41 | } 42 | 43 | @Override 44 | public String onPlaceholderRequest(Player ignore, Player viewer, String params) { 45 | return onPlaceholderRequest(viewer, params); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/player/LanguagePlayer.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.player; 2 | 3 | import com.rexcantor64.triton.api.language.Language; 4 | 5 | import java.util.UUID; 6 | 7 | public interface LanguagePlayer extends com.rexcantor64.triton.api.players.LanguagePlayer { 8 | 9 | boolean isWaitingForClientLocale(); 10 | 11 | void waitForClientLocale(); 12 | 13 | @Override 14 | default Language getLanguage() { 15 | return this.getLang(); 16 | } 17 | 18 | /** 19 | * @return the UUID that is to be used for storage-related operations 20 | */ 21 | default UUID getStorageUniqueId() { 22 | return this.getUUID(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/player/PlayerManager.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.player; 2 | 3 | import com.google.common.collect.Maps; 4 | import com.rexcantor64.triton.BungeeMLP; 5 | import com.rexcantor64.triton.SpigotMLP; 6 | import com.rexcantor64.triton.Triton; 7 | import com.rexcantor64.triton.VelocityMLP; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.Map; 12 | import java.util.UUID; 13 | 14 | public class PlayerManager implements com.rexcantor64.triton.api.players.PlayerManager { 15 | 16 | private Map players = Maps.newHashMap(); 17 | 18 | public LanguagePlayer get(UUID p) { 19 | LanguagePlayer lp = players.get(p); 20 | if (lp != null) return lp; 21 | 22 | Triton.get().getLogger().logTrace("[Player Manager] Tried to get an unregistered language player, so registering a new one for UUID %1", p); 23 | 24 | if (Triton.get() instanceof BungeeMLP) 25 | players.put(p, lp = new BungeeLanguagePlayer(p)); 26 | else if (Triton.get() instanceof SpigotMLP) 27 | players.put(p, lp = new SpigotLanguagePlayer(p)); 28 | else if (Triton.get() instanceof VelocityMLP) 29 | players.put(p, lp = VelocityLanguagePlayer.fromUUID(p)); 30 | 31 | return lp; 32 | } 33 | 34 | public boolean hasPlayer(UUID p) { 35 | return players.containsKey(p); 36 | } 37 | 38 | public void unregisterPlayer(UUID p) { 39 | Triton.get().getLogger().logTrace("[Player Manager] Unregistering language player with UUID %1", p); 40 | players.remove(p); 41 | } 42 | 43 | public void registerPlayer(LanguagePlayer lp) { 44 | Triton.get().getLogger().logTrace("[Player Manager] Registering language player %1", lp); 45 | players.put(lp.getUUID(), lp); 46 | } 47 | 48 | public List getAll() { 49 | return new ArrayList<>(players.values()); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/plugin/BungeePlugin.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.plugin; 2 | 3 | import com.rexcantor64.triton.BungeeMLP; 4 | import com.rexcantor64.triton.Triton; 5 | import com.rexcantor64.triton.logger.JavaLogger; 6 | import com.rexcantor64.triton.logger.TritonLogger; 7 | import com.rexcantor64.triton.terminal.BungeeTerminalManager; 8 | import com.rexcantor64.triton.terminal.Log4jInjector; 9 | import net.md_5.bungee.api.plugin.Plugin; 10 | 11 | import java.util.logging.Level; 12 | 13 | public class BungeePlugin extends Plugin implements PluginLoader { 14 | private TritonLogger logger; 15 | 16 | @Override 17 | public void onEnable() { 18 | this.logger = new JavaLogger(this.getLogger()); 19 | new BungeeMLP(this).onEnable(); 20 | } 21 | 22 | @Override 23 | public void onDisable() { 24 | // Set the formatter back to default 25 | try { 26 | if (Triton.get().getConf().isTerminal()) 27 | BungeeTerminalManager.uninjectTerminalFormatter(); 28 | } catch (Error | Exception e) { 29 | try { 30 | if (Triton.get().getConf().isTerminal()) 31 | Log4jInjector.uninjectAppender(); 32 | } catch (Error | Exception e1) { 33 | getLogger() 34 | .log(Level.SEVERE, "Failed to uninject terminal translations. Some forked BungeeCord servers " + 35 | "might not work correctly. To hide this message, disable terminal translation on " + 36 | "config."); 37 | e.printStackTrace(); 38 | e1.printStackTrace(); 39 | } 40 | } 41 | } 42 | 43 | @Override 44 | public PluginType getType() { 45 | return PluginType.BUNGEE; 46 | } 47 | 48 | @Override 49 | public TritonLogger getTritonLogger() { 50 | return this.logger; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/plugin/PluginLoader.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.plugin; 2 | 3 | import com.rexcantor64.triton.logger.TritonLogger; 4 | 5 | import java.io.InputStream; 6 | 7 | public interface PluginLoader { 8 | 9 | PluginType getType(); 10 | 11 | TritonLogger getTritonLogger(); 12 | 13 | InputStream getResourceAsStream(String fileName); 14 | 15 | enum PluginType { 16 | SPIGOT, BUNGEE, VELOCITY 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/plugin/SpigotPlugin.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.plugin; 2 | 3 | import com.rexcantor64.triton.SpigotMLP; 4 | import com.rexcantor64.triton.Triton; 5 | import com.rexcantor64.triton.logger.JavaLogger; 6 | import com.rexcantor64.triton.logger.TritonLogger; 7 | import com.rexcantor64.triton.terminal.Log4jInjector; 8 | import org.bukkit.plugin.java.JavaPlugin; 9 | 10 | import java.io.InputStream; 11 | 12 | public class SpigotPlugin extends JavaPlugin implements PluginLoader { 13 | private TritonLogger logger; 14 | 15 | @Override 16 | public void onEnable() { 17 | this.logger = new JavaLogger(this.getLogger()); 18 | new SpigotMLP(this).onEnable(); 19 | } 20 | 21 | @Override 22 | public void onDisable() { 23 | if (Triton.get().getConf().isTerminal()) 24 | Log4jInjector.uninjectAppender(); 25 | } 26 | 27 | @Override 28 | public PluginType getType() { 29 | return PluginType.SPIGOT; 30 | } 31 | 32 | @Override 33 | public InputStream getResourceAsStream(String fileName) { 34 | return getResource(fileName); 35 | } 36 | 37 | @Override 38 | public TritonLogger getTritonLogger() { 39 | return this.logger; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/plugin/VelocityPlugin.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.plugin; 2 | 3 | import com.google.inject.Inject; 4 | import com.rexcantor64.triton.VelocityMLP; 5 | import com.rexcantor64.triton.logger.SLF4JLogger; 6 | import com.rexcantor64.triton.logger.TritonLogger; 7 | import com.velocitypowered.api.event.Subscribe; 8 | import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; 9 | import com.velocitypowered.api.plugin.Plugin; 10 | import com.velocitypowered.api.plugin.annotation.DataDirectory; 11 | import com.velocitypowered.api.proxy.ProxyServer; 12 | import lombok.Getter; 13 | import org.slf4j.Logger; 14 | 15 | import java.io.InputStream; 16 | import java.nio.file.Path; 17 | 18 | @Plugin(id = "triton", name = "Triton", url = "https://triton.rexcantor64.com", description = 19 | "A plugin that replaces any message on your server, to the receiver's language, in real time!", 20 | version = "@version@", 21 | authors = {"Rexcantor64"}) 22 | 23 | @Getter 24 | public class VelocityPlugin implements PluginLoader { 25 | private final ProxyServer server; 26 | private final TritonLogger tritonLogger; 27 | private final Path dataDirectory; 28 | 29 | @Inject 30 | public VelocityPlugin(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory) { 31 | this.server = server; 32 | this.tritonLogger = new SLF4JLogger(logger); 33 | this.dataDirectory = dataDirectory; 34 | } 35 | 36 | @Subscribe 37 | public void onEnable(ProxyInitializeEvent event) { 38 | new VelocityMLP(this).onEnable(); 39 | } 40 | 41 | @Override 42 | public PluginType getType() { 43 | return PluginType.VELOCITY; 44 | } 45 | 46 | @Override 47 | public InputStream getResourceAsStream(String fileName) { 48 | return VelocityPlugin.class.getResourceAsStream("/" + fileName); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/storage/IpCache.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.storage; 2 | 3 | import com.rexcantor64.triton.Triton; 4 | import lombok.RequiredArgsConstructor; 5 | import lombok.val; 6 | 7 | import java.util.concurrent.ConcurrentHashMap; 8 | import java.util.concurrent.TimeUnit; 9 | 10 | public class IpCache { 11 | 12 | private final ConcurrentHashMap cache = new ConcurrentHashMap<>(); 13 | 14 | public void addToCache(String ip, String lang) { 15 | cache.put(ip, new CacheEntry(lang, System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(5))); 16 | } 17 | 18 | public String getFromCache(String ip) { 19 | Triton.get().runAsync(this::clearExpiredEntries); 20 | val entry = cache.get(ip); 21 | return entry == null || entry.expiresAt < System.currentTimeMillis() ? null : entry.lang; 22 | } 23 | 24 | private synchronized void clearExpiredEntries() { 25 | cache.keySet().removeIf(key -> cache.get(key).expiresAt < System.currentTimeMillis()); 26 | } 27 | 28 | @RequiredArgsConstructor 29 | private static class CacheEntry { 30 | private final String lang; 31 | private final long expiresAt; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/storage/Storage.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.storage; 2 | 3 | import com.rexcantor64.triton.api.language.Language; 4 | import com.rexcantor64.triton.language.item.Collection; 5 | import com.rexcantor64.triton.language.item.LanguageItem; 6 | import com.rexcantor64.triton.language.item.LanguageSign; 7 | import com.rexcantor64.triton.language.item.SignLocation; 8 | import com.rexcantor64.triton.player.LanguagePlayer; 9 | import lombok.Getter; 10 | import lombok.Setter; 11 | import lombok.val; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | import java.util.UUID; 16 | import java.util.concurrent.ConcurrentHashMap; 17 | import java.util.stream.Collectors; 18 | 19 | public abstract class Storage { 20 | 21 | @Getter 22 | @Setter 23 | protected ConcurrentHashMap collections = new ConcurrentHashMap<>(); 24 | 25 | public abstract Language getLanguageFromIp(String ip); 26 | 27 | public abstract Language getLanguage(LanguagePlayer lp); 28 | 29 | public abstract void setLanguage(UUID uuid, String ip, Language newLanguage); 30 | 31 | public abstract void load(); 32 | 33 | public abstract void unload(); 34 | 35 | public abstract boolean uploadToStorage(ConcurrentHashMap collections); 36 | 37 | public abstract boolean uploadPartiallyToStorage(ConcurrentHashMap collections, 38 | List changed, List deleted); 39 | 40 | public abstract ConcurrentHashMap downloadFromStorage(); 41 | 42 | // If key is null, it's a remove action; otherwise, it's an add action 43 | public List toggleLocationForSignGroup(SignLocation location, String key) { 44 | val changed = new ArrayList(); 45 | for (val collection : collections.values()) { 46 | for (val item : collection.getItems()) { 47 | if (!(item instanceof LanguageSign)) continue; 48 | 49 | val sign = (LanguageSign) item; 50 | 51 | // Remove locations for a sign group 52 | if (key == null && sign.getLocations() != null) { 53 | val newLocations = sign.getLocations().stream() 54 | .filter(loc -> location.getServer() == null ? 55 | !loc.equals(location) : 56 | !loc.equalsWithServer(location)) 57 | .collect(Collectors.toList()); 58 | 59 | if (newLocations.size() != sign.getLocations().size()) 60 | changed.add(sign); 61 | 62 | sign.setLocations(newLocations); 63 | } 64 | 65 | if (sign.getKey().equals(key)) { 66 | if (sign.hasLocation(location, location.getServer() != null)) continue; 67 | if (sign.getLocations() == null) sign.setLocations(new ArrayList<>()); 68 | sign.getLocations().add(location); 69 | changed.add(sign); 70 | } 71 | } 72 | } 73 | return changed; 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/terminal/BungeeTerminalFormatter.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.terminal; 2 | 3 | import com.rexcantor64.triton.Triton; 4 | import net.md_5.bungee.log.ConciseFormatter; 5 | 6 | import java.util.logging.LogRecord; 7 | 8 | public class BungeeTerminalFormatter extends ConciseFormatter { 9 | 10 | public BungeeTerminalFormatter() { 11 | super(true); 12 | } 13 | 14 | @Override 15 | public String format(LogRecord record) { 16 | String superResult = super.format(record); 17 | if (Triton.get().getLanguageManager().getMainLanguage() != null) { 18 | String result = Triton.get().getLanguageParser() 19 | .replaceLanguages(superResult, Triton.get().getLanguageManager().getMainLanguage().getName(), Triton 20 | .get().getConf().getChatSyntax()); 21 | if (result != null) return result; 22 | } 23 | return superResult; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/terminal/BungeeTerminalManager.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.terminal; 2 | 3 | import net.md_5.bungee.BungeeCord; 4 | import net.md_5.bungee.log.ColouredWriter; 5 | import net.md_5.bungee.log.ConciseFormatter; 6 | 7 | import java.util.logging.Handler; 8 | 9 | public class BungeeTerminalManager { 10 | 11 | public static void injectTerminalFormatter() { 12 | for (Handler h : BungeeCord.getInstance().getLogger().getHandlers()) 13 | if (h instanceof ColouredWriter) 14 | h.setFormatter(new BungeeTerminalFormatter()); 15 | } 16 | 17 | public static void uninjectTerminalFormatter() { 18 | for (Handler h : BungeeCord.getInstance().getLogger().getHandlers()) 19 | if (h instanceof ColouredWriter) 20 | h.setFormatter(new ConciseFormatter(true)); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/terminal/ChatColorTerminalReplacer.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.terminal; 2 | 3 | import lombok.val; 4 | import net.md_5.bungee.api.ChatColor; 5 | import org.fusesource.jansi.Ansi; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | public class ChatColorTerminalReplacer { 11 | 12 | private final Map replacements = new HashMap<>(); 13 | 14 | ChatColorTerminalReplacer() { 15 | this.replacements 16 | .put(ChatColor.BLACK, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.BLACK).boldOff().toString()); 17 | this.replacements 18 | .put(ChatColor.DARK_BLUE, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.BLUE).boldOff().toString()); 19 | this.replacements.put(ChatColor.DARK_GREEN, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.GREEN).boldOff() 20 | .toString()); 21 | this.replacements 22 | .put(ChatColor.DARK_AQUA, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.CYAN).boldOff().toString()); 23 | this.replacements 24 | .put(ChatColor.DARK_RED, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.RED).boldOff().toString()); 25 | this.replacements 26 | .put(ChatColor.DARK_PURPLE, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.MAGENTA).boldOff() 27 | .toString()); 28 | this.replacements 29 | .put(ChatColor.GOLD, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.YELLOW).boldOff().toString()); 30 | this.replacements 31 | .put(ChatColor.GRAY, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.WHITE).boldOff().toString()); 32 | this.replacements 33 | .put(ChatColor.DARK_GRAY, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.BLACK).bold().toString()); 34 | this.replacements 35 | .put(ChatColor.BLUE, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.BLUE).bold().toString()); 36 | this.replacements 37 | .put(ChatColor.GREEN, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.GREEN).bold().toString()); 38 | this.replacements 39 | .put(ChatColor.AQUA, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.CYAN).bold().toString()); 40 | this.replacements.put(ChatColor.RED, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.RED).bold().toString()); 41 | this.replacements.put(ChatColor.LIGHT_PURPLE, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.MAGENTA).bold() 42 | .toString()); 43 | this.replacements 44 | .put(ChatColor.YELLOW, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.YELLOW).bold().toString()); 45 | this.replacements 46 | .put(ChatColor.WHITE, Ansi.ansi().a(Ansi.Attribute.RESET).fg(Ansi.Color.WHITE).bold().toString()); 47 | this.replacements.put(ChatColor.MAGIC, Ansi.ansi().a(Ansi.Attribute.BLINK_SLOW).toString()); 48 | this.replacements.put(ChatColor.BOLD, Ansi.ansi().a(Ansi.Attribute.UNDERLINE_DOUBLE).toString()); 49 | this.replacements.put(ChatColor.STRIKETHROUGH, Ansi.ansi().a(Ansi.Attribute.STRIKETHROUGH_ON).toString()); 50 | this.replacements.put(ChatColor.UNDERLINE, Ansi.ansi().a(Ansi.Attribute.UNDERLINE).toString()); 51 | this.replacements.put(ChatColor.ITALIC, Ansi.ansi().a(Ansi.Attribute.ITALIC).toString()); 52 | this.replacements.put(ChatColor.RESET, Ansi.ansi().a(Ansi.Attribute.RESET).toString()); 53 | } 54 | 55 | public String parseMessage(String message) { 56 | String result = message; 57 | 58 | for (val entry : this.replacements.entrySet()) { 59 | result = result.replaceAll("(?i)" + entry.getKey().toString(), entry.getValue()); 60 | } 61 | 62 | return result + Ansi.ansi().reset().toString(); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/terminal/Log4jInjector.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.terminal; 2 | 3 | import com.rexcantor64.triton.Triton; 4 | import com.rexcantor64.triton.utils.AppenderRefFactory; 5 | import lombok.var; 6 | import org.apache.logging.log4j.LogManager; 7 | import org.apache.logging.log4j.core.Appender; 8 | import org.apache.logging.log4j.core.Logger; 9 | import org.apache.logging.log4j.core.appender.rewrite.RewriteAppender; 10 | import org.apache.logging.log4j.core.config.AppenderRef; 11 | import org.apache.logging.log4j.core.config.Configuration; 12 | 13 | public class Log4jInjector { 14 | 15 | public static void injectAppender() { 16 | Logger logger = (Logger) LogManager.getRootLogger(); 17 | Configuration config = logger.getContext().getConfiguration(); 18 | 19 | Appender originalAppender = logger.getAppenders().get("TerminalConsole"); 20 | if (originalAppender == null) originalAppender = logger.getAppenders().get("rewrite"); 21 | // Paper now uses an "Async" AppenderRef over the "rewrite" and "rewrite2" refs 22 | if (originalAppender == null) originalAppender = logger.getAppenders().get("Async"); 23 | if (originalAppender == null) { 24 | Triton.get().getLogger().logError("Failed to inject rewrite policy into Log4j. Terminal translation won't work properly."); 25 | return; 26 | } 27 | 28 | AppenderRef appenderRef = AppenderRefFactory.getAppenderRef(originalAppender.getName()); 29 | 30 | if (appenderRef != null) { 31 | RewriteAppender appender = RewriteAppender.createAppender("TritonTerminalTranslation", 32 | "false", 33 | new AppenderRef[]{appenderRef}, 34 | config, 35 | TritonTerminalRewrite.createPolicy(), 36 | null); 37 | appender.start(); 38 | logger.addAppender(appender); 39 | logger.removeAppender(originalAppender); 40 | } 41 | } 42 | 43 | public static void uninjectAppender() { 44 | Logger logger = (Logger) LogManager.getRootLogger(); 45 | Configuration config = logger.getContext().getConfiguration(); 46 | if (logger.getAppenders().containsKey("TritonTerminalTranslation")) { 47 | logger.removeAppender(logger.getAppenders().get("TritonTerminalTranslation")); 48 | logger.addAppender(config.getAppenders().get("TerminalConsole")); 49 | } 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/terminal/SpigotTerminalFormatter.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.terminal; 2 | 3 | import com.rexcantor64.triton.Triton; 4 | 5 | import java.util.logging.LogRecord; 6 | import java.util.logging.SimpleFormatter; 7 | 8 | public class SpigotTerminalFormatter extends SimpleFormatter { 9 | 10 | @Override 11 | public synchronized String format(LogRecord record) { 12 | return handleString(super.format(record)); 13 | } 14 | 15 | @Override 16 | public synchronized String formatMessage(LogRecord record) { 17 | return handleString(super.formatMessage(record)); 18 | } 19 | 20 | private String handleString(String superResult) { 21 | if (Triton.get().getLanguageManager().getMainLanguage() != null) { 22 | String result = Triton.get().getLanguageParser() 23 | .replaceLanguages(superResult, Triton.get().getLanguageManager().getMainLanguage().getName(), Triton 24 | .get().getConf().getChatSyntax()); 25 | if (result != null) return result; 26 | } 27 | return superResult; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/terminal/TranslatablePrintStream.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.terminal; 2 | 3 | import com.rexcantor64.triton.Triton; 4 | 5 | import java.io.PrintStream; 6 | 7 | public class TranslatablePrintStream extends PrintStream { 8 | 9 | private PrintStream original; 10 | 11 | public TranslatablePrintStream(PrintStream out) { 12 | super(out, true); 13 | this.original = out; 14 | } 15 | 16 | @Override 17 | public void print(Object obj) { 18 | super.print(translate(String.valueOf(obj))); 19 | } 20 | 21 | @Override 22 | public void print(String s) { 23 | super.print(translate(s)); 24 | } 25 | 26 | private String translate(String input) { 27 | if (Triton.get().getLanguageManager().getMainLanguage() != null) { 28 | String result = Triton.get().getLanguageParser() 29 | .replaceLanguages(input, Triton.get().getLanguageManager().getMainLanguage().getName(), Triton 30 | .get().getConf().getChatSyntax()); 31 | if (result != null) return result; 32 | } 33 | return input; 34 | } 35 | 36 | public PrintStream getOriginal() { 37 | return original; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/terminal/TritonTerminalRewrite.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.terminal; 2 | 3 | import com.rexcantor64.triton.Triton; 4 | import com.rexcantor64.triton.language.Language; 5 | import com.rexcantor64.triton.utils.NMSUtils; 6 | import org.apache.logging.log4j.core.LogEvent; 7 | import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy; 8 | import org.apache.logging.log4j.core.config.plugins.Plugin; 9 | import org.apache.logging.log4j.core.config.plugins.PluginFactory; 10 | import org.apache.logging.log4j.message.SimpleMessage; 11 | 12 | @Plugin(name = "TritonTerminal", category = "Core", elementType = "rewritePolicy", printObject = false) 13 | public final class TritonTerminalRewrite implements RewritePolicy { 14 | 15 | ChatColorTerminalReplacer chatColorTerminalReplacer; 16 | 17 | public TritonTerminalRewrite() { 18 | try { 19 | this.chatColorTerminalReplacer = new ChatColorTerminalReplacer(); 20 | } catch (Exception | Error e) { 21 | Triton.get().getLogger().logError(e, "Failed to setup chat color terminal replacer."); 22 | } 23 | } 24 | 25 | @PluginFactory 26 | public static TritonTerminalRewrite createPolicy() { 27 | return new TritonTerminalRewrite(); 28 | } 29 | 30 | @Override 31 | public LogEvent rewrite(final LogEvent event) { 32 | Language lang = Triton.get().getLanguageManager().getMainLanguage(); 33 | if (lang == null) return event; 34 | String translated = Triton.get().getLanguageParser() 35 | .replaceLanguages(event.getMessage().getFormattedMessage(), lang.getName(), Triton.get().getConf() 36 | .getChatSyntax()); 37 | if (translated == null) 38 | return event; 39 | if (chatColorTerminalReplacer != null && Triton.get().getConf().isTerminalAnsi()) 40 | translated = chatColorTerminalReplacer.parseMessage(translated); 41 | 42 | try { 43 | NMSUtils.setDeclaredField(event, "message", new SimpleMessage(translated)); 44 | } catch (Exception | Error ignored) { 45 | } 46 | return event; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/utils/AppenderRefFactory.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.utils; 2 | 3 | import com.rexcantor64.triton.Triton; 4 | import org.apache.logging.log4j.core.Filter; 5 | import org.apache.logging.log4j.core.config.AppenderRef; 6 | 7 | import java.lang.reflect.Method; 8 | 9 | public class AppenderRefFactory { 10 | 11 | public static AppenderRef getAppenderRef(String appender) { 12 | try { 13 | return AppenderRef.createAppenderRef(appender, null, null); 14 | } catch (NoSuchMethodError e) { 15 | try { 16 | Method method = AppenderRef.class 17 | .getMethod("createAppenderRef", String.class, String.class, Filter.class); 18 | return (AppenderRef) method.invoke(null, appender, null, null); 19 | } catch (Exception | Error e1) { 20 | Triton.get().getLogger().logError(e, "Failed to inject terminal translations!"); 21 | Triton.get().getLogger().logError(e1, ""); 22 | return null; 23 | } 24 | } 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/utils/FileUtils.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.utils; 2 | 3 | import com.google.common.io.ByteStreams; 4 | import com.rexcantor64.triton.Triton; 5 | import lombok.SneakyThrows; 6 | 7 | import java.io.*; 8 | import java.nio.charset.StandardCharsets; 9 | import java.nio.file.Files; 10 | 11 | public class FileUtils { 12 | 13 | public static File getResource(String fileName, String internalFileName) { 14 | Triton.get().getLogger().logDebug("Reading %1 file...", fileName); 15 | File folder = Triton.get().getDataFolder(); 16 | if (!folder.exists()) { 17 | Triton.get().getLogger().logDebug("Plugin folder not found. Creating one..."); 18 | if (!folder.mkdirs()) { 19 | Triton.get().getLogger() 20 | .logError("Failed to create plugin folder! Please check if the server has the necessary " + 21 | "permissions. The plugin will not work correctly."); 22 | } else { 23 | Triton.get().getLogger().logDebug("Plugin folder created."); 24 | } 25 | } 26 | 27 | File resourceFile = new File(folder, fileName); 28 | try { 29 | if (!resourceFile.exists()) { 30 | Triton.get().getLogger().logDebug("File %1 not found. Creating new one...", fileName); 31 | if (!resourceFile.createNewFile()) { 32 | Triton.get().getLogger().logError("Failed to create the file %1!", fileName); 33 | } 34 | try (InputStream in = Triton.get().getLoader().getResourceAsStream(internalFileName); 35 | OutputStream out = Files.newOutputStream(resourceFile.toPath())) { 36 | ByteStreams.copy(in, out); 37 | } 38 | Triton.get().getLogger().logDebug("File %1 created successfully.", fileName); 39 | } 40 | } catch (Exception e) { 41 | e.printStackTrace(); 42 | } 43 | return resourceFile; 44 | } 45 | 46 | @SneakyThrows 47 | public static Reader getReaderFromFile(File file) { 48 | return new BufferedReader(new InputStreamReader(Files.newInputStream(file.toPath()), StandardCharsets.UTF_8)); 49 | } 50 | 51 | @SneakyThrows 52 | public static Writer getWriterFromFile(File file) { 53 | return new OutputStreamWriter(Files.newOutputStream(file.toPath()), StandardCharsets.UTF_8); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/utils/ModernComponentGetters.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.utils; 2 | 3 | import net.md_5.bungee.api.chat.BaseComponent; 4 | import net.md_5.bungee.api.chat.KeybindComponent; 5 | 6 | import java.util.Optional; 7 | 8 | /** 9 | * Avoid loading "modern" (as in, not 1.8.8) md_5's chat library classes on old Spigot versions. 10 | * This will be removed in Triton v4 (where Adventure is used instead). 11 | */ 12 | public class ModernComponentGetters { 13 | 14 | public static Optional getKeybind(BaseComponent component) { 15 | if (component instanceof KeybindComponent) { 16 | return Optional.ofNullable(((KeybindComponent) component).getKeybind()); 17 | } 18 | return Optional.empty(); 19 | } 20 | 21 | public static BaseComponent newKeybindComponent(String keybind) { 22 | return new KeybindComponent(keybind); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/utils/ScoreboardUtils.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.utils; 2 | 3 | public class ScoreboardUtils { 4 | 5 | public static String getEntrySuffix(int index) { 6 | if (index < 0) 7 | return "§k§l"; 8 | if (index < 10) 9 | return "§k§" + index; 10 | if (index == 10) 11 | return "§k§a"; 12 | if (index == 11) 13 | return "§k§b"; 14 | if (index == 12) 15 | return "§k§c"; 16 | if (index == 13) 17 | return "§k§d"; 18 | if (index == 14) 19 | return "§k§e"; 20 | if (index == 15) 21 | return "§k§f"; 22 | return "§k§l"; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/utils/SocketUtils.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.utils; 2 | 3 | import io.netty.channel.unix.DomainSocketAddress; 4 | 5 | import java.net.InetAddress; 6 | import java.net.InetSocketAddress; 7 | import java.net.SocketAddress; 8 | 9 | public class SocketUtils { 10 | 11 | public static String getIpAddress(SocketAddress address) { 12 | if (address instanceof InetSocketAddress) { 13 | InetAddress addr = ((InetSocketAddress) address).getAddress(); 14 | if (addr == null) { 15 | return null; 16 | } 17 | return addr.getHostAddress(); 18 | } 19 | 20 | if (address instanceof DomainSocketAddress) 21 | return ((DomainSocketAddress) address).path(); 22 | 23 | return null; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/utils/StringUtils.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.utils; 2 | 3 | public class StringUtils { 4 | 5 | public static String join(String delimiter, String... strings) { 6 | StringBuilder builder = new StringBuilder(); 7 | for (int i = 0; i < strings.length; i++) { 8 | if (i != 0) builder.append(delimiter); 9 | builder.append(strings[i]); 10 | } 11 | return builder.toString(); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/utils/YAMLUtils.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.utils; 2 | 3 | import com.rexcantor64.triton.config.interfaces.Configuration; 4 | import lombok.val; 5 | 6 | import java.util.HashMap; 7 | import java.util.List; 8 | 9 | public class YAMLUtils { 10 | 11 | public static List getStringOrStringList(Configuration config, String index) { 12 | val result = config.getStringList(index); 13 | if (result.size() == 0) 14 | if (config.getString(index) != null) result.add(config.getString(index)); 15 | return result; 16 | } 17 | 18 | public static HashMap deepToMap(Configuration conf, String prefix) { 19 | val result = new HashMap(); 20 | for (val key : conf.getKeys()) { 21 | Object value = conf.get(key); 22 | if (value instanceof Configuration) 23 | result.putAll(deepToMap((Configuration) value, prefix + key + ".")); 24 | else 25 | result.put(prefix + key, value); 26 | } 27 | return result; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/wrappers/AdventureComponentWrapper.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.wrappers; 2 | 3 | import net.kyori.adventure.text.Component; 4 | import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; 5 | import net.md_5.bungee.api.chat.BaseComponent; 6 | import net.md_5.bungee.chat.ComponentSerializer; 7 | 8 | public class AdventureComponentWrapper { 9 | 10 | public static BaseComponent[] toMd5Component(Object component) { 11 | return ComponentSerializer.parse(toJson(component)); 12 | } 13 | 14 | public static String toJson(Object component) { 15 | return GsonComponentSerializer.gson().serialize((Component) component); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/wrappers/HoverComponentWrapper.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.wrappers; 2 | 3 | import com.rexcantor64.triton.Triton; 4 | import com.rexcantor64.triton.api.config.FeatureSyntax; 5 | import com.rexcantor64.triton.api.language.Localized; 6 | import com.rexcantor64.triton.utils.ItemStackTranslationUtils; 7 | import lombok.val; 8 | import net.md_5.bungee.api.chat.BaseComponent; 9 | import net.md_5.bungee.api.chat.HoverEvent; 10 | import net.md_5.bungee.api.chat.ItemTag; 11 | import net.md_5.bungee.api.chat.TextComponent; 12 | import net.md_5.bungee.api.chat.hover.content.Content; 13 | import net.md_5.bungee.api.chat.hover.content.Entity; 14 | import net.md_5.bungee.api.chat.hover.content.Item; 15 | import net.md_5.bungee.api.chat.hover.content.Text; 16 | 17 | import java.util.ArrayList; 18 | import java.util.concurrent.atomic.AtomicBoolean; 19 | 20 | public class HoverComponentWrapper { 21 | 22 | public static HoverEvent handleHoverEvent(HoverEvent hoverEvent, Localized language, FeatureSyntax syntax) { 23 | val changed = new AtomicBoolean(false); 24 | 25 | val contents = hoverEvent.getContents(); 26 | val newContents = new ArrayList(); 27 | val languageParser = Triton.get().getLanguageParser(); 28 | 29 | contents.forEach((content -> { 30 | if (content instanceof Text) { 31 | val text = (Text) content; 32 | val value = text.getValue(); 33 | String valueString; 34 | if (value instanceof BaseComponent[]) { 35 | valueString = TextComponent.toLegacyText((BaseComponent[]) value); 36 | } else if (value instanceof String) { 37 | valueString = (String) value; 38 | } else { 39 | newContents.add(text); 40 | return; 41 | } 42 | // TODO implement check in replaceLanguages instead? 43 | val replaced = languageParser.replaceLanguages(Triton.get().getLanguageManager() 44 | .matchPattern(valueString, language), language, syntax); 45 | if (!valueString.equals(replaced)) { 46 | changed.set(true); 47 | if (replaced != null) // handle disabled line 48 | newContents.add(new Text(TextComponent.fromLegacyText(replaced))); 49 | return; 50 | } 51 | newContents.add(text); 52 | } else if (content instanceof Item) { 53 | val item = (Item) content; 54 | if (item.getTag() != null) { 55 | val tag = item.getTag(); 56 | val newTag = ItemTag.ofNbt(ItemStackTranslationUtils.translateNbtString(tag.getNbt(), language)); 57 | item.setTag(newTag); 58 | } 59 | newContents.add(item); 60 | } else if (content instanceof Entity) { 61 | val entity = (Entity) content; 62 | if (entity.getName() != null) { 63 | val string = TextComponent.toLegacyText(entity.getName()); 64 | // TODO implement check in replaceLanguages instead? 65 | val replaced = languageParser.replaceLanguages(Triton.get().getLanguageManager() 66 | .matchPattern(string, language), language, syntax); 67 | if (!string.equals(replaced)) { 68 | changed.set(true); 69 | if (replaced != null) // handle disabled line 70 | newContents.add(new Entity(entity.getType(), entity.getId(), new TextComponent(TextComponent 71 | .fromLegacyText(replaced)))); 72 | return; 73 | } 74 | } 75 | newContents.add(entity); 76 | } 77 | })); 78 | 79 | if (changed.get()) { 80 | if (newContents.size() == 0) return null; 81 | val newHover = new HoverEvent(hoverEvent.getAction(), newContents); 82 | newHover.setLegacy(hoverEvent.isLegacy()); 83 | return newHover; 84 | } 85 | return hoverEvent; 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/wrappers/MaterialWrapperManager.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.wrappers; 2 | 3 | import lombok.Getter; 4 | import org.bukkit.Material; 5 | 6 | import java.util.Arrays; 7 | import java.util.Objects; 8 | 9 | public class MaterialWrapperManager { 10 | 11 | private static final String[] MATERIAL_NAMES = new String[]{"BLACK_BANNER", "BANNER"}; 12 | 13 | @Getter 14 | private Material bannerMaterial; 15 | 16 | public MaterialWrapperManager() { 17 | this.bannerMaterial = Arrays.stream(MATERIAL_NAMES) 18 | .map(Material::getMaterial) 19 | .filter(Objects::nonNull) 20 | .findFirst() 21 | .orElseThrow(() -> 22 | new RuntimeException("Can't find a suitable material for banners for this Minecraft version. The plugin might not work as expected. Is it up-to-date?") 23 | ); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/wrappers/WrappedAdvancementHolder.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.wrappers; 2 | 3 | import com.comphenix.protocol.reflect.EquivalentConverter; 4 | import com.comphenix.protocol.reflect.accessors.Accessors; 5 | import com.comphenix.protocol.reflect.accessors.FieldAccessor; 6 | import com.comphenix.protocol.utility.MinecraftReflection; 7 | import com.comphenix.protocol.wrappers.AbstractWrapper; 8 | import com.comphenix.protocol.wrappers.Converters; 9 | import org.jetbrains.annotations.Contract; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | /** 13 | * Custom ProtocolLib Wrapper of NMS' AdvancementHolder (added to NMS in 1.20.2) 14 | * Tested with 1.20.2 15 | */ 16 | public class WrappedAdvancementHolder extends AbstractWrapper { 17 | 18 | private static Class ADVANCEMENT_HOLDER = MinecraftReflection.getMinecraftClass("advancements.AdvancementHolder"); 19 | private static Class ADVANCEMENT_CLASS = MinecraftReflection.getMinecraftClass("advancements.Advancement", "Advancement"); 20 | private static FieldAccessor ADVANCEMENT = Accessors.getFieldAccessor(ADVANCEMENT_HOLDER, ADVANCEMENT_CLASS, true); 21 | 22 | public static final EquivalentConverter CONVERTER = Converters.ignoreNull(Converters.handle(WrappedAdvancementHolder::getHandle, WrappedAdvancementHolder::fromHandle, WrappedAdvancementHolder.class)); 23 | 24 | /** 25 | * Construct a new AdvancementDisplay wrapper. 26 | */ 27 | private WrappedAdvancementHolder(Object handle) { 28 | super(getWrappedClass()); 29 | setHandle(handle); 30 | } 31 | 32 | public WrappedAdvancement getAdvancement() { 33 | return WrappedAdvancement.CONVERTER.getSpecific(ADVANCEMENT.get(handle)); 34 | } 35 | 36 | public void setAdvancement(WrappedAdvancement advancement) { 37 | ADVANCEMENT.set(handle, WrappedAdvancement.CONVERTER.getGeneric(advancement)); 38 | } 39 | 40 | /** 41 | * Construct a wrapped advancement display from a native NMS object. 42 | * 43 | * @param handle - the native object. 44 | * @return The wrapped advancement display object. 45 | */ 46 | @Contract("_ -> new") 47 | public static @NotNull WrappedAdvancementHolder fromHandle(Object handle) { 48 | return new WrappedAdvancementHolder(handle); 49 | } 50 | 51 | public static Class getWrappedClass() { 52 | return ADVANCEMENT_HOLDER; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/wrappers/WrappedClientConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.wrappers; 2 | 3 | import com.comphenix.protocol.reflect.EquivalentConverter; 4 | import com.comphenix.protocol.reflect.accessors.Accessors; 5 | import com.comphenix.protocol.reflect.accessors.FieldAccessor; 6 | import com.comphenix.protocol.utility.MinecraftReflection; 7 | import com.comphenix.protocol.wrappers.AbstractWrapper; 8 | import com.comphenix.protocol.wrappers.Converters; 9 | 10 | /** 11 | * Custom ProtocolLib Wrapper of NMS' ClientConfiguration. 12 | * Added to NMS in 1.20.2. 13 | */ 14 | public class WrappedClientConfiguration extends AbstractWrapper { 15 | 16 | private static Class CLIENT_INFORMATION = MinecraftReflection.getMinecraftClass("server.level.ClientInformation"); 17 | private static FieldAccessor LOCALE = Accessors.getFieldAccessor(CLIENT_INFORMATION, String.class, true); 18 | 19 | public static final EquivalentConverter CONVERTER = Converters.ignoreNull(Converters.handle(WrappedClientConfiguration::getHandle, WrappedClientConfiguration::fromHandle, WrappedClientConfiguration.class)); 20 | 21 | /** 22 | * Construct a new ClientConfiguration wrapper. 23 | */ 24 | private WrappedClientConfiguration(Object handle) { 25 | super(getWrappedClass()); 26 | setHandle(handle); 27 | } 28 | 29 | public String getLocale() { 30 | return (String) LOCALE.get(handle); 31 | } 32 | 33 | /** 34 | * Construct a wrapped advancement display from a native NMS object. 35 | * 36 | * @param handle - the native object. 37 | * @return The wrapped advancement display object. 38 | */ 39 | public static WrappedClientConfiguration fromHandle(Object handle) { 40 | return new WrappedClientConfiguration(handle); 41 | } 42 | 43 | public static Class getWrappedClass() { 44 | return CLIENT_INFORMATION; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/wrappers/WrappedPlayerChatMessage.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.wrappers; 2 | 3 | import com.comphenix.protocol.reflect.EquivalentConverter; 4 | import com.comphenix.protocol.reflect.FuzzyReflection; 5 | import com.comphenix.protocol.reflect.accessors.Accessors; 6 | import com.comphenix.protocol.reflect.accessors.FieldAccessor; 7 | import com.comphenix.protocol.utility.MinecraftReflection; 8 | import com.comphenix.protocol.wrappers.AbstractWrapper; 9 | import com.comphenix.protocol.wrappers.BukkitConverters; 10 | import com.comphenix.protocol.wrappers.Converters; 11 | import com.comphenix.protocol.wrappers.WrappedChatComponent; 12 | import org.jetbrains.annotations.Contract; 13 | import org.jetbrains.annotations.NotNull; 14 | 15 | import java.util.Optional; 16 | 17 | /** 18 | * Custom ProtocolLib Wrapper of NMS' PlayerChatMessage (added to NMS in 1.19 and removed in 1.19.3) 19 | * Tested with 1.19.2 20 | */ 21 | public class WrappedPlayerChatMessage extends AbstractWrapper { 22 | 23 | private static Class PLAYER_CHAT_MESSAGE = MinecraftReflection.getMinecraftClass("network.chat.PlayerChatMessage"); 24 | private static FuzzyReflection FUZZY_REFLECTION = FuzzyReflection.fromClass(PLAYER_CHAT_MESSAGE, true); 25 | private static FieldAccessor CHAT_COMPONENT = Accessors.getFieldAccessor(FUZZY_REFLECTION.getParameterizedField(Optional.class, MinecraftReflection.getIChatBaseComponentClass())); 26 | private static EquivalentConverter> CHAT_COMPONENT_CONVERTER = Converters.optional(BukkitConverters.getWrappedChatComponentConverter()); 27 | 28 | public static final EquivalentConverter CONVERTER = Converters.ignoreNull(Converters.handle(WrappedPlayerChatMessage::getHandle, WrappedPlayerChatMessage::fromHandle, WrappedPlayerChatMessage.class)); 29 | 30 | /** 31 | * Construct a new PlayerChatMessage wrapper. 32 | */ 33 | private WrappedPlayerChatMessage(Object handle) { 34 | super(getWrappedClass()); 35 | setHandle(handle); 36 | } 37 | 38 | public Optional getMessage() { 39 | return CHAT_COMPONENT_CONVERTER.getSpecific(CHAT_COMPONENT.get(handle)); 40 | } 41 | 42 | @SuppressWarnings("OptionalUsedAsFieldOrParameterType") 43 | public void setMessage(Optional message) { 44 | CHAT_COMPONENT.set(handle, CHAT_COMPONENT_CONVERTER.getGeneric(message)); 45 | } 46 | 47 | /** 48 | * Construct a player chat message from a native NMS object. 49 | * 50 | * @param handle - the native object. 51 | * @return The wrapped player chat message object. 52 | */ 53 | @Contract("_ -> new") 54 | public static @NotNull WrappedPlayerChatMessage fromHandle(Object handle) { 55 | return new WrappedPlayerChatMessage(handle); 56 | } 57 | 58 | public static Class getWrappedClass() { 59 | return PLAYER_CHAT_MESSAGE; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/wrappers/items/ItemStackParser.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.wrappers.items; 2 | 3 | import com.comphenix.protocol.utility.MinecraftVersion; 4 | import com.rexcantor64.triton.Triton; 5 | import com.rexcantor64.triton.banners.Banner; 6 | import com.rexcantor64.triton.banners.Colors; 7 | import net.md_5.bungee.api.ChatColor; 8 | import org.bukkit.DyeColor; 9 | import org.bukkit.block.banner.Pattern; 10 | import org.bukkit.block.banner.PatternType; 11 | import org.bukkit.inventory.ItemFlag; 12 | import org.bukkit.inventory.ItemStack; 13 | import org.bukkit.inventory.meta.BannerMeta; 14 | 15 | import java.util.Collections; 16 | import java.util.Objects; 17 | import java.util.stream.Stream; 18 | 19 | public class ItemStackParser { 20 | 21 | private final static ItemFlag[] ITEM_FLAGS; 22 | 23 | static { 24 | // Enum value was renamed in MC 1.20.6 25 | ItemFlag hideAdditionalTooltipFlag = Stream.of("HIDE_POTION_EFFECTS", "HIDE_ADDITIONAL_TOOLTIP") 26 | .map(name -> { 27 | try { 28 | return ItemFlag.valueOf(name); 29 | } catch (IllegalArgumentException e) { 30 | return null; 31 | } 32 | }) 33 | .filter(Objects::nonNull) 34 | .findFirst() 35 | .orElseThrow(() -> new RuntimeException("Failed to get HIDE_ADDITIONAL_TOOLTIP item flag")); 36 | 37 | ITEM_FLAGS = new ItemFlag[] { 38 | ItemFlag.HIDE_ENCHANTS, 39 | ItemFlag.HIDE_ATTRIBUTES, 40 | ItemFlag.HIDE_UNBREAKABLE, 41 | ItemFlag.HIDE_DESTROYS, 42 | ItemFlag.HIDE_PLACED_ON, 43 | hideAdditionalTooltipFlag, 44 | }; 45 | } 46 | 47 | public static ItemStack bannerToItemStack(Banner banner, boolean active) { 48 | ItemStack is = new ItemStack(Triton.asSpigot().getWrapperManager().getBannerMaterial()); 49 | BannerMeta bm = (BannerMeta) is.getItemMeta(); 50 | for (Banner.Layer layer : banner.getLayers()) 51 | bm.addPattern(new Pattern(getDyeColor(layer.getColor()), PatternType 52 | .valueOf(layer.getPattern().getType()))); 53 | bm.setDisplayName(ChatColor.translateAlternateColorCodes('&', banner.getDisplayName())); 54 | if (active) 55 | bm.setLore(Collections.singletonList(ChatColor 56 | .translateAlternateColorCodes('&', Triton.get().getMessagesConfig().getMessage("other.selected")))); 57 | bm.addItemFlags(ITEM_FLAGS); 58 | is.setItemMeta(bm); 59 | return is; 60 | } 61 | 62 | private static DyeColor getDyeColor(Colors color) { 63 | if (color == Colors.GRAY && MinecraftVersion.AQUATIC_UPDATE.atOrAbove()) { 64 | // On 1.12 and below, this color is called "SILVER" 65 | return DyeColor.valueOf("SILVER"); 66 | } 67 | return DyeColor.valueOf(color.getColor()); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/wrappers/items/WrappedFilterable.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.wrappers.items; 2 | 3 | import com.comphenix.protocol.reflect.EquivalentConverter; 4 | import com.comphenix.protocol.reflect.accessors.Accessors; 5 | import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; 6 | import com.comphenix.protocol.reflect.accessors.FieldAccessor; 7 | import com.comphenix.protocol.utility.MinecraftReflection; 8 | import com.comphenix.protocol.wrappers.AbstractWrapper; 9 | import com.comphenix.protocol.wrappers.Converters; 10 | import lombok.Getter; 11 | import org.jetbrains.annotations.Contract; 12 | import org.jetbrains.annotations.NotNull; 13 | 14 | import java.util.Optional; 15 | 16 | /** 17 | * Custom ProtocolLib Wrapper of NMS' Filterable (added to NMS in 1.20.6). 18 | * This is used while storing written book's content. 19 | * 20 | * @since 3.9.6 21 | */ 22 | @Getter 23 | public class WrappedFilterable extends AbstractWrapper { 24 | private static final Class ITEM_LORE = MinecraftReflection.getMinecraftClass("server.network.Filterable"); 25 | private static final FieldAccessor RAW_FIELD = Accessors.getFieldAccessor(ITEM_LORE, Object.class, true); 26 | private static final FieldAccessor FILTERED_FIELD = Accessors.getFieldAccessor(ITEM_LORE, Optional.class, true); 27 | private static final ConstructorAccessor CONSTRUCTOR = Accessors.getConstructorAccessor(ITEM_LORE, Object.class, Optional.class); 28 | 29 | private final EquivalentConverter innerConverter; 30 | 31 | private WrappedFilterable(Object handle, EquivalentConverter innerConverter) { 32 | super(getWrappedClass()); 33 | setHandle(handle); 34 | this.innerConverter = innerConverter; 35 | } 36 | 37 | public @NotNull T getRaw() { 38 | return this.innerConverter.getSpecific(RAW_FIELD.get(this.getHandle())); 39 | } 40 | 41 | public void setRaw(@NotNull T inner) { 42 | setHandle(CONSTRUCTOR.invoke( 43 | this.innerConverter.getGeneric(inner), 44 | FILTERED_FIELD.get(getHandle()) 45 | )); 46 | } 47 | 48 | /** 49 | * Construct a filterable from a native NMS object. 50 | * 51 | * @param handle - the native object. 52 | * @return The wrapped filterable. 53 | */ 54 | @Contract("_, _ -> new") 55 | public static @NotNull WrappedFilterable fromHandle(Object handle, EquivalentConverter innerConverter) { 56 | return new WrappedFilterable(handle, innerConverter); 57 | } 58 | 59 | public static EquivalentConverter> getConverter(EquivalentConverter innerConverter) { 60 | return Converters.ignoreNull(new EquivalentConverter>() { 61 | @Override 62 | public Object getGeneric(WrappedFilterable wrappedFilterable) { 63 | return wrappedFilterable.getHandle(); 64 | } 65 | 66 | @Override 67 | public WrappedFilterable getSpecific(Object handle) { 68 | return WrappedFilterable.fromHandle(handle, innerConverter); 69 | } 70 | 71 | @SuppressWarnings("unchecked") 72 | @Override 73 | public Class> getSpecificType() { 74 | // Damn you Java 75 | Class dummy = WrappedFilterable.class; 76 | return (Class>) dummy; 77 | } 78 | }); 79 | } 80 | 81 | public static Class getWrappedClass() { 82 | return ITEM_LORE; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/wrappers/items/WrappedItemContainerContents.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.wrappers.items; 2 | 3 | import com.comphenix.protocol.reflect.EquivalentConverter; 4 | import com.comphenix.protocol.reflect.FuzzyReflection; 5 | import com.comphenix.protocol.reflect.accessors.Accessors; 6 | import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; 7 | import com.comphenix.protocol.reflect.accessors.FieldAccessor; 8 | import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; 9 | import com.comphenix.protocol.utility.MinecraftReflection; 10 | import com.comphenix.protocol.wrappers.AbstractWrapper; 11 | import com.comphenix.protocol.wrappers.BukkitConverters; 12 | import com.comphenix.protocol.wrappers.Converters; 13 | import org.bukkit.inventory.ItemStack; 14 | import org.jetbrains.annotations.Contract; 15 | import org.jetbrains.annotations.NotNull; 16 | 17 | import java.util.List; 18 | 19 | /** 20 | * Custom ProtocolLib Wrapper of NMS' ItemContainerContents (added to NMS in 1.20.6). 21 | * This is used to store the items that are inside a container (e.g., shulker box). 22 | * 23 | * @since 3.9.6 24 | */ 25 | public class WrappedItemContainerContents extends AbstractWrapper { 26 | private static final Class ITEM_CONTAINER_CONTENTS = MinecraftReflection.getMinecraftClass("world.item.component.ItemContainerContents"); 27 | private static final FieldAccessor ITEMS_FIELD = Accessors.getFieldAccessor(ITEM_CONTAINER_CONTENTS, MinecraftReflection.getNonNullListClass(), true); 28 | private static final ConstructorAccessor CONSTRUCTOR = Accessors.getConstructorAccessor( 29 | FuzzyReflection.fromClass(ITEM_CONTAINER_CONTENTS, true).getConstructor( 30 | FuzzyMethodContract.newBuilder() 31 | .parameterExactType(List.class) 32 | .build() 33 | ) 34 | ); 35 | 36 | private static final EquivalentConverter ITEM_STACK_CONVERTER = BukkitConverters.getItemStackConverter(); 37 | private static final EquivalentConverter> ITEM_STACK_LIST_CONVERTER = BukkitConverters.getListConverter(ITEM_STACK_CONVERTER); 38 | 39 | private static final EquivalentConverter CONVERTER = Converters.ignoreNull(Converters.handle(AbstractWrapper::getHandle, WrappedItemContainerContents::fromHandle, WrappedItemContainerContents.class)); 40 | 41 | private WrappedItemContainerContents(Object handle) { 42 | super(getWrappedClass()); 43 | setHandle(handle); 44 | } 45 | 46 | public @NotNull List getItems() { 47 | return ITEM_STACK_LIST_CONVERTER.getSpecific(ITEMS_FIELD.get(this.getHandle())); 48 | } 49 | 50 | public void setItems(@NotNull List items) { 51 | this.handle = CONSTRUCTOR.invoke(ITEM_STACK_LIST_CONVERTER.getGeneric(items)); 52 | } 53 | 54 | /** 55 | * Construct item container contents from a native NMS object. 56 | * 57 | * @param handle - the native object. 58 | * @return The wrapped item container contents. 59 | */ 60 | @Contract("_ -> new") 61 | public static @NotNull WrappedItemContainerContents fromHandle(Object handle) { 62 | return new WrappedItemContainerContents(handle); 63 | } 64 | 65 | public static EquivalentConverter getConverter() { 66 | return CONVERTER; 67 | } 68 | 69 | public static Class getWrappedClass() { 70 | return ITEM_CONTAINER_CONTENTS; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/wrappers/items/WrappedItemLore.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.wrappers.items; 2 | 3 | import com.comphenix.protocol.reflect.EquivalentConverter; 4 | import com.comphenix.protocol.reflect.accessors.Accessors; 5 | import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; 6 | import com.comphenix.protocol.reflect.accessors.FieldAccessor; 7 | import com.comphenix.protocol.utility.MinecraftReflection; 8 | import com.comphenix.protocol.wrappers.AbstractWrapper; 9 | import com.comphenix.protocol.wrappers.BukkitConverters; 10 | import com.comphenix.protocol.wrappers.Converters; 11 | import com.comphenix.protocol.wrappers.WrappedChatComponent; 12 | import org.jetbrains.annotations.Contract; 13 | import org.jetbrains.annotations.NotNull; 14 | 15 | import java.util.List; 16 | 17 | /** 18 | * Custom ProtocolLib Wrapper of NMS' ItemLore (added to NMS in 1.20.6). 19 | * This is used to store the lore of items. 20 | * 21 | * @since 3.9.6 22 | */ 23 | public class WrappedItemLore extends AbstractWrapper { 24 | private static final Class ITEM_LORE = MinecraftReflection.getMinecraftClass("world.item.component.ItemLore"); 25 | private static final FieldAccessor LINES_FIELD = Accessors.getFieldAccessor(ITEM_LORE, List.class, true); 26 | private static final ConstructorAccessor CONSTRUCTOR = Accessors.getConstructorAccessor(ITEM_LORE, List.class); 27 | 28 | private static final EquivalentConverter> COMPONENT_LIST_CONVERTER = BukkitConverters.getListConverter(BukkitConverters.getWrappedChatComponentConverter()); 29 | 30 | private static final EquivalentConverter CONVERTER = Converters.ignoreNull(Converters.handle(AbstractWrapper::getHandle, WrappedItemLore::fromHandle, WrappedItemLore.class)); 31 | 32 | private WrappedItemLore(Object handle) { 33 | super(getWrappedClass()); 34 | setHandle(handle); 35 | } 36 | 37 | public @NotNull List getLines() { 38 | return COMPONENT_LIST_CONVERTER.getSpecific(LINES_FIELD.get(this.getHandle())); 39 | } 40 | 41 | public void setLines(@NotNull List lines) { 42 | setHandle(CONSTRUCTOR.invoke(COMPONENT_LIST_CONVERTER.getGeneric(lines))); 43 | } 44 | 45 | /** 46 | * Construct an item lore from a native NMS object. 47 | * 48 | * @param handle - the native object. 49 | * @return The wrapped item lore. 50 | */ 51 | @Contract("_ -> new") 52 | public static @NotNull WrappedItemLore fromHandle(Object handle) { 53 | return new WrappedItemLore(handle); 54 | } 55 | 56 | public static EquivalentConverter getConverter() { 57 | return CONVERTER; 58 | } 59 | 60 | public static Class getWrappedClass() { 61 | return ITEM_LORE; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /core/src/main/java/com/rexcantor64/triton/wrappers/items/WrappedWrittenBookContent.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.wrappers.items; 2 | 3 | import com.comphenix.protocol.reflect.EquivalentConverter; 4 | import com.comphenix.protocol.reflect.accessors.Accessors; 5 | import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; 6 | import com.comphenix.protocol.reflect.accessors.FieldAccessor; 7 | import com.comphenix.protocol.utility.MinecraftReflection; 8 | import com.comphenix.protocol.wrappers.AbstractWrapper; 9 | import com.comphenix.protocol.wrappers.BukkitConverters; 10 | import com.comphenix.protocol.wrappers.Converters; 11 | import com.comphenix.protocol.wrappers.WrappedChatComponent; 12 | import org.jetbrains.annotations.Contract; 13 | import org.jetbrains.annotations.NotNull; 14 | 15 | import java.util.List; 16 | 17 | /** 18 | * Custom ProtocolLib Wrapper of NMS' WrittenBookContent (added to NMS in 1.20.6). 19 | * This is used to store metadata about written books. 20 | * 21 | * @since 3.9.6 22 | */ 23 | public class WrappedWrittenBookContent extends AbstractWrapper { 24 | private static final Class WRITTEN_BOOK_CONTENT = MinecraftReflection.getMinecraftClass("world.item.component.WrittenBookContent"); 25 | 26 | private static final FieldAccessor TITLE_FIELD = Accessors.getFieldAccessor(WRITTEN_BOOK_CONTENT, WrappedFilterable.getWrappedClass(), true); 27 | private static final FieldAccessor AUTHOR_FIELD = Accessors.getFieldAccessor(WRITTEN_BOOK_CONTENT, String.class, true); 28 | private static final FieldAccessor GENERATION_FIELD = Accessors.getFieldAccessor(WRITTEN_BOOK_CONTENT, int.class, true); 29 | private static final FieldAccessor PAGES_FIELD = Accessors.getFieldAccessor(WRITTEN_BOOK_CONTENT, List.class, true); 30 | private static final FieldAccessor RESOLVED_FIELD = Accessors.getFieldAccessor(WRITTEN_BOOK_CONTENT, boolean.class, true); 31 | 32 | private static final ConstructorAccessor CONSTRUCTOR = Accessors.getConstructorAccessor(WRITTEN_BOOK_CONTENT, WrappedFilterable.getWrappedClass(), String.class, int.class, List.class, boolean.class); 33 | 34 | private static final EquivalentConverter>> FILTERABLE_COMPONENT_LIST_CONVERTER = BukkitConverters.getListConverter(WrappedFilterable.getConverter(BukkitConverters.getWrappedChatComponentConverter())); 35 | 36 | private static final EquivalentConverter CONVERTER = Converters.ignoreNull(Converters.handle(AbstractWrapper::getHandle, WrappedWrittenBookContent::fromHandle, WrappedWrittenBookContent.class)); 37 | 38 | private WrappedWrittenBookContent(Object handle) { 39 | super(getWrappedClass()); 40 | setHandle(handle); 41 | } 42 | 43 | public @NotNull List> getPages() { 44 | return FILTERABLE_COMPONENT_LIST_CONVERTER.getSpecific(PAGES_FIELD.get(this.getHandle())); 45 | } 46 | 47 | public void setPages(@NotNull List> pages) { 48 | setHandle(CONSTRUCTOR.invoke( 49 | TITLE_FIELD.get(getHandle()), 50 | AUTHOR_FIELD.get(getHandle()), 51 | GENERATION_FIELD.get(getHandle()), 52 | FILTERABLE_COMPONENT_LIST_CONVERTER.getGeneric(pages), 53 | RESOLVED_FIELD.get(getHandle()) 54 | )); 55 | } 56 | 57 | /** 58 | * Construct a written book content from a native NMS object. 59 | * 60 | * @param handle - the native object. 61 | * @return The wrapped written book content. 62 | */ 63 | @Contract("_ -> new") 64 | public static @NotNull WrappedWrittenBookContent fromHandle(Object handle) { 65 | return new WrappedWrittenBookContent(handle); 66 | } 67 | 68 | public static EquivalentConverter getConverter() { 69 | return CONVERTER; 70 | } 71 | 72 | public static Class getWrappedClass() { 73 | return WRITTEN_BOOK_CONTENT; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /core/src/main/resources/messages.yml: -------------------------------------------------------------------------------- 1 | error: 2 | no-permission: "&cNo permission. Permission required: &4%1" 3 | message-not-found: "ERROR 404: Message not found: '%1'! Args: %2" 4 | lang-not-found: "&cLanguage &4%1&c not found! &7&oNote: It's case sensitive. Use TAB to show all the available languages." 5 | player-not-found-use-uuid: "&cPlayer &4%1&c not found! Use UUID to search through offline players." 6 | bungee-reload-invalid-mode: "&cMode &4%1&c does not exist. Available modes are 'bungee' (or 'b'), 'server' (or 's') and 'all' (or 'a')." 7 | not-sign: "&cYou're not looking at a sign." 8 | sign-not-found: "&cSign group &4%1&c not found! &7&oNote: It's case sensitive. Use TAB to all the available options." 9 | database-invalid-mode: "&cMode &4%1&c does not exist. Available modes are 'upload' (or 'u') and 'download' (or 'd')." 10 | database-not-supported: "&cThis command isn't supported on local storage." 11 | not-available-on-spigot: "&cThis action isn't available on Spigot when using BungeeCord. Please run this through BungeeCord instead." 12 | only-console: "&cThis command can only be executed from the server console." 13 | help: 14 | menu: 15 | - "&8-------[ &b&lTriton&8 ]-------" 16 | - "&bAvailable commands:" 17 | - "%1" 18 | menu-item: "&b/%1 &3%2 &8&l- &f%3" 19 | getflag: "&7Use &b/%1 getflag " 20 | setlanguage: "&7Use &b/%1 setlanguage [player]" 21 | sign: "&7Use &b/%1 sign &7 to add a sign to a group." 22 | database: "&7Use &b/%1 database " 23 | loglevel: "&7Use &b/%1 loglevel [number]" 24 | command: 25 | help: "Displays this message" 26 | getflag: "Gives the flag (banner) of a language to you" 27 | openselector: "Opens a GUI where you can change your language" 28 | reload: "Reloads the plugin" 29 | setlanguage: "Changes the language of a player" 30 | sign: "Adds or removes signs from sign groups" 31 | twin: "Uploads/downloads your config to/from TWIN for easy configuration" 32 | database: "Uploads/downloads translations to/from a database" 33 | info: "Shows information about the plugin (version, author, storage, etc)" 34 | loglevel: "See or change Triton's log level temporarily" 35 | success: 36 | getflag: "&bYou received the &7%1&b flag!" 37 | selector: "&bYour language has been changed to &7%1" 38 | reload: "&bConfig successfully reloaded" 39 | bungee-reload: "&bBungeeCord config successfully reloaded" 40 | setlanguage: "&bYour language has been changed to &7%1" 41 | setlanguage-others: "&7%1&b's language has been changed to &7%2" 42 | detected-language: "&bYour language has been automatically set to &7%1" 43 | sign-set: "&bSign successfully added to group &7%1" 44 | sign-remove: "&bSuccessfully removed sign from all groups" 45 | database: "&bOperation successful" 46 | current-loglevel: "&aTriton is currently outputting at log level %1" 47 | set-loglevel: "&aTriton's log level has been changed to %1 until the plugin is reloaded" 48 | twin: 49 | failed-bungeecord: "&cCan't upload the config because you have BungeeCord enabled on config! Please execute this command through BungeeCord." 50 | no-internet: "&cFailed to upload config. Please check your internet connection and/or firewall! Error description: %1" 51 | failed-fetch: "&cFailed to fetch the config: &7%1" 52 | failed-upload: "&cFailed to upload the config: &7%1" 53 | incorrect-status: "&7status is not 200 (received &c%1&7)" 54 | connecting: "&bConnecting to TWIN... Please wait" 55 | uploaded: "&bYour config is live! Start editing now at &3%1" 56 | success: "&bSuccessfully fetched the config from TWIN and applied it into the server!" 57 | no-token: "&cInvalid token! Please check if TWIN is setup correctly on config." 58 | repeated-download: "&c&lWARNING &cYou're trying to download the same config twice! This might break the translation files. If you want to bypass this message, just execute the same command again." 59 | failed-collection-save: "&cError while saving collections to storage. Check the console for error details." 60 | suggestion-wrong-command: "&cDid you mean to execute &b%1&c instead?" 61 | other: 62 | selector-gui-name: "&bSelect a language" 63 | selector-gui-prev: "&bPrevious" 64 | selector-gui-forward: "&bNext" 65 | selector-gui-currentpage: "&bPage &3%1&b of &3%2" 66 | selected: "&bCurrently selected" 67 | database-loading: "&bPerforming operation..." 68 | info-command: 69 | - "&8-------[ &b&lTriton&8 ]-------" 70 | - "&bVersion: &7%1" 71 | - "&bDeveloped by: &7%2" 72 | - "&bStorage in use: &7%3" 73 | - "&bUsing BungeeCord: &7%4" -------------------------------------------------------------------------------- /core/src/test/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tritonmc/Triton/da285c4ed5aa321da59fc27d3f9ac055b378b1dc/core/src/test/.gitkeep -------------------------------------------------------------------------------- /core/src/test/java/com/rexcantor64/triton/language/parser/AdvancedComponentTest.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.language.parser; 2 | 3 | import net.md_5.bungee.api.ChatColor; 4 | import net.md_5.bungee.api.chat.BaseComponent; 5 | import net.md_5.bungee.api.chat.HoverEvent; 6 | import net.md_5.bungee.api.chat.TextComponent; 7 | import net.md_5.bungee.api.chat.hover.content.Text; 8 | import net.md_5.bungee.chat.ComponentSerializer; 9 | import org.junit.jupiter.api.Test; 10 | 11 | import java.awt.*; 12 | 13 | import static org.junit.jupiter.api.Assertions.assertEquals; 14 | 15 | public class AdvancedComponentTest { 16 | 17 | @Test 18 | public void testColorCodeBetweenClickEvent() { 19 | BaseComponent root = new TextComponent(); 20 | BaseComponent child1 = new TextComponent("Testing"); 21 | child1.setColor(ChatColor.GRAY); 22 | child1.setStrikethrough(true); 23 | child1.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(""))); 24 | root.addExtra(child1); 25 | BaseComponent child2 = new TextComponent("another test"); 26 | child2.setColor(ChatColor.GRAY); 27 | child2.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(""))); 28 | root.addExtra(child2); 29 | 30 | AdvancedComponent advancedComponent = AdvancedComponent.fromBaseComponent(root); 31 | advancedComponent.setText(advancedComponent.getTextClean()); 32 | 33 | BaseComponent[] components = advancedComponent.toBaseComponent(); 34 | 35 | String expectedResultJson = "{\"extra\":[{\"strikethrough\":true,\"color\":\"gray\",\"hoverEvent\":{\"action\":\"show_text\",\"contents\":\"\"},\"extra\":[{\"text\":\"Testing\"}],\"text\":\"\"},{\"color\":\"gray\",\"hoverEvent\":{\"action\":\"show_text\",\"contents\":\"\"},\"extra\":[{\"text\":\"another test\"}],\"text\":\"\"}],\"text\":\"\"}"; 36 | assertEquals(expectedResultJson, ComponentSerializer.toString(components)); 37 | } 38 | 39 | @Test 40 | public void testShadowColor() { 41 | BaseComponent root = new TextComponent(); 42 | BaseComponent child1 = new TextComponent("Testing"); 43 | child1.setColor(ChatColor.GRAY); 44 | child1.setStrikethrough(true); 45 | child1.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(""))); 46 | root.addExtra(child1); 47 | BaseComponent child2 = new TextComponent("another test"); 48 | child2.setColor(ChatColor.GRAY); 49 | child2.setShadowColor(new Color(0x33, 0x44, 0x55, 0x88)); 50 | root.addExtra(child2); 51 | 52 | AdvancedComponent advancedComponent = AdvancedComponent.fromBaseComponent(root); 53 | advancedComponent.setText(advancedComponent.getTextClean()); 54 | 55 | BaseComponent[] components = advancedComponent.toBaseComponent(); 56 | 57 | String expectedResultJson = "{\"extra\":[{\"strikethrough\":true,\"color\":\"gray\",\"hoverEvent\":{\"action\":\"show_text\",\"contents\":\"\"},\"extra\":[{\"text\":\"Testing\"}],\"text\":\"\"},{\"color\":\"gray\",\"shadow_color\":-2009906091,\"text\":\"another test\"}],\"text\":\"\"}"; 58 | assertEquals(expectedResultJson, ComponentSerializer.toString(components)); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /core/src/test/java/com/rexcantor64/triton/utils/ComponentUtilsTest.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.utils; 2 | 3 | import lombok.val; 4 | import net.md_5.bungee.api.ChatColor; 5 | import net.md_5.bungee.api.chat.BaseComponent; 6 | import net.md_5.bungee.api.chat.TextComponent; 7 | import net.md_5.bungee.chat.ComponentSerializer; 8 | import org.junit.jupiter.api.Test; 9 | 10 | import java.util.Arrays; 11 | import java.util.Collections; 12 | import java.util.stream.Collectors; 13 | 14 | import static org.junit.jupiter.api.Assertions.assertEquals; 15 | 16 | public class ComponentUtilsTest { 17 | 18 | @Test 19 | public void testSplitByNewLineWithoutExtras() { 20 | BaseComponent component = new TextComponent("First line\nSecond line"); 21 | component.setColor(ChatColor.BLUE); 22 | 23 | val result = ComponentUtils.splitByNewLine(Collections.singletonList(component)); 24 | 25 | assertEquals(2, result.size()); 26 | 27 | val jsonResult = result.stream().map(ComponentSerializer::toString).collect(Collectors.toList()); 28 | 29 | assertEquals("[{\"color\":\"blue\",\"text\":\"First line\"}]", jsonResult.get(0)); 30 | assertEquals("[{\"color\":\"blue\",\"text\":\"Second line\"}]", jsonResult.get(1)); 31 | } 32 | 33 | @Test 34 | public void testSplitByNewLineWithExtras() { 35 | BaseComponent root = new TextComponent(); 36 | root.setItalic(true); 37 | BaseComponent child1 = new TextComponent("First line\nSecond "); 38 | child1.setColor(ChatColor.BLACK); 39 | root.addExtra(child1); 40 | BaseComponent child2 = new TextComponent("li"); 41 | child2.setColor(ChatColor.RED); 42 | BaseComponent childChild = new TextComponent("ne\nThird line"); 43 | childChild.setUnderlined(true); 44 | child2.addExtra(childChild); 45 | child2.setBold(true); 46 | root.addExtra(child2); 47 | 48 | val result = ComponentUtils.splitByNewLine(Collections.singletonList(root)); 49 | 50 | assertEquals(3, result.size()); 51 | 52 | val jsonResult = result.stream().map(ComponentSerializer::toString).collect(Collectors.toList()); 53 | 54 | assertEquals("[{\"italic\":true,\"extra\":[{\"color\":\"black\",\"text\":\"First line\"}],\"text\":\"\"}]", jsonResult.get(0)); 55 | assertEquals("[{\"italic\":true,\"extra\":[{\"color\":\"black\",\"text\":\"Second \"},{\"bold\":true,\"color\":\"red\",\"extra\":[{\"underlined\":true,\"text\":\"ne\"}],\"text\":\"li\"}],\"text\":\"\"}]", jsonResult.get(1)); 56 | assertEquals("[{\"italic\":true,\"extra\":[{\"bold\":true,\"color\":\"red\",\"extra\":[{\"underlined\":true,\"text\":\"Third line\"}],\"text\":\"\"}],\"text\":\"\"}]", jsonResult.get(2)); 57 | } 58 | 59 | @Test 60 | public void testSplitByNewLineWithSlashNAtTheEnd() { 61 | BaseComponent[] root = ComponentSerializer.parse("{\"extra\":[{\"color\":\"dark_purple\",\"text\":\"First line!\\n\"},{\"color\":\"gray\",\"text\":\"Second Line\"}],\"text\":\"\"}"); 62 | 63 | val result = ComponentUtils.splitByNewLine(Arrays.asList(root)); 64 | 65 | assertEquals(2, result.size()); 66 | 67 | val jsonResult = result.stream().map(ComponentSerializer::toString).collect(Collectors.toList()); 68 | 69 | assertEquals("[{\"extra\":[{\"color\":\"dark_purple\",\"text\":\"First line!\"}],\"text\":\"\"}]", jsonResult.get(0)); 70 | assertEquals("[{\"extra\":[{\"color\":\"dark_purple\",\"text\":\"\"},{\"color\":\"gray\",\"text\":\"Second Line\"}],\"text\":\"\"}]", jsonResult.get(1)); 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | This folder contains example files in order to help users understand how to use the plugin. 2 | 3 | `languages.json`: Example of this file on the Spigot side. 4 | `languages_bungee.json`: Example of `languages.json` on the Bungee side. 5 | -------------------------------------------------------------------------------- /examples/languages.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "text", 4 | "key": "join.greetings", 5 | "languages": { 6 | "en_GB": "&6Welcome to the server!", 7 | "pt_PT": "&6Bem vindo ao servidor!" 8 | } 9 | }, 10 | { 11 | "type": "text", 12 | "key": "economy.balance", 13 | "languages": { 14 | "en_GB": "&6Your balance: &e$%1", 15 | "pt_PT": "&6O seu saldo: &e$%1" 16 | } 17 | }, 18 | { 19 | "type": "text", 20 | "key": "economy.send.money", 21 | "languages": { 22 | "en_GB": "&6You've just sent &e$%1 &6 to &e%2&6!", 23 | "pt_PT": "&6Enviaste &e$%1 &6 para &e%2&6!" 24 | } 25 | }, 26 | { 27 | "type": "sign", 28 | "key": "sign.help", 29 | "locations": [ 30 | { 31 | "world": "world", 32 | "x": 165, 33 | "y": 66, 34 | "z": -424 35 | }, 36 | { 37 | "world": "world", 38 | "x": -762, 39 | "y": 88, 40 | "z": -525 41 | } 42 | ], 43 | "lines": { 44 | "en_GB": ["&4Welcome!", "&cType &b/help", "&cto get a list", "&cof commands!"], 45 | "pt_PT": ["&4Bem-vindo!", "&cEscreve &b/help", "&cpara a lista", "&cde comandos!"] 46 | } 47 | } 48 | ] 49 | 50 | -------------------------------------------------------------------------------- /examples/languages_bungee.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "text", 4 | "key": "join.greetings", 5 | "languages": { 6 | "en_GB": "&6Welcome to the server!", 7 | "pt_PT": "&6Bem vindo ao servidor!" 8 | }, 9 | "universal": true 10 | }, 11 | { 12 | "type": "text", 13 | "key": "economy.balance", 14 | "languages": { 15 | "en_GB": "&6Your balance: &e$%1", 16 | "pt_PT": "&6O seu saldo: &e$%1" 17 | }, 18 | "universal": false, 19 | "servers": ["survival-1", "survival-2"] 20 | }, 21 | { 22 | "type": "text", 23 | "key": "economy.send.money", 24 | "languages": { 25 | "en_GB": "&6You've just sent &e$%1 &6 to &e%2&6!", 26 | "pt_PT": "&6Enviaste &e$%1 &6 para &e%2&6!" 27 | }, 28 | "universal": false, 29 | "servers": ["survival-3", "survival-4"] 30 | }, 31 | { 32 | "type": "sign", 33 | "key": "sign.help", 34 | "locations": [ 35 | { 36 | "server": "hub-1", 37 | "world": "world", 38 | "x": 165, 39 | "y": 66, 40 | "z": -424 41 | }, 42 | { 43 | "server": "survival-1", 44 | "world": "world", 45 | "x": -762, 46 | "y": 88, 47 | "z": -525 48 | } 49 | ], 50 | "lines": { 51 | "en_GB": ["&4Welcome!", "&cType &b/help", "&cto get a list", "&cof commands!"], 52 | "pt_PT": ["&4Bem-vindo!", "&cEscreve &b/help", "&cpara a lista", "&cde comandos!"] 53 | } 54 | } 55 | ] 56 | 57 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | pluginDescription=A plugin that replaces any message on your server, to the receiver's language, in real time! 2 | pluginWebsite=https://triton.rexcantor64.com/ 3 | pluginAuthor=Rexcantor64 4 | pluginApiVersion=1.13 5 | pluginName=Triton -------------------------------------------------------------------------------- /gradle/publish.gradle: -------------------------------------------------------------------------------- 1 | publishing { 2 | repositories { 3 | maven { 4 | credentials(PasswordCredentials.class) 5 | 6 | name = "diogotcRepository" 7 | def base = 'https://repo.diogotc.com' 8 | def releasesRepoUrl = "$base/releases/" 9 | def snapshotsRepoUrl = "$base/snapshots/" 10 | url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tritonmc/Triton/da285c4ed5aa321da59fc27d3f9ac055b378b1dc/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /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 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'Triton' 2 | include 'api' 3 | include 'spigot-legacy' 4 | include 'v1_13' 5 | include 'core' 6 | -------------------------------------------------------------------------------- /spigot-legacy/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | } 4 | 5 | group 'com.rexcantor64.triton' 6 | 7 | dependencies { 8 | compileOnly 'org.spigotmc:spigot-api:1.8.8-R0.1-SNAPSHOT' 9 | compileOnly 'com.comphenix.protocol:ProtocolLib:5.4.0-SNAPSHOT' 10 | } 11 | -------------------------------------------------------------------------------- /spigot-legacy/src/main/java/com/rexcantor64/triton/wrappers/legacy/HoverComponentWrapper.java: -------------------------------------------------------------------------------- 1 | package com.rexcantor64.triton.wrappers.legacy; 2 | 3 | import com.comphenix.protocol.reflect.FuzzyReflection; 4 | import com.comphenix.protocol.reflect.accessors.Accessors; 5 | import com.comphenix.protocol.reflect.accessors.MethodAccessor; 6 | import com.comphenix.protocol.utility.MinecraftReflection; 7 | import com.comphenix.protocol.wrappers.nbt.NbtCompound; 8 | import com.comphenix.protocol.wrappers.nbt.NbtFactory; 9 | import lombok.val; 10 | import net.md_5.bungee.api.chat.BaseComponent; 11 | import net.md_5.bungee.api.chat.HoverEvent; 12 | import net.md_5.bungee.api.chat.TextComponent; 13 | 14 | import java.util.Optional; 15 | 16 | public class HoverComponentWrapper { 17 | 18 | private static final MethodAccessor NBT_DESERIALIZER_METHOD; 19 | 20 | static { 21 | val mojangsonParserClass = MinecraftReflection.getMinecraftClass("nbt.MojangsonParser", "MojangsonParser"); 22 | FuzzyReflection fuzzy = FuzzyReflection.fromClass(mojangsonParserClass); 23 | val method = fuzzy.getMethodByReturnTypeAndParameters("deserializeNbtCompound", MinecraftReflection.getNBTCompoundClass(), String.class); 24 | NBT_DESERIALIZER_METHOD = Accessors.getMethodAccessor(method); 25 | } 26 | 27 | public static BaseComponent[] getValue(HoverEvent hover) { 28 | return hover.getValue(); 29 | } 30 | 31 | public static HoverEvent setValue(HoverEvent hover, BaseComponent... components) { 32 | return new HoverEvent(hover.getAction(), components); 33 | } 34 | 35 | private static NbtCompound deserializeItemTagNbt(String nbt) { 36 | val nmsCompound = NBT_DESERIALIZER_METHOD.invoke(null, nbt); 37 | return NbtFactory.fromNMSCompound(nmsCompound); 38 | } 39 | 40 | private static String serializeItemTagNbt(NbtCompound nbt) { 41 | return nbt.getHandle().toString(); 42 | } 43 | 44 | /** 45 | * Get an NBT compound with the item data, stored in a hover event. 46 | * If the item does not have a "tag" attribute, an empty Optional is returned, 47 | * since it does not have any metadata to translate. 48 | * 49 | * @param components The text stored inside the hover event. 50 | * @return If valid, the NBT compound in the base component. 51 | */ 52 | public static Optional getNbtItemData(BaseComponent[] components) { 53 | if (components.length != 1) { 54 | return Optional.empty(); 55 | } 56 | if (!(components[0] instanceof TextComponent)) { 57 | return Optional.empty(); 58 | } 59 | val component = (TextComponent) components[0]; 60 | val itemMojangson = component.getText(); 61 | 62 | val itemCompound = deserializeItemTagNbt(itemMojangson); 63 | if (!itemCompound.containsKey("tag")) { 64 | return Optional.empty(); 65 | } 66 | //return Optional.of(itemCompound.getCompoundOrDefault("tag")); 67 | return Optional.of(itemCompound); 68 | } 69 | 70 | /** 71 | * Get a chat component that has a serialized Mojangson NBT item data string inside. 72 | * 73 | * @param compound The NBT item data to serialize. 74 | * @return The chat component with the NBT item data inside. 75 | */ 76 | public static BaseComponent[] fromNbtItemData(NbtCompound compound) { 77 | val serializedNbt = serializeItemTagNbt(compound); 78 | 79 | return new BaseComponent[]{new TextComponent(serializedNbt)}; 80 | } 81 | 82 | } 83 | --------------------------------------------------------------------------------