├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── FUNDING.yml ├── ISSUE_TEMPLATE │ └── from-spigot-forums.md └── workflows │ └── gradle.yml ├── .gitignore ├── .idea └── icon.png ├── LICENSE ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src └── main ├── java └── com │ └── cyr1en │ └── commandprompter │ ├── CommandPrompter.java │ ├── PluginLogger.java │ ├── PluginMessenger.java │ ├── api │ ├── Dispatcher.java │ └── prompt │ │ ├── CompoundableValidator.java │ │ ├── InputValidator.java │ │ └── Prompt.java │ ├── commands │ ├── CommandAPIWrapper.java │ ├── DelegateCommand.java │ └── MainCommand.java │ ├── config │ ├── AliasedSection.java │ ├── CommandPrompterConfig.java │ ├── ConfigurationManager.java │ ├── PromptConfig.java │ ├── annotations │ │ ├── field │ │ │ ├── ConfigNode.java │ │ │ ├── IntegerConstraint.java │ │ │ ├── Match.java │ │ │ ├── NodeComment.java │ │ │ ├── NodeDefault.java │ │ │ └── NodeName.java │ │ └── type │ │ │ ├── ConfigHeader.java │ │ │ ├── ConfigPath.java │ │ │ └── Configuration.java │ └── handlers │ │ ├── ConfigTypeHandler.java │ │ ├── ConfigTypeHandlerFactory.java │ │ └── impl │ │ ├── BooleanHandler.java │ │ ├── DoubleHandler.java │ │ ├── IntegerHandler.java │ │ ├── ListHandler.java │ │ └── StringHandler.java │ ├── hook │ ├── Hook.java │ ├── HookContainer.java │ ├── annotations │ │ └── TargetPlugin.java │ └── hooks │ │ ├── BaseHook.java │ │ ├── CarbonChatHook.java │ │ ├── FilterHook.java │ │ ├── HuskTownsHook.java │ │ ├── LuckPermsHook.java │ │ ├── PapiHook.java │ │ ├── PremiumVanishHook.java │ │ ├── SuperVanishHook.java │ │ ├── TownyHook.java │ │ ├── VanishHook.java │ │ └── VanishNoPacketHook.java │ ├── listener │ ├── CommandListener.java │ ├── CommandSendListener.java │ └── VanillaListener.java │ ├── prompt │ ├── ContextProcessor.java │ ├── PromptContext.java │ ├── PromptManager.java │ ├── PromptParser.java │ ├── PromptQueue.java │ ├── PromptRegistry.java │ ├── prompts │ │ ├── AbstractPrompt.java │ │ ├── AnvilPrompt.java │ │ ├── ChatPrompt.java │ │ ├── PlayerUIPrompt.java │ │ └── SignPrompt.java │ ├── ui │ │ ├── CacheFilter.java │ │ ├── HeadCache.java │ │ └── inventory │ │ │ └── ControlPane.java │ └── validators │ │ ├── CompoundedValidator.java │ │ ├── JSExprValidator.java │ │ ├── NoopValidator.java │ │ ├── OnlinePlayerValidator.java │ │ └── RegexValidator.java │ └── util │ ├── MMUtil.java │ ├── ServerUtil.java │ ├── Util.java │ └── unsafe │ ├── PvtFieldMutationException.java │ └── PvtFieldMutator.java └── resources ├── CommandPrompter.properties └── plugin.yml /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | . 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to CommandPrompter 2 | 3 | We welcome contributions from the community! Please take a moment to review this document in order to make the contribution process smooth and effective for everyone involved. 4 | 5 | ## Bug Reports and Feature Requests 6 | 7 | If you encounter any issues or would like to request a new feature, please open an issue on the [GitHub issue tracker](https://github.com/CyR1en/CommandPrompter/issues) with a descriptive title and detailed information about the problem or feature request. 8 | 9 | ## Pull Requests 10 | 11 | We encourage you to contribute to CommandPrompter by submitting pull requests for any bug fixes, improvements, or new features. Here's a simple guide to help you get started: 12 | 13 | 1. Fork the repository and create your branch from `main`. 14 | 2. If you've added code that should be tested, please add tests. 15 | 3. Ensure your code adheres to the coding conventions used in the project. 16 | 4. Ensure that your commits are properly documented. 17 | 5. Make sure to update the README with any changes if necessary. 18 | 19 | ## Coding Standards 20 | 21 | Please follow the coding standards used in the project, including: 22 | 23 | - Use clear and descriptive variable and function names. 24 | - Write clear and concise comments where necessary. 25 | - Adhere to the existing code formatting style. 26 | 27 | ## License 28 | 29 | By contributing to CommandPrompter, you agree that your contributions will be licensed under the [MIT License](https://opensource.org/licenses/MIT). 30 | 31 | ## Contact 32 | 33 | If you have any further questions or need more information, feel free to contact us via [email](mailto:cyrien3519@gmail.com). 34 | 35 | Thank you for your interest in contributing to CommandPrompter! 36 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: cyr1en 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/from-spigot-forums.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: From Spigot Forums 3 | about: Issues or features that were proposed on Spigot Forums. 4 | title: '' 5 | labels: '' 6 | assignees: CyR1en 7 | 8 | --- 9 | 10 | This issue/feature-request was posted in [Spigot](discussion link), but moved here so we can keep track of it. 11 | 12 | # Content 13 | ``` 14 | [paste content here] 15 | ``` 16 | -------------------------------------------------------------------------------- /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | name: Java CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | release: 9 | types: 10 | - created 11 | 12 | jobs: 13 | build: 14 | if: github.event_name != 'release' 15 | 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - uses: actions/checkout@v2.4.0 20 | - name: Set up JDK 21 | uses: actions/setup-java@v2.5.0 22 | with: 23 | distribution: temurin 24 | java-version: 21 25 | - name: Build with Gradle 26 | run: ./gradlew clean build 27 | - name: Upload artifact 28 | uses: actions/upload-artifact@v4.4.3 29 | with: 30 | name: CommandPrompter 31 | path: build/libs 32 | 33 | publish: 34 | if: github.event_name == 'release' 35 | 36 | runs-on: ubuntu-latest 37 | 38 | steps: 39 | - uses: actions/checkout@v2.4.0 40 | - name: Set up JDK 41 | uses: actions/setup-java@v2.5.0 42 | with: 43 | distribution: temurin 44 | java-version: 21 45 | - name: Publish to Kakuno 46 | env: 47 | KAKUNO_USER: ${{ secrets.KAKUNO_USER }} 48 | KAKUNO_TOKEN: ${{ secrets.KAKUNO_TOKEN }} 49 | run: ./gradlew clean build publish 50 | 51 | - name: Export LATEST_TAG 52 | run: | 53 | echo "LATEST_TAG=$(curl -qsSL \ 54 | -H "Accept: application/vnd.github+json" \ 55 | -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ 56 | -H "X-GitHub-Api-Version: 2022-11-28" \ 57 | "${{ github.api_url }}/repos/${{ github.repository }}/releases/latest" \ 58 | | jq -r .tag_name)" >> $GITHUB_ENV 59 | - name: Upload Release Artifact 60 | env: 61 | GH_TOKEN: ${{ secrets.ACCESS_TOKEN }} 62 | run: 63 | gh release upload ${{ env.LATEST_TAG }} build/libs/*.jar 64 | 65 | 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Gradle template 3 | .gradle 4 | /build/ 5 | 6 | # Ignore Gradle GUI config 7 | gradle-app.setting 8 | 9 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 10 | !gradle-wrapper.jar 11 | 12 | # Cache of project 13 | .gradletasknamecache 14 | 15 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 16 | # gradle/wrapper/gradle-wrapper.properties 17 | ### macOS template 18 | # General 19 | .DS_Store 20 | .AppleDouble 21 | .LSOverride 22 | 23 | # Icon must end with two \r 24 | Icon 25 | 26 | # Thumbnails 27 | ._* 28 | 29 | # Files that might appear in the root of a volume 30 | .DocumentRevisions-V100 31 | .fseventsd 32 | .Spotlight-V100 33 | .TemporaryItems 34 | .Trashes 35 | .VolumeIcon.icns 36 | .com.apple.timemachine.donotpresent 37 | 38 | # Directories potentially created on remote AFP share 39 | .AppleDB 40 | .AppleDesktop 41 | Network Trash Folder 42 | Temporary Items 43 | .apdisk 44 | ### Windows template 45 | # Windows thumbnail cache files 46 | Thumbs.db 47 | ehthumbs.db 48 | ehthumbs_vista.db 49 | 50 | # Dump file 51 | *.stackdump 52 | 53 | # Folder config file 54 | [Dd]esktop.ini 55 | 56 | # Recycle Bin used on file shares 57 | $RECYCLE.BIN/ 58 | 59 | # Windows Installer files 60 | *.cab 61 | *.msi 62 | *.msix 63 | *.msm 64 | *.msp 65 | 66 | # Windows shortcuts 67 | *.lnk 68 | ### Example user template template 69 | ### Example user template 70 | 71 | # IntelliJ project files 72 | .idea 73 | *.iml 74 | out 75 | gen### Java template 76 | 77 | # Vscode 78 | .vscode 79 | 80 | # Compiled class file 81 | *.class 82 | 83 | # Log file 84 | *.log 85 | 86 | # BlueJ files 87 | *.ctxt 88 | 89 | # Mobile Tools for Java (J2ME) 90 | .mtj.tmp/ 91 | 92 | # Package Files # 93 | *.jar 94 | *.war 95 | *.nar 96 | *.ear 97 | *.zip 98 | *.tar.gz 99 | *.rar 100 | 101 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 102 | hs_err_pid* 103 | 104 | bin/ -------------------------------------------------------------------------------- /.idea/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyR1en/CommandPrompter/d4761a82b0d6ec8c09f029ec18ee610f2e34372f/.idea/icon.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Ethan Bacurio 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](https://files.gitbook.com/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FDaCb7lTXGFkT8XKPSg62%2Fuploads%2F7dP2oyXS2s5oc3AoU6KA%2FAsset%204.png?alt=media&token=fb70ddc2-4a5f-4f20-93b9-dff1aa3bf801) 2 | # CommandPrompter 3 | [![](https://ci.cyr1en.com/job/CommandPrompter/badge/icon)](https://ci.cyr1en.com/job/CommandPrompter) 4 | [![](https://img.shields.io/badge/GitBook-documentation-brightgreen?logo=gitbook)](https://cyr1en.gitbook.io/commandprompter/) 5 | [![](https://img.shields.io/discord/936346802402238514.svg?label=Discord&logo=discord)](https://discord.com/invite/qHM8kE4XHj) 6 | [![](https://img.shields.io/badge/Kofi-support%20development-brightgreen?logo=Kofi)](https://ko-fi.com/cyr1en) 7 | 8 | CommandPrompter is a powerful tool that enhances the command prompting experience and menu creation in your Minecraft server. With its customizable configuration options, CommandPrompter allows you to tailor the command prompting process to suit your server's specific requirements. 9 | 10 | Features: 11 | * **Easy to use** - designed to be used within minutes after installation. 12 | * **Multiple prompts** - allows you to choose different [prompts](https://cyr1en.gitbook.io/commandprompter/prompts/) for your menus. 13 | * **Multi language support** - supports utf-8 characters to support a wide range of languages. 14 | * **Input validations** - never worry about miss inputs anymore. 15 | * **Console delegate** - a robust way to prompt a player via console. 16 | * **Post command** - expands your command by incorporation post commands. 17 | 18 | ## Building 19 | 20 | CommandPrompter uses Gradle as a project manager. You can build CommandPrompter for yourself by following the instructions below: 21 | 22 | #### Requirements 23 | * JDK 17 or newer 24 | * Git 25 | 26 | #### Compiling from source 27 | ```sh 28 | git clone https://github.com/CyR1en/CommandPrompter.git 29 | cd CommandPrompter/ 30 | ./gradlew clean build 31 | ``` 32 | 33 | ## Special Thanks To: 34 |
35 | 36 | 37 | 38 | 39 |

This project owes a huge thanks to GitBook's fantastic Open Source License and their amazing platform for creating beautiful and accessible documentation. Their dedication to open source and ease-of-use has been invaluable to this project's success!

40 | 41 | 42 | 43 | 44 | 45 |

Lithium Hosting's invaluable support by providing a server to facilitate development. Their dedication to open source and the developer community has been instrumental in making this project possible.

46 |
47 | 48 | ## License 49 | CommandPrompter is licensed under the permissive [MIT license](https://github.com/CyR1en/CommandPrompter/blob/master/LICENSE). Please see LICENSE.txt for more info. 50 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Ethan Bacurio 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | plugins { 26 | id "io.github.goooler.shadow" version "8.1.7" 27 | id 'java' 28 | id 'maven-publish' 29 | } 30 | 31 | compileJava.options.encoding = "UTF-8" 32 | compileTestJava.options.encoding = "UTF-8" 33 | 34 | java { 35 | toolchain { 36 | languageVersion = JavaLanguageVersion.of(17) 37 | } 38 | } 39 | 40 | PluginManifest pluginManifest = [ 41 | name : 'CommandPrompter', 42 | version : new Version(major: 2, minor: 13, patch: 0, fix: 0, classifier: 'SNAPSHOT'), 43 | author : 'CyR1en', 44 | description: 'Making Commands More Interactive!', 45 | entry : 'com.cyr1en.commandprompter.CommandPrompter' 46 | ] 47 | 48 | group = 'com.cyr1en' 49 | version = pluginManifest.version.toString() 50 | 51 | repositories { 52 | mavenCentral() 53 | maven { url 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' } 54 | maven { url 'https://libraries.minecraft.net/' } 55 | maven { url 'https://repo.cyr1en.com/snapshots' } 56 | maven { url 'https://repo.dmulloy2.net/repository/public/' } 57 | maven { url 'https://repo.codemc.io/repository/maven-snapshots/' } 58 | maven { url 'https://repo.codemc.io/repository/maven-public/' } 59 | maven { url 'https://repo.extendedclip.com/content/repositories/placeholderapi/' } 60 | maven { url 'https://jitpack.io' } 61 | maven { url 'https://repo.glaremasters.me/repository/towny/' } 62 | maven { url 'https://repo.william278.net/releases' } 63 | flatDir { dirs 'libs' } 64 | } 65 | 66 | dependencies { 67 | implementation 'com.cyr1en:kiso-utils:1.8-SNAPSHOT' 68 | implementation 'com.cyr1en:kiso-mc:1.8-SNAPSHOT' 69 | implementation 'net.wesjd:anvilgui:1.10.5-SNAPSHOT' 70 | implementation 'org.bstats:bstats-bukkit:3.0.2' 71 | implementation group: 'org.fusesource.jansi', name: 'jansi', version: '2.4.0' 72 | implementation 'com.github.stefvanschie.inventoryframework:IF:0.11.0' 73 | implementation "net.kyori:adventure-text-minimessage:4.21.0" 74 | implementation "dev.jorel:commandapi-bukkit-shade:10.0.1" 75 | implementation 'de.rapha149.signgui:signgui:2.5.1' 76 | implementation 'org.openjdk.nashorn:nashorn-core:15.4' 77 | 78 | compileOnly "net.kyori:adventure-text-serializer-legacy:4.21.0" 79 | compileOnly "net.kyori:adventure-text-serializer-plain:4.21.0" 80 | compileOnly 'me.clip:placeholderapi:2.11.6' 81 | compileOnly 'com.palmergames.bukkit.towny:towny:0.100.3.0' 82 | compileOnly "org.spigotmc:spigot-api:1.21.3-R0.1-SNAPSHOT" 83 | compileOnly 'com.github.LeonMangler:SuperVanish:6.2.18-3' 84 | compileOnly 'de.hexaoxi:carbonchat-api:3.0.0-beta.26' 85 | compileOnly 'com.github.mbax:VanishNoPacket:3.22' 86 | compileOnly 'org.jetbrains:annotations:23.0.0' 87 | compileOnly 'net.luckperms:api:5.4' 88 | compileOnly 'net.william278.husktowns:husktowns-common:3.0.5' 89 | compileOnly 'com.github.LeonMangler:PremiumVanishAPI:2.9.0-4' 90 | // Local 91 | implementation fileTree(dir: 'libs', include: '*.jar') 92 | compileOnly fileTree(dir: 'libs/compileonly', include: '*.jar') 93 | } 94 | 95 | configurations.implementation { 96 | exclude group: 'joda-time', module: 'joda-time' 97 | exclude group: 'org.atteo.classindex', module: 'classindex' 98 | } 99 | 100 | tasks.named('jar').configure { enabled = true } 101 | 102 | shadowJar { 103 | dependencies { 104 | exclude(dependency('com.mojang:brigadier')) 105 | } 106 | 107 | archiveBaseName.set("$project.name") 108 | archiveClassifier.set('') 109 | archiveVersion.set(pluginManifest.version.getFullVersion()) 110 | 111 | //relocate 'net.kyori.adventure', 'com.cyr1en.adventure' 112 | relocate 'com.github.stefvanschie.inventoryframework', 'com.cyr1en.inventoryframework' 113 | relocate 'net.wesjd.anvilgui', 'com.cyr1en.anvilgui' 114 | relocate 'de.rapha149.signgui', 'com.cyr1en.signgui' 115 | relocate 'dev.jorel.commandapi', 'com.cyr1en.commandapi' 116 | relocate 'org.fusesource.jansi', 'com.cyr1en.jansi' 117 | relocate 'org.bstats', 'com.cyr1en.bstats' 118 | } 119 | 120 | processResources { 121 | duplicatesStrategy = DuplicatesStrategy.INCLUDE 122 | from(sourceSets.main.resources.srcDirs) { 123 | expand( 124 | 'projectName': pluginManifest.name, 125 | 'projectVersion': version, 126 | 'projectAuthor': pluginManifest.author, 127 | 'projectDescription': pluginManifest.description, 128 | 'projectEntry': pluginManifest.entry 129 | ) 130 | include 'plugin.yml' 131 | } 132 | } 133 | 134 | artifacts { 135 | archives shadowJar 136 | } 137 | 138 | publishing { 139 | repositories { 140 | maven { 141 | name = "Kakuno" 142 | url = uri("https://repo.cyr1en.com/snapshots") 143 | credentials { 144 | username = System.getenv("KAKUNO_USER") 145 | password = System.getenv("KAKUNO_TOKEN") 146 | } 147 | } 148 | } 149 | publications { 150 | gpr(MavenPublication) { 151 | from(components.java) 152 | } 153 | } 154 | } 155 | 156 | class PluginManifest { 157 | String name, author, description, entry 158 | Version version 159 | } 160 | 161 | class Version { 162 | 163 | String major, minor, patch, fix, classifier, full 164 | 165 | String getFullVersion() { 166 | full = "$major.$minor.$patch${(fix as Integer) > 0 ? ".$fix" : ''}" 167 | return full 168 | } 169 | 170 | String toString() { 171 | getFullVersion() 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyR1en/CommandPrompter/d4761a82b0d6ec8c09f029ec18ee610f2e34372f/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.10-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s 90 | ' "$PWD" ) || exit 91 | 92 | # Use the maximum available, or set MAX_FD != -1 to use that value. 93 | MAX_FD=maximum 94 | 95 | warn () { 96 | echo "$*" 97 | } >&2 98 | 99 | die () { 100 | echo 101 | echo "$*" 102 | echo 103 | exit 1 104 | } >&2 105 | 106 | # OS specific support (must be 'true' or 'false'). 107 | cygwin=false 108 | msys=false 109 | darwin=false 110 | nonstop=false 111 | case "$( uname )" in #( 112 | CYGWIN* ) cygwin=true ;; #( 113 | Darwin* ) darwin=true ;; #( 114 | MSYS* | MINGW* ) msys=true ;; #( 115 | NONSTOP* ) nonstop=true ;; 116 | esac 117 | 118 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 119 | 120 | 121 | # Determine the Java command to use to start the JVM. 122 | if [ -n "$JAVA_HOME" ] ; then 123 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 124 | # IBM's JDK on AIX uses strange locations for the executables 125 | JAVACMD=$JAVA_HOME/jre/sh/java 126 | else 127 | JAVACMD=$JAVA_HOME/bin/java 128 | fi 129 | if [ ! -x "$JAVACMD" ] ; then 130 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 131 | 132 | Please set the JAVA_HOME variable in your environment to match the 133 | location of your Java installation." 134 | fi 135 | else 136 | JAVACMD=java 137 | if ! command -v java >/dev/null 2>&1 138 | then 139 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 140 | 141 | Please set the JAVA_HOME variable in your environment to match the 142 | location of your Java installation." 143 | fi 144 | fi 145 | 146 | # Increase the maximum file descriptors if we can. 147 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 148 | case $MAX_FD in #( 149 | max*) 150 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 151 | # shellcheck disable=SC2039,SC3045 152 | MAX_FD=$( ulimit -H -n ) || 153 | warn "Could not query maximum file descriptor limit" 154 | esac 155 | case $MAX_FD in #( 156 | '' | soft) :;; #( 157 | *) 158 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 159 | # shellcheck disable=SC2039,SC3045 160 | ulimit -n "$MAX_FD" || 161 | warn "Could not set maximum file descriptor limit to $MAX_FD" 162 | esac 163 | fi 164 | 165 | # Collect all arguments for the java command, stacking in reverse order: 166 | # * args from the command line 167 | # * the main class name 168 | # * -classpath 169 | # * -D...appname settings 170 | # * --module-path (only if needed) 171 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 172 | 173 | # For Cygwin or MSYS, switch paths to Windows format before running java 174 | if "$cygwin" || "$msys" ; then 175 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 176 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 177 | 178 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 179 | 180 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 181 | for arg do 182 | if 183 | case $arg in #( 184 | -*) false ;; # don't mess with options #( 185 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 186 | [ -e "$t" ] ;; #( 187 | *) false ;; 188 | esac 189 | then 190 | arg=$( cygpath --path --ignore --mixed "$arg" ) 191 | fi 192 | # Roll the args list around exactly as many times as the number of 193 | # args, so each arg winds up back in the position where it started, but 194 | # possibly modified. 195 | # 196 | # NB: a `for` loop captures its iteration list before it begins, so 197 | # changing the positional parameters here affects neither the number of 198 | # iterations, nor the values presented in `arg`. 199 | shift # remove old arg 200 | set -- "$@" "$arg" # push replacement arg 201 | done 202 | fi 203 | 204 | 205 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 206 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 207 | 208 | # Collect all arguments for the java command: 209 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 210 | # and any embedded shellness will be escaped. 211 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 212 | # treated as '${Hostname}' itself on the command line. 213 | 214 | set -- \ 215 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 216 | -classpath "$CLASSPATH" \ 217 | org.gradle.wrapper.GradleWrapperMain \ 218 | "$@" 219 | 220 | # Stop when "xargs" is not available. 221 | if ! command -v xargs >/dev/null 2>&1 222 | then 223 | die "xargs is not available" 224 | fi 225 | 226 | # Use "xargs" to parse quoted args. 227 | # 228 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 229 | # 230 | # In Bash we could simply go: 231 | # 232 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 233 | # set -- "${ARGS[@]}" "$@" 234 | # 235 | # but POSIX shell has neither arrays nor command substitution, so instead we 236 | # post-process each arg (as a line of input to sed) to backslash-escape any 237 | # character that might be a shell metacharacter, then use eval to reverse 238 | # that process (while maintaining the separation between arguments), and wrap 239 | # the whole thing up as a single "set" statement. 240 | # 241 | # This will of course break if any of these variables contains a newline or 242 | # an unmatched quote. 243 | # 244 | 245 | eval "set -- $( 246 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 247 | xargs -n1 | 248 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 249 | tr '\n' ' ' 250 | )" '"$@"' 251 | 252 | exec "$JAVACMD" "$@" 253 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'CommandPrompter' 2 | 3 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/CommandPrompter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Ethan Bacurio 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.cyr1en.commandprompter; 26 | 27 | import com.cyr1en.commandprompter.commands.CommandAPIWrapper; 28 | import com.cyr1en.commandprompter.config.CommandPrompterConfig; 29 | import com.cyr1en.commandprompter.config.ConfigurationManager; 30 | import com.cyr1en.commandprompter.config.PromptConfig; 31 | import com.cyr1en.commandprompter.hook.HookContainer; 32 | import com.cyr1en.commandprompter.listener.CommandListener; 33 | import com.cyr1en.commandprompter.listener.CommandSendListener; 34 | import com.cyr1en.commandprompter.listener.VanillaListener; 35 | import com.cyr1en.commandprompter.prompt.PromptManager; 36 | import com.cyr1en.commandprompter.prompt.prompts.ChatPrompt; 37 | import com.cyr1en.commandprompter.prompt.ui.HeadCache; 38 | import com.cyr1en.commandprompter.util.ServerUtil; 39 | import com.cyr1en.kiso.mc.I18N; 40 | import com.cyr1en.kiso.mc.UpdateChecker; 41 | import com.cyr1en.kiso.utils.SRegex; 42 | import org.bstats.bukkit.Metrics; 43 | import org.bukkit.Bukkit; 44 | import org.bukkit.entity.Player; 45 | import org.bukkit.event.HandlerList; 46 | import org.bukkit.plugin.java.JavaPlugin; 47 | 48 | import java.util.Objects; 49 | 50 | public class CommandPrompter extends JavaPlugin { 51 | 52 | private static CommandPrompter instance; 53 | 54 | private ConfigurationManager configManager; 55 | private CommandPrompterConfig config; 56 | private PromptConfig promptConfig; 57 | private HookContainer hookContainer; 58 | 59 | private PluginLogger logger; 60 | private CommandListener commandListener; 61 | private I18N i18n; 62 | private UpdateChecker updateChecker; 63 | private PromptManager promptManager; 64 | private PluginMessenger messenger; 65 | private HeadCache headCache; 66 | private CommandAPIWrapper commandAPIWrapper; 67 | 68 | @Override 69 | public void onEnable() { 70 | new Metrics(this, 5359); 71 | setupConfig(); 72 | logger = new PluginLogger(this, "CommandPrompter"); 73 | var serverType = ServerUtil.resolve(); 74 | logger.debug("Server Name: " + serverType.name()); 75 | logger.debug("Server Version: " + ServerUtil.version()); 76 | 77 | i18n = new I18N(this, "CommandPrompter"); 78 | 79 | commandAPIWrapper = new CommandAPIWrapper(this); 80 | commandAPIWrapper.load(); 81 | commandAPIWrapper.onEnable(); 82 | 83 | messenger = new PluginMessenger(config.promptPrefix()); 84 | 85 | setupUpdater(); 86 | initPromptSystem(); 87 | setupCommands(); 88 | 89 | instance = this; 90 | Bukkit.getPluginManager().registerEvents(new CommandSendListener(this), this); 91 | 92 | Bukkit.getScheduler().runTaskLater(this, () -> { 93 | hookContainer = new HookContainer(this); 94 | hookContainer.initHooks(); 95 | headCache.registerFilters(); 96 | promptManager.registerPrompts(); 97 | ChatPrompt.resolveListener(this); 98 | }, 1L); 99 | } 100 | 101 | @Override 102 | public void onDisable() { 103 | commandAPIWrapper.onDisable(); 104 | if (promptManager != null) 105 | promptManager.clearPromptRegistry(); 106 | 107 | if (Objects.nonNull(updateChecker) && !updateChecker.isDisabled()) 108 | HandlerList.unregisterAll(updateChecker); 109 | } 110 | 111 | private void initPromptSystem() { 112 | Bukkit.getPluginManager().registerEvents(headCache = new HeadCache(this), this); 113 | promptManager = new PromptManager(this); 114 | initCommandListener(); 115 | } 116 | 117 | /** 118 | * Function to initialize the command listener that this plugin will use 119 | *

120 | * If unsafe is enabled in the config, this plugin will use the modified 121 | * command map. Otherwise, it will just use the vanilla listener. 122 | */ 123 | private void initCommandListener() { 124 | commandListener = new VanillaListener(promptManager); 125 | Bukkit.getPluginManager().registerEvents(commandListener, this); 126 | } 127 | 128 | private void setupConfig() { 129 | configManager = new ConfigurationManager(this); 130 | config = configManager.getConfig(CommandPrompterConfig.class); 131 | promptConfig = configManager.getConfig(PromptConfig.class); 132 | } 133 | 134 | private void setupCommands() { 135 | commandAPIWrapper.registerCommands(); 136 | } 137 | 138 | private void setupUpdater() { 139 | updateChecker = new UpdateChecker(this, 47772); 140 | if (updateChecker.isDisabled()) 141 | return; 142 | Bukkit.getServer().getScheduler().runTaskAsynchronously(this, () -> { 143 | if (updateChecker.newVersionAvailable()) 144 | logger.info(SRegex.ANSI_GREEN + "A new update is available! (" + 145 | updateChecker.getCurrVersion().asString() + ")" + SRegex.ANSI_RESET); 146 | else 147 | logger.info("No update was found."); 148 | }); 149 | Bukkit.getPluginManager().registerEvents(updateChecker, this); 150 | } 151 | 152 | public I18N getI18N() { 153 | return i18n; 154 | } 155 | 156 | public HookContainer getHookContainer() { 157 | return this.hookContainer; 158 | } 159 | 160 | public PluginMessenger getMessenger() { 161 | return messenger; 162 | } 163 | 164 | public PromptManager getPromptManager() { 165 | return promptManager; 166 | } 167 | 168 | public PluginLogger getPluginLogger() { 169 | return logger; 170 | } 171 | 172 | public HeadCache getHeadCache() { 173 | return headCache; 174 | } 175 | 176 | public void reload(boolean clean) { 177 | config = configManager.reload(CommandPrompterConfig.class); 178 | promptConfig = configManager.reload(PromptConfig.class); 179 | messenger.setPrefix(config.promptPrefix()); 180 | logger = new PluginLogger(this, "CommandPrompter"); 181 | i18n = new I18N(this, "CommandPrompter"); 182 | headCache.reBuildCache(); 183 | promptManager.getParser().initRegex(); 184 | ChatPrompt.DefaultListener.setPriority(this); 185 | setupUpdater(); 186 | if (clean) 187 | promptManager.clearPromptRegistry(); 188 | 189 | // Update commands just in case tab completer is changed 190 | Bukkit.getOnlinePlayers().forEach(Player::updateCommands); 191 | } 192 | 193 | public static CommandPrompter getInstance() { 194 | return instance; 195 | } 196 | 197 | public CommandPrompterConfig getConfiguration() { 198 | return config; 199 | } 200 | 201 | public PromptConfig getPromptConfig() { 202 | return promptConfig; 203 | } 204 | 205 | public UpdateChecker getUpdateChecker() { 206 | return updateChecker; 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/PluginLogger.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.fusesource.jansi.Ansi; 5 | 6 | import java.awt.*; 7 | import java.util.Objects; 8 | import java.util.UnknownFormatConversionException; 9 | import java.util.logging.Level; 10 | 11 | public class PluginLogger { 12 | 13 | private String prefix; 14 | private String debugPrefix; 15 | 16 | private final ColorGradient normalGrad; 17 | private final ColorGradient debugGrad; 18 | 19 | private final boolean debugMode; 20 | private final boolean isFancy; 21 | 22 | public PluginLogger(CommandPrompter plugin, String prefix) { 23 | this.isFancy = plugin.getConfiguration().fancyLogger(); 24 | this.debugMode = plugin.getConfiguration().debugMode(); 25 | 26 | // Spread love not war <3 27 | normalGrad = new ColorGradient(new Color(1, 88, 181), new Color(246, 206, 0)); 28 | 29 | debugGrad = new ColorGradient(new Color(255, 96, 109), new Color(255, 195, 113)); 30 | 31 | setPrefix(prefix); 32 | } 33 | 34 | public void setPrefix(String prefix) { 35 | var sep = isFancy ? new Ansi().fgRgb(153, 214, 90).a(">>").reset().toString() : ">>"; 36 | var normal = isFancy ? makeGradient(prefix, normalGrad) : prefix; 37 | var debug = isFancy ? makeGradient(prefix + "-" + "Debug", debugGrad) : prefix + "-" + "Debug"; 38 | this.prefix = String.format("%s %s ", normal, sep); 39 | this.debugPrefix = String.format("%s %s ", debug, sep); 40 | } 41 | 42 | private String makeGradient(String prefix, ColorGradient grad) { 43 | var colorGrad = grad.getGradient(prefix.length()); 44 | var a = new Ansi(); 45 | for (int i = 0; i < colorGrad.length; i++) 46 | a.fgRgb(colorGrad[i].getRGB()).a(prefix.charAt(i)); 47 | 48 | return a.reset().toString(); 49 | } 50 | 51 | public void log(String prefix, Level level, String msg, Object... args) { 52 | String pre = prefix == null ? getPrefix() : prefix; 53 | try { 54 | if (msg.matches("%s")) 55 | msg = String.format(msg, args); 56 | } catch (UnknownFormatConversionException ignore) { 57 | } 58 | Bukkit.getLogger().log(level, pre + msg); 59 | } 60 | 61 | public void log(Level level, String msg, Object... args) { 62 | log(null, level, msg, args); 63 | } 64 | 65 | public void info(String msg, Object... args) { 66 | log(Level.INFO, msg, args); 67 | } 68 | 69 | public void warn(String msg, Object... args) { 70 | var str = new Ansi().fgRgb(255, 195, 113).a(msg).reset().toString(); 71 | log(Level.WARNING, str, args); 72 | } 73 | 74 | public void err(String msg, Object... args) { 75 | var str = new Ansi().fgRgb(255, 50, 21).a(msg).reset().toString(); 76 | log(Level.SEVERE, str, args); 77 | } 78 | 79 | private Class lastDebugClass; 80 | 81 | public void debug(String msg, Object... args) { 82 | var caller = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE).getCallerClass(); 83 | var callerAvailable = Objects.nonNull(caller) && !caller.getSimpleName().isBlank(); 84 | if (callerAvailable) 85 | lastDebugClass = caller; 86 | 87 | if (debugMode) { 88 | msg = callerAvailable ? String.format("[%s] - %s", caller.getSimpleName(), msg) 89 | : Objects.isNull(lastDebugClass) ? msg 90 | : String.format("[%s?] - %s", lastDebugClass.getSimpleName(), msg); 91 | var str = new Ansi().fgRgb(255, 195, 113).a(msg).reset().toString(); 92 | log(debugPrefix, Level.INFO, str, args); 93 | } 94 | } 95 | 96 | private String getPrefix() { 97 | return prefix; 98 | } 99 | 100 | public record ColorGradient(Color c1, Color c2) { 101 | 102 | public Color[] getGradient(int segmentCount) { 103 | var colors = new Color[segmentCount]; 104 | var seg = 1.0F / segmentCount; 105 | var currSeg = 0.0F; 106 | for (int i = 0; i < segmentCount; i++) { 107 | colors[i] = getPercentGradient(currSeg); 108 | currSeg += seg; 109 | } 110 | return colors; 111 | } 112 | 113 | public Color getPercentGradient(float percent) { 114 | if (percent < 0 || percent > 1) 115 | return Color.WHITE; 116 | return new Color( 117 | linInterpolate(c1.getRed(), c2.getRed(), percent), 118 | linInterpolate(c1.getGreen(), c2.getGreen(), percent), 119 | linInterpolate(c1.getBlue(), c2.getBlue(), percent)); 120 | } 121 | 122 | private int linInterpolate(int f1, int f2, float percent) { 123 | var res = f1 + percent * (f2 - f1); 124 | return Math.round(res); 125 | } 126 | 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/PluginMessenger.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter; 2 | 3 | import com.cyr1en.commandprompter.util.Util; 4 | import org.bukkit.command.CommandSender; 5 | 6 | public class PluginMessenger { 7 | 8 | private String prefix; 9 | 10 | public PluginMessenger(String prefix) { 11 | this.prefix = prefix; 12 | } 13 | 14 | public void setPrefix(String prefix) { 15 | this.prefix = prefix; 16 | } 17 | 18 | public void sendMessage(CommandSender sender, String message) { 19 | if (message.isBlank()) return; 20 | var whole = prefix + message; 21 | sender.sendMessage(Util.color(whole)); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/api/Dispatcher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Ethan Bacurio 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.cyr1en.commandprompter.api; 26 | 27 | import com.cyr1en.commandprompter.CommandPrompter; 28 | import org.bukkit.Bukkit; 29 | import org.bukkit.command.CommandSender; 30 | import org.bukkit.entity.Player; 31 | import org.bukkit.plugin.Plugin; 32 | import org.bukkit.scheduler.BukkitRunnable; 33 | import org.jetbrains.annotations.NotNull; 34 | 35 | /** 36 | * Player command dispatcher for Support with CommandPrompter. 37 | * 38 | *

39 | * Because CommandPrompter cannot catch commands that were dispatched from 40 | * {@link org.bukkit.Bukkit#dispatchCommand(CommandSender, String)}, plugins 41 | * need a special way to execute player commands. 42 | *

43 | */ 44 | public class Dispatcher { 45 | 46 | /** 47 | * Dispatches command by forcing a player to chat the command. 48 | * This will allow plugins to support CommandPrompter. 49 | * 50 | * @param plugin Instance of plugin. 51 | * @param sender command sender (in menu's, then the item clicker) 52 | * @param command command that would be dispatched. 53 | */ 54 | public static void dispatchCommand(Plugin plugin, Player sender, String command) { 55 | final String checked = command.codePointAt(0) == 0x2F ? command : "/" + command; 56 | new BukkitRunnable() { 57 | public void run() { 58 | sender.chat(checked); 59 | } 60 | }.runTask(plugin); 61 | } 62 | 63 | /** 64 | * Dispatch the command as Console. 65 | * 66 | * @param command command that would be dispatched. 67 | */ 68 | public static void dispatchConsole(final String command) { 69 | final String checked = command.codePointAt(0) == 0x2F ? command.substring(1) : command; 70 | new BukkitRunnable() { 71 | public void run() { 72 | Bukkit.dispatchCommand(Bukkit.getConsoleSender(), checked); 73 | } 74 | }.runTask(CommandPrompter.getInstance()); 75 | } 76 | 77 | /** 78 | * Dispatch a command for a player with a PermissionAttachment that contains 79 | * all the whitelisted commands. 80 | * 81 | * @param plugin Instance of plugin. 82 | * @param sender command sender (in menu's, then the item clicker) 83 | * @param command command that would be dispatched. 84 | * @param ticks Number of ticks before the attachment expires 85 | * @param perms Permissions to set to the PermissionAttachment 86 | */ 87 | public static void dispatchWithAttachment(Plugin plugin, Player sender, String command, int ticks, 88 | @NotNull String[] perms) { 89 | var commandPrompter = (CommandPrompter) plugin; 90 | var logger = commandPrompter.getPluginLogger(); 91 | 92 | logger.debug("Dispatching command with permission attachment"); 93 | 94 | var attachment = sender.addAttachment(plugin, ticks); 95 | if (attachment == null) { 96 | logger.err("Unable to create PermissionAttachment for " + sender.getName()); 97 | return; 98 | } 99 | 100 | for (String perm : perms) { 101 | logger.debug("Attached Perm: " + perm); 102 | attachment.setPermission(perm, true); 103 | } 104 | attachment.getPermissible().recalculatePermissions(); 105 | final String checked = command.codePointAt(0) == 0x2F ? command.substring(1) : command; 106 | Bukkit.dispatchCommand(sender, checked); 107 | //dispatchCommand(plugin, sender, command); 108 | sender.removeAttachment(attachment); 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/api/prompt/CompoundableValidator.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.api.prompt; 2 | 3 | 4 | /** 5 | * A class that represents a compoundable validator. 6 | *

7 | * This class is used to represent a validator that can be combined with other validators. 8 | */ 9 | public interface CompoundableValidator { 10 | 11 | /** 12 | * Get the type of the compoundable validator. 13 | * 14 | * @return the type of the compoundable validator. 15 | */ 16 | public Type getType(); 17 | 18 | /** 19 | * Set the type of the compoundable validator. 20 | * 21 | * @param type the type of the compoundable validator. 22 | */ 23 | public void setType(Type type); 24 | 25 | public enum Type { 26 | AND, 27 | OR, 28 | DEFAULT 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/api/prompt/InputValidator.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.api.prompt; 2 | 3 | 4 | import org.bukkit.entity.Player; 5 | 6 | /** 7 | * A functional interface for validating input. 8 | * 9 | *

Implement this interface to create a custom validator for your prompt.

10 | */ 11 | public interface InputValidator { 12 | 13 | /** 14 | * Validate the input. 15 | * 16 | *

Return true if the input is valid, false otherwise.

17 | * 18 | * @param input the input to validate 19 | * @return true if the input is valid, false otherwise 20 | */ 21 | boolean validate(String input); 22 | 23 | /** 24 | * Get the alias for this validator. 25 | * 26 | *

The alias is used to identify the validator.

27 | * 28 | * @return the alias for this validator 29 | */ 30 | String alias(); 31 | 32 | /** 33 | * Get the message to send when validation fails. 34 | * 35 | * @return the message to send when validation fails 36 | */ 37 | String messageOnFail(); 38 | 39 | /** 40 | * Get the player that entered the input. 41 | */ 42 | Player inputPlayer(); 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/api/prompt/Prompt.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Ethan Bacurio 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.cyr1en.commandprompter.api.prompt; 26 | 27 | import com.cyr1en.commandprompter.CommandPrompter; 28 | import com.cyr1en.commandprompter.prompt.PromptContext; 29 | import com.cyr1en.commandprompter.prompt.PromptManager; 30 | import com.cyr1en.commandprompter.prompt.PromptParser; 31 | 32 | import java.util.List; 33 | 34 | public interface Prompt { 35 | 36 | /** 37 | * Method that sends the prompt. 38 | */ 39 | void sendPrompt(); 40 | 41 | /** 42 | * Accessor for the {@link PromptContext}. 43 | * 44 | *

{@link PromptContext} contains all the information that you need for 45 | * the {@link Prompt}.

46 | * 47 | * @return context for the prompt. 48 | * @see PromptContext 49 | */ 50 | PromptContext getContext(); 51 | 52 | /** 53 | * Get instance of the plugin. 54 | * 55 | *

The instance is a sub-class {@link org.bukkit.plugin.java.JavaPlugin}.

56 | * 57 | * @return Instance of a plugin. 58 | */ 59 | CommandPrompter getPlugin(); 60 | 61 | /** 62 | * Get the actual prompt to send 63 | */ 64 | String getPrompt(); 65 | 66 | /** 67 | * Get prompt manager 68 | */ 69 | PromptManager getPromptManager(); 70 | 71 | List getArgs(); 72 | 73 | /** 74 | * Set the input validator 75 | * 76 | * @param inputValidator input validator to check prompt input 77 | */ 78 | void setInputValidator(InputValidator inputValidator); 79 | 80 | /** 81 | * Get the input validator 82 | * 83 | * @return input validator to check prompt input 84 | */ 85 | InputValidator getInputValidator(); 86 | 87 | /** 88 | * Returns a boolean value if inputs should be sanitized. 89 | * 90 | * @return true if inputs should be sanitized 91 | */ 92 | boolean sanitizeInput(); 93 | 94 | /** 95 | * Set whether inputs should be sanitized. 96 | * 97 | * @param sanitize true if inputs should be sanitized 98 | */ 99 | void setInputSanitization(boolean sanitize); 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/commands/CommandAPIWrapper.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.commands; 2 | 3 | import com.cyr1en.commandprompter.CommandPrompter; 4 | import com.cyr1en.commandprompter.util.Util; 5 | import dev.jorel.commandapi.CommandAPI; 6 | import dev.jorel.commandapi.CommandAPIBukkitConfig; 7 | 8 | /** 9 | * Wrapper for CommandAPI. 10 | * 11 | *

12 | * Since CommandPrompter loads CommandAPI dynamically, we need a wrapper to 13 | * prevent 14 | * CommandAPI imports on the main class. 15 | */ 16 | public class CommandAPIWrapper { 17 | 18 | private final CommandPrompter plugin; 19 | 20 | public CommandAPIWrapper(CommandPrompter plugin) { 21 | this.plugin = plugin; 22 | } 23 | 24 | public void load() { 25 | var config = new CommandAPIBukkitConfig(plugin); 26 | config.skipReloadDatapacks(true); 27 | config.useLatestNMSVersion(false); 28 | config = plugin.getConfiguration().debugMode() ? config.silentLogs(false).verboseOutput(true) 29 | : config.silentLogs(true).verboseOutput(false); 30 | 31 | var msg = plugin.getI18N().getProperty("DelegateConsoleOnly"); 32 | config.missingExecutorImplementationMessage(Util.color(msg)); 33 | CommandAPI.onLoad(config); 34 | } 35 | 36 | public void onEnable() { 37 | CommandAPI.onEnable(); 38 | } 39 | 40 | public void onDisable() { 41 | CommandAPI.onDisable(); 42 | } 43 | 44 | public void registerCommands() { 45 | new MainCommand(plugin).register(); 46 | new DelegateCommand.ConsoleDelegate(plugin).register(); 47 | new DelegateCommand.PlayerDelegate(plugin).register(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/commands/DelegateCommand.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.commands; 2 | 3 | import com.cyr1en.commandprompter.CommandPrompter; 4 | import com.cyr1en.commandprompter.PluginLogger; 5 | import com.cyr1en.commandprompter.PluginMessenger; 6 | import com.cyr1en.commandprompter.prompt.ContextProcessor; 7 | import com.cyr1en.commandprompter.prompt.PromptContext; 8 | import com.cyr1en.kiso.mc.I18N; 9 | import dev.jorel.commandapi.CommandAPICommand; 10 | import dev.jorel.commandapi.arguments.ArgumentSuggestions; 11 | import dev.jorel.commandapi.arguments.GreedyStringArgument; 12 | import dev.jorel.commandapi.arguments.PlayerArgument; 13 | import dev.jorel.commandapi.arguments.StringArgument; 14 | import dev.jorel.commandapi.executors.CommandArguments; 15 | import org.bukkit.command.CommandSender; 16 | import org.bukkit.command.ConsoleCommandSender; 17 | import org.bukkit.entity.Player; 18 | import org.bukkit.plugin.java.JavaPlugin; 19 | 20 | public abstract class DelegateCommand extends ContextProcessor { 21 | 22 | protected final CommandPrompter plugin; 23 | private final PluginLogger logger; 24 | private final PluginMessenger messenger; 25 | private final I18N i18n; 26 | 27 | 28 | private DelegateCommand(JavaPlugin plugin) { 29 | super((CommandPrompter) plugin, ((CommandPrompter) plugin).getPromptManager()); 30 | this.plugin = (CommandPrompter) plugin; 31 | this.logger = this.plugin.getPluginLogger(); 32 | this.messenger = this.plugin.getMessenger(); 33 | this.i18n = this.plugin.getI18N(); 34 | } 35 | 36 | public abstract void register(); 37 | 38 | public abstract void doCommand(Player targetPlayer, String command, CommandArguments args); 39 | 40 | protected void exec(CommandSender sender, CommandArguments args) { 41 | logger.debug("Command Arguments: " + args.fullInput()); 42 | 43 | if (!(sender instanceof ConsoleCommandSender)) { 44 | messenger.sendMessage(sender, i18n.getProperty("DelegateConsoleOnly")); 45 | return; 46 | } 47 | 48 | var arg0 = args.getRaw("target"); 49 | if (arg0 == null || arg0.isEmpty()) return; 50 | 51 | var targetPlayer = plugin.getServer().getPlayer(arg0); 52 | 53 | if (targetPlayer == null) { 54 | messenger.sendMessage(sender, i18n.getFormattedProperty("DelegateInvalidPlayer", arg0)); 55 | return; 56 | } 57 | 58 | var delegatedCommand = args.getRaw("command"); 59 | 60 | if (delegatedCommand == null || delegatedCommand.isEmpty()) { 61 | messenger.sendMessage(sender, i18n.getProperty("DelegateInvalidCommand")); 62 | return; 63 | } 64 | 65 | if (delegatedCommand.contains("%target_player%")) 66 | delegatedCommand = delegatedCommand.replace("%target_player%", targetPlayer.getName()); 67 | 68 | doCommand(targetPlayer, delegatedCommand, args); 69 | } 70 | 71 | public static class ConsoleDelegate extends DelegateCommand { 72 | 73 | ConsoleDelegate(JavaPlugin plugin) { 74 | super(plugin); 75 | } 76 | 77 | public void register() { 78 | new CommandAPICommand("consoledelegate") 79 | .withPermission("commandprompter.consoledelegate") 80 | .withArguments(new PlayerArgument("target")) 81 | .withArguments(new GreedyStringArgument("command")) 82 | .executesConsole(this::exec) 83 | .register(); 84 | } 85 | 86 | public void doCommand(Player targetPlayer, String command, CommandArguments args) { 87 | var context = new PromptContext.Builder() 88 | .setSender(targetPlayer) 89 | .setContent(command) 90 | .setConsoleDelegate(true) 91 | .build(); 92 | this.process(context); 93 | } 94 | } 95 | 96 | public static class PlayerDelegate extends DelegateCommand { 97 | 98 | 99 | PlayerDelegate(JavaPlugin plugin) { 100 | super(plugin); 101 | } 102 | 103 | @Override 104 | public void register() { 105 | var keys = plugin.getConfiguration().getPermissionKeys(); 106 | ArgumentSuggestions suggestions = ArgumentSuggestions.strings(keys); 107 | var permissionArg = new StringArgument("permission") 108 | .replaceSuggestions(suggestions); 109 | 110 | new CommandAPICommand("playerdelegate") 111 | .withPermission("commandprompter.playerdelegate") 112 | .withArguments(new PlayerArgument("target")) 113 | .withArguments(permissionArg) 114 | .withArguments(new GreedyStringArgument("command")) 115 | .executesConsole(this::exec) 116 | .register(); 117 | } 118 | 119 | @Override 120 | public void doCommand(Player targetPlayer, String command, CommandArguments args) { 121 | var context = new PromptContext.Builder() 122 | .setSender(targetPlayer) 123 | .setContent(command) 124 | .setPaKey(args.getRaw("permission")) 125 | .build(); 126 | this.process(context); 127 | } 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/commands/MainCommand.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.commands; 2 | 3 | import com.cyr1en.commandprompter.CommandPrompter; 4 | import dev.jorel.commandapi.CommandAPICommand; 5 | import dev.jorel.commandapi.executors.CommandArguments; 6 | import org.bukkit.command.CommandSender; 7 | 8 | import java.util.Collections; 9 | import java.util.regex.Pattern; 10 | 11 | public class MainCommand { 12 | private final CommandPrompter plugin; 13 | // private final PluginLogger logger; 14 | 15 | public MainCommand(CommandPrompter plugin) { 16 | this.plugin = plugin; 17 | // this.logger = plugin.getPluginLogger(); 18 | } 19 | 20 | public void register() { 21 | var command = new CommandAPICommand("commandprompter") 22 | .withAliases("cmdp") 23 | .withSubcommand(new Reload(plugin)) 24 | .withSubcommand(new Cancel(plugin)) 25 | .withSubcommand(new RebuildHeadCache(plugin)); 26 | 27 | if (!plugin.getConfiguration().showCompleted()) 28 | command.setArguments(Collections.emptyList()); 29 | 30 | command.register(); 31 | } 32 | 33 | public static class Reload extends CommandAPICommand { 34 | 35 | private final CommandPrompter plugin; 36 | 37 | public Reload(CommandPrompter plugin) { 38 | super("reload"); 39 | this.plugin = plugin; 40 | withPermission("commandprompter.reload"); 41 | executes(this::exec); 42 | } 43 | 44 | public void exec(CommandSender sender, CommandArguments args) { 45 | plugin.reload(true); 46 | String message = plugin.getI18N().getProperty("CommandReloadSuccess"); 47 | plugin.getMessenger().sendMessage(sender, message); 48 | } 49 | 50 | } 51 | 52 | public static class Cancel extends CommandAPICommand { 53 | 54 | public static final Pattern commandPattern = Pattern.compile("(commandprompter|cmdp) cancel"); 55 | 56 | private final CommandPrompter plugin; 57 | 58 | public Cancel(CommandPrompter plugin) { 59 | super("cancel"); 60 | this.plugin = plugin; 61 | withPermission("commandprompter.cancel"); 62 | executes(this::exec); 63 | } 64 | 65 | public void exec(CommandSender sender, CommandArguments args) { 66 | var inCommand = plugin.getPromptManager() 67 | .getPromptRegistry().inCommandProcess(sender); 68 | if (!inCommand) { 69 | var msg = plugin.getI18N().getProperty("CommandCancelNotInCompletion"); 70 | plugin.getMessenger().sendMessage(sender, msg); 71 | return; 72 | } 73 | 74 | plugin.getPromptManager().cancel(sender); 75 | } 76 | 77 | } 78 | 79 | public static class RebuildHeadCache extends CommandAPICommand { 80 | 81 | private final CommandPrompter plugin; 82 | 83 | public RebuildHeadCache(CommandPrompter plugin) { 84 | super("rebuildheadcache"); 85 | this.plugin = plugin; 86 | withAliases("rhc"); 87 | withPermission("commandprompter.rebuildheadcache"); 88 | executes(this::exec); 89 | } 90 | 91 | public void exec(CommandSender sender, CommandArguments args) { 92 | var currMillis = System.currentTimeMillis(); 93 | plugin.getHeadCache().reBuildCache().thenAccept(c -> { 94 | var timeTaken = System.currentTimeMillis() - currMillis; 95 | var msg = plugin.getI18N().getFormattedProperty("CommandRebuildHeadCacheSuccess", timeTaken + ""); 96 | plugin.getMessenger().sendMessage(sender, msg); 97 | plugin.getPluginLogger().debug("Rebuilt head cache in " + timeTaken + "ms"); 98 | }); 99 | } 100 | 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/config/AliasedSection.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.config; 2 | 3 | import com.cyr1en.kiso.mc.configuration.base.Config; 4 | import org.bukkit.configuration.ConfigurationSection; 5 | 6 | public interface AliasedSection { 7 | 8 | Config rawConfig(); 9 | 10 | /** 11 | * Get a value of a key in a validation section using a different key value to check if we're in the right section. 12 | * 13 | * @param key key to check 14 | * @param keyVal value of the key to check 15 | * @param query key to get the value of 16 | * @return value of the query key 17 | */ 18 | default String getInputValidationValue(String section, String key, String keyVal, String query) { 19 | var raw = rawConfig(); 20 | var validations = raw.getConfigurationSection(section); 21 | 22 | for (var k : validations.getKeys(false)) { 23 | var inner = validations.getConfigurationSection(k); 24 | var asserted = asserted(inner, key, keyVal, query); 25 | if (!asserted.isEmpty() && !asserted.isBlank()) return asserted; 26 | } 27 | return ""; 28 | } 29 | 30 | default String asserted(ConfigurationSection section, String key, String keyVal, String query) { 31 | if (section == null) return ""; 32 | // Still check for alias because we are anchoring each input validation with an alias. 33 | // Therefore, the alias must always be present. 34 | if (!section.getKeys(false).contains("Alias")) return ""; 35 | 36 | var cfgAlias = section.getString(key); 37 | cfgAlias = cfgAlias != null ? cfgAlias : ""; 38 | 39 | if (cfgAlias.equals(keyVal)) { 40 | var regex = section.getString(query); 41 | return regex != null ? regex : ""; 42 | } 43 | return ""; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/config/CommandPrompterConfig.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.config; 2 | 3 | import com.cyr1en.commandprompter.config.annotations.field.ConfigNode; 4 | import com.cyr1en.commandprompter.config.annotations.field.NodeComment; 5 | import com.cyr1en.commandprompter.config.annotations.field.NodeDefault; 6 | import com.cyr1en.commandprompter.config.annotations.field.NodeName; 7 | import com.cyr1en.commandprompter.config.annotations.type.ConfigHeader; 8 | import com.cyr1en.commandprompter.config.annotations.type.ConfigPath; 9 | import com.cyr1en.commandprompter.config.annotations.type.Configuration; 10 | import com.cyr1en.kiso.mc.configuration.base.Config; 11 | 12 | import java.util.List; 13 | import java.util.Set; 14 | 15 | @Configuration 16 | @ConfigPath("config.yml") 17 | @ConfigHeader({"Command Prompter", "Configuration"}) 18 | public record CommandPrompterConfig( 19 | Config rawConfig, 20 | 21 | @ConfigNode 22 | @NodeName("Prompt-Prefix") 23 | @NodeDefault("[&3Prompter&r] ") 24 | @NodeComment({"Set the plugin prefix"}) 25 | String promptPrefix, 26 | 27 | @ConfigNode 28 | @NodeName("Prompt-Timeout") 29 | @NodeDefault("300") 30 | @NodeComment({ 31 | "After how many seconds until", 32 | "CommandPrompter cancels a", 33 | "prompt"}) 34 | int promptTimeout, 35 | 36 | @ConfigNode 37 | @NodeName("Cancel-Keyword") 38 | @NodeDefault("cancel") 39 | @NodeComment({ 40 | "Word that cancels command", 41 | "prompting."}) 42 | String cancelKeyword, 43 | 44 | @ConfigNode 45 | @NodeName("Enable-Permission") 46 | @NodeDefault("false") 47 | @NodeComment({ 48 | "Enable permission check", 49 | "before a player can use", 50 | "the prompting feature", "", 51 | "Checking for commandprompter.use"}) 52 | boolean enablePermission, 53 | 54 | @ConfigNode 55 | @NodeName("Update-Checker") 56 | @NodeDefault("true") 57 | @NodeComment({ 58 | "Allow CommandPrompter to", 59 | "check if it's up to date."}) 60 | boolean updateChecker, 61 | 62 | @ConfigNode 63 | @NodeName("Argument-Regex") 64 | @NodeDefault("<.*?>") 65 | @NodeComment({ 66 | "This will determine if", 67 | "a part of a command is", 68 | "a prompt.", "", 69 | "ONLY CHANGE THE FIRST AND LAST", 70 | "I.E (.*?), {.*?}, or [.*?]"}) 71 | String argumentRegex, 72 | 73 | @ConfigNode 74 | @NodeName("Debug-Mode") 75 | @NodeDefault("false") 76 | @NodeComment({ 77 | "Enable debug mode for CommandPrompter." 78 | }) 79 | boolean debugMode, 80 | 81 | @ConfigNode 82 | @NodeName("Show-Complete-Command") 83 | @NodeDefault("true") 84 | @NodeComment({ 85 | "Should CommandPrompter send", 86 | "the completed command to the", 87 | "player before dispatching it?" 88 | }) 89 | boolean showCompleted, 90 | 91 | @ConfigNode 92 | @NodeName("Show-Prompt-Cancelled") 93 | @NodeDefault("true") 94 | @NodeComment({ 95 | "Should CommandPrompter a", 96 | "prompt cancellation message", 97 | "to the player." 98 | }) 99 | boolean showCancelled, 100 | 101 | @ConfigNode 102 | @NodeName("Fancy-Logger") 103 | @NodeDefault("true") 104 | @NodeComment({ 105 | "Enable fancy logger", 106 | "Do /commandprompter reload to", 107 | "apply the change" 108 | }) 109 | boolean fancyLogger, 110 | 111 | @ConfigNode 112 | @NodeName("Ignored-Commands") 113 | @NodeDefault("sampleCommand, sampleCommand2") 114 | @NodeComment({ 115 | "What commands should CommandPrompter ignore", 116 | "", 117 | "When a command is ignored, CommandPrompter", 118 | "will not proceed to check if the command", 119 | "contains a prompt.", 120 | "", 121 | "Do not include the /", 122 | "", 123 | "VentureChat channels are automatically ignored." 124 | }) 125 | List ignoredCommands, 126 | 127 | @ConfigNode 128 | @NodeName("Allowed-Commands-In-Prompt") 129 | @NodeDefault("sampleCommand, sampleCommand2") 130 | @NodeComment({ 131 | "What commands should CommandPrompter allow", 132 | "", 133 | "When the player executes another command while", 134 | "completing a prompt.", 135 | "", 136 | "Do not include the /", 137 | "", 138 | }) 139 | List allowedWhileInPrompt, 140 | 141 | @ConfigNode 142 | @NodeName("Permission-Attachment.ticks") 143 | @NodeDefault("1") 144 | @NodeComment({ 145 | "Permission Attachment Config", 146 | "", 147 | "Permission attachments allow players", 148 | "to have temporary permissions.", 149 | "", 150 | "ticks - Set how long (in ticks) should the", 151 | " permission attachment persist.", 152 | "", 153 | "permissions - permissions to temporarily", 154 | " attach to the players." 155 | }) 156 | int permissionAttachmentTicks, 157 | 158 | @ConfigNode 159 | @NodeName("Permission-Attachment.Permissions.GAMEMODE") 160 | @NodeDefault("bukkit.command.gamemode, " + 161 | "essentials.gamemode.survival," + 162 | "essentials.gamemode.creative") 163 | List attachmentPermissions, 164 | 165 | @ConfigNode 166 | @NodeName("Command-Tab-Complete") 167 | @NodeComment({ 168 | "Enable command tab complete", 169 | "for CommandPrompter" 170 | }) 171 | @NodeDefault("true") 172 | boolean commandTabComplete, 173 | 174 | @ConfigNode 175 | @NodeName("Ignore-MiniMessage") 176 | @NodeComment({ 177 | "If Prompt-Regex is left to default", 178 | "this will ignore MiniMessage syntax." 179 | }) 180 | boolean ignoreMiniMessage 181 | 182 | 183 | ) implements AliasedSection { 184 | 185 | public String[] getPermissionAttachment(String key) { 186 | var section = rawConfig.getConfigurationSection("Permission-Attachment.Permissions"); 187 | var keyExist = section.getKeys(false).contains(key); 188 | return keyExist ? section.getStringList(key).toArray(String[]::new) : new String[]{}; 189 | } 190 | 191 | public String[] getPermissionKeys() { 192 | var section = rawConfig.getConfigurationSection("Permission-Attachment.Permissions"); 193 | Set keys = section == null ? Set.of() : section.getKeys(false); 194 | keys.add("NONE"); 195 | return keys.toArray(String[]::new); 196 | } 197 | 198 | 199 | } 200 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/config/ConfigurationManager.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.config; 2 | 3 | import com.cyr1en.commandprompter.CommandPrompter; 4 | import com.cyr1en.commandprompter.PluginLogger; 5 | import com.cyr1en.commandprompter.config.annotations.field.*; 6 | import com.cyr1en.commandprompter.config.annotations.type.ConfigHeader; 7 | import com.cyr1en.commandprompter.config.annotations.type.ConfigPath; 8 | import com.cyr1en.commandprompter.config.annotations.type.Configuration; 9 | import com.cyr1en.commandprompter.config.handlers.ConfigTypeHandlerFactory; 10 | import com.cyr1en.kiso.mc.configuration.base.Config; 11 | import com.cyr1en.kiso.mc.configuration.base.ConfigManager; 12 | 13 | import java.lang.reflect.Field; 14 | import java.lang.reflect.InvocationTargetException; 15 | import java.util.ArrayList; 16 | import java.util.regex.Pattern; 17 | 18 | /** 19 | * Class that manages your plugin's configuration(s) 20 | * 21 | *

22 | * This class will turn a configuration record into a YAML configuration file. 23 | * For this class to work, a record must be: 24 | * - Annotated with {@link Configuration}. 25 | * - Fields of the record must be annotated with {@link ConfigNode} 26 | * 27 | *

28 | * Reflection is heavily used in this class to grab the types of each field in a 29 | * record and initializes the defaults for the config file. It then uses reflection 30 | * again to query those default values in the config file to instantiate the actual 31 | * record with its respective data. 32 | */ 33 | public class ConfigurationManager { 34 | 35 | private final ConfigManager configManager; 36 | private final PluginLogger logger; 37 | 38 | /** 39 | * Constructs a new ConfigurationManager for a given plugin. 40 | * 41 | * @param plugin The plugin instance which this manager will handle configurations for. 42 | */ 43 | public ConfigurationManager(CommandPrompter plugin) { 44 | this.configManager = new ConfigManager(plugin); 45 | this.logger = plugin.getPluginLogger(); 46 | } 47 | 48 | /** 49 | * Retrieves or initializes the configuration for a specified configuration class. 50 | * 51 | * @param The type of the configuration record. 52 | * @param configClass The class of the configuration record. 53 | * @return An instance of T with values from the configuration, or null if configuration class is not annotated properly. 54 | */ 55 | public T getConfig(Class configClass) { 56 | if (configClass.getAnnotation(Configuration.class) == null) 57 | return null; 58 | 59 | var config = initConfigFile(configClass); 60 | 61 | initializeConfig(configClass, config); 62 | 63 | var configValues = new ArrayList<>(); 64 | configValues.add(config); 65 | 66 | for (Field field : configClass.getDeclaredFields()) { 67 | if (field.getAnnotation(ConfigNode.class) == null) continue; 68 | 69 | var nameAnnotation = field.getAnnotation(NodeName.class); 70 | String nodeName = nameAnnotation != null ? nameAnnotation.value() : field.getName(); 71 | 72 | var handler = ConfigTypeHandlerFactory.getHandler(field.getType()); 73 | 74 | if (field.isAnnotationPresent(Match.class)) { 75 | var matchAnnotation = field.getAnnotation(Match.class); 76 | var regex = matchAnnotation.regex(); 77 | var pattern = Pattern.compile(regex); 78 | var res = pattern.matcher(config.getString(nodeName)).matches(); 79 | if (!res) { 80 | logger.warn("Configured value for " + nodeName + " is invalid! Using default."); 81 | configValues.add(handler.getDefault(field)); 82 | continue; 83 | } 84 | } 85 | 86 | configValues.add(handler.getValue(config, nodeName, field)); 87 | } 88 | try { 89 | // Since records in Java have canonical constructors, we directly use the first constructor 90 | var recordConfig = configClass.getDeclaredConstructors()[0].newInstance(configValues.toArray()); 91 | @SuppressWarnings("unchecked") var out = (T) recordConfig; 92 | return out; 93 | } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { 94 | logger.err("Failed to instantiate config: " + configClass.getSimpleName()); 95 | } 96 | return null; 97 | } 98 | 99 | /** 100 | * Reloads the configuration for the given configuration class. This method simply calls getConfig. 101 | * 102 | * @param The type of the configuration record. 103 | * @param configClass The class of the configuration record to reload. 104 | * @return A reloaded instance of T or null if reload fails or configClass is invalid. 105 | */ 106 | public T reload(Class configClass) { 107 | return getConfig(configClass); 108 | } 109 | 110 | /** 111 | * Initializes the configuration file with default values defined in the record. 112 | * 113 | * @param configClass The class of the record for which configuration needs to be initialized. 114 | * @param config The Config object representing the YAML file. 115 | */ 116 | private void initializeConfig(Class configClass, Config config) { 117 | var fields = configClass.getDeclaredFields(); 118 | for (Field field : fields) 119 | initializeField(field, config); 120 | } 121 | 122 | /** 123 | * Initializes or retrieves the configuration file for the given configuration class. 124 | * 125 | * @param configClass The class of the configuration record. 126 | * @return A Config object representing the configuration file. 127 | */ 128 | private Config initConfigFile(Class configClass) { 129 | // Determine file path and header for the config file 130 | var pathAnnotation = configClass.getAnnotation(ConfigPath.class); 131 | var filePath = pathAnnotation == null ? configClass.getSimpleName() : pathAnnotation.value(); 132 | 133 | var headerAnnotation = configClass.getAnnotation(ConfigHeader.class); 134 | var header = headerAnnotation == null ? new String[]{configClass.getSimpleName(), "Configuration"} : 135 | headerAnnotation.value(); 136 | return configManager.getNewConfig(filePath, header); 137 | } 138 | 139 | /** 140 | * Initializes a single field of the configuration with its default value or annotated default. 141 | * 142 | * @param field The field from the record to initialize in the config. 143 | * @param config The Config object where the field should be initialized. 144 | */ 145 | private void initializeField(Field field, Config config) { 146 | if (field.getAnnotation(ConfigNode.class) == null) return; 147 | 148 | var nameAnnotation = field.getAnnotation(NodeName.class); 149 | var nodeName = nameAnnotation != null ? nameAnnotation.value() : field.getName(); 150 | 151 | var handler = ConfigTypeHandlerFactory.getHandler(field.getType()); 152 | 153 | var defaultAnnotation = field.getAnnotation(NodeDefault.class); 154 | var nodeDefault = defaultAnnotation != null ? parseDefault(field) : handler.getDefault(field); 155 | 156 | var commentAnnotation = field.getAnnotation(NodeComment.class); 157 | var nodeComment = commentAnnotation != null ? commentAnnotation.value() : new String[]{}; 158 | 159 | if (config.get(nodeName) == null) { 160 | handler.setValue(config, nodeName, nodeDefault, nodeComment); 161 | config.saveConfig(); 162 | } 163 | } 164 | 165 | /** 166 | * Constructs a default value for a field when no default is provided via annotations. 167 | * 168 | * @param f The field for which to construct a default value. 169 | * @return An object representing the default value for the field, or null on failure. 170 | */ 171 | private Object constructDefaultField(Field f) { 172 | 173 | try { 174 | if (f.getType().isPrimitive()) { 175 | var handler = ConfigTypeHandlerFactory.getHandler(f.getType()); 176 | return handler.getDefault(f); 177 | } 178 | return f.getType().getDeclaredConstructor().newInstance(); 179 | } catch (Exception e) { 180 | logger.err("Failed to instantiate default value for field: " + f.getName()); 181 | } 182 | return null; 183 | } 184 | 185 | /** 186 | * Parses the default value from the @NodeDefault annotation for a field. 187 | * 188 | * @param field The field with a @NodeDefault annotation. 189 | * @return The parsed default value as an object, according to the field's type. 190 | */ 191 | private Object parseDefault(Field field) { 192 | var handler = ConfigTypeHandlerFactory.getHandler(field.getType()); 193 | return handler.getDefault(field); 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/config/annotations/field/ConfigNode.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.config.annotations.field; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.FIELD) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface ConfigNode { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/config/annotations/field/IntegerConstraint.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.config.annotations.field; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.FIELD) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface IntegerConstraint { 11 | 12 | int min(); 13 | 14 | int max(); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/config/annotations/field/Match.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.config.annotations.field; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.FIELD) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface Match { 11 | String regex(); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/config/annotations/field/NodeComment.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.config.annotations.field; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.FIELD) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface NodeComment { 11 | String[] value(); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/config/annotations/field/NodeDefault.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.config.annotations.field; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.FIELD) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface NodeDefault { 11 | String value(); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/config/annotations/field/NodeName.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.config.annotations.field; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.FIELD) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface NodeName { 11 | String value(); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/config/annotations/type/ConfigHeader.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.config.annotations.type; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(value = ElementType.TYPE) 9 | @Retention(value = RetentionPolicy.RUNTIME) 10 | public @interface ConfigHeader { 11 | String[] value(); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/config/annotations/type/ConfigPath.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.config.annotations.type; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(value = ElementType.TYPE) 9 | @Retention(value = RetentionPolicy.RUNTIME) 10 | public @interface ConfigPath { 11 | String value(); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/config/annotations/type/Configuration.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.config.annotations.type; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(value = ElementType.TYPE) 9 | @Retention(value = RetentionPolicy.RUNTIME) 10 | public @interface Configuration {} 11 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/config/handlers/ConfigTypeHandler.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.config.handlers; 2 | 3 | import com.cyr1en.kiso.mc.configuration.base.Config; 4 | 5 | import java.lang.reflect.Field; 6 | 7 | public interface ConfigTypeHandler { 8 | T getValue(Config config, String nodeName, Field field); 9 | 10 | void setValue(Config config, String nodeName, Object value, String[] comments); 11 | 12 | T getDefault(Field field); 13 | } -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/config/handlers/ConfigTypeHandlerFactory.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.config.handlers; 2 | 3 | import com.cyr1en.commandprompter.config.handlers.impl.*; 4 | 5 | import java.util.HashMap; 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | public class ConfigTypeHandlerFactory { 10 | private static final Map, ConfigTypeHandler> handlers = new HashMap<>(); 11 | 12 | static { 13 | handlers.put(int.class, new IntegerHandler()); 14 | handlers.put(boolean.class, new BooleanHandler()); 15 | handlers.put(String.class, new StringHandler()); 16 | handlers.put(double.class, new DoubleHandler()); 17 | handlers.put(List.class, new ListHandler()); 18 | } 19 | 20 | public static ConfigTypeHandler getHandler(Class type) { 21 | if (handlers.containsKey(type)) { 22 | return handlers.get(type); 23 | } 24 | return handlers.getOrDefault(type, new StringHandler()); // Fallback handler 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/config/handlers/impl/BooleanHandler.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.config.handlers.impl; 2 | 3 | import com.cyr1en.commandprompter.config.annotations.field.NodeDefault; 4 | import com.cyr1en.commandprompter.config.handlers.ConfigTypeHandler; 5 | import com.cyr1en.kiso.mc.configuration.base.Config; 6 | 7 | import java.lang.reflect.Field; 8 | 9 | public class BooleanHandler implements ConfigTypeHandler { 10 | 11 | 12 | @Override 13 | public Boolean getValue(Config config, String nodeName, Field field) { 14 | return config.getBoolean(nodeName); 15 | } 16 | 17 | @Override 18 | public void setValue(Config config, String nodeName, Object value, String[] comments) { 19 | config.set(nodeName, value, comments); 20 | } 21 | 22 | @Override 23 | public Boolean getDefault(Field field) { 24 | var defaultAnnotation = field.getAnnotation(NodeDefault.class); 25 | return defaultAnnotation != null && Boolean.parseBoolean(defaultAnnotation.value()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/config/handlers/impl/DoubleHandler.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.config.handlers.impl; 2 | 3 | import com.cyr1en.commandprompter.config.annotations.field.NodeDefault; 4 | import com.cyr1en.commandprompter.config.handlers.ConfigTypeHandler; 5 | import com.cyr1en.kiso.mc.configuration.base.Config; 6 | 7 | import java.lang.reflect.Field; 8 | 9 | public class DoubleHandler implements ConfigTypeHandler { 10 | @Override 11 | public Double getValue(Config config, String nodeName, Field field) { 12 | return config.getDouble(nodeName); 13 | } 14 | 15 | @Override 16 | public void setValue(Config config, String nodeName, Object value, String[] comments) { 17 | config.set(nodeName, value, comments); 18 | } 19 | 20 | @Override 21 | public Double getDefault(Field field) { 22 | var defaultAnnotation = field.getAnnotation(NodeDefault.class); 23 | return defaultAnnotation != null ? Double.parseDouble(defaultAnnotation.value()) : 0.0; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/config/handlers/impl/IntegerHandler.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.config.handlers.impl; 2 | 3 | import com.cyr1en.commandprompter.config.annotations.field.IntegerConstraint; 4 | import com.cyr1en.commandprompter.config.annotations.field.NodeDefault; 5 | import com.cyr1en.commandprompter.config.handlers.ConfigTypeHandler; 6 | import com.cyr1en.kiso.mc.configuration.base.Config; 7 | 8 | import java.lang.reflect.Field; 9 | 10 | public class IntegerHandler implements ConfigTypeHandler { 11 | @Override 12 | public Integer getValue(Config config, String nodeName, Field field) { 13 | int val = config.getInt(nodeName); 14 | IntegerConstraint constraint = field.getAnnotation(IntegerConstraint.class); 15 | if (constraint != null) 16 | val = Math.min(Math.max(val, constraint.min()), constraint.max()); 17 | return val; 18 | } 19 | 20 | @Override 21 | public void setValue(Config config, String nodeName, Object value, String[] comments) { 22 | config.set(nodeName, value, comments); 23 | } 24 | 25 | @Override 26 | public Integer getDefault(Field field) { 27 | var defaultAnnotation = field.getAnnotation(NodeDefault.class); 28 | return defaultAnnotation != null ? Integer.parseInt(defaultAnnotation.value()) : 0; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/config/handlers/impl/ListHandler.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.config.handlers.impl; 2 | 3 | import com.cyr1en.commandprompter.config.annotations.field.NodeDefault; 4 | import com.cyr1en.commandprompter.config.handlers.ConfigTypeHandler; 5 | import com.cyr1en.kiso.mc.configuration.base.Config; 6 | 7 | import java.lang.reflect.Field; 8 | import java.util.List; 9 | 10 | public class ListHandler implements ConfigTypeHandler> { 11 | @Override 12 | public List getValue(Config config, String nodeName, Field field) { 13 | return config.getList(nodeName); 14 | } 15 | 16 | @Override 17 | public void setValue(Config config, String nodeName, Object value, String[] comments) { 18 | config.set(nodeName, value, comments); 19 | } 20 | 21 | @Override 22 | public List getDefault(Field field) { 23 | var defaultAnnotation = field.getAnnotation(NodeDefault.class); 24 | if (defaultAnnotation != null) { 25 | return List.of(defaultAnnotation.value().split(",")); 26 | } 27 | return List.of(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/config/handlers/impl/StringHandler.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.config.handlers.impl; 2 | 3 | import com.cyr1en.commandprompter.config.annotations.field.NodeDefault; 4 | import com.cyr1en.commandprompter.config.handlers.ConfigTypeHandler; 5 | import com.cyr1en.kiso.mc.configuration.base.Config; 6 | 7 | import java.lang.reflect.Field; 8 | 9 | public class StringHandler implements ConfigTypeHandler { 10 | @Override 11 | public String getValue(Config config, String nodeName, Field field) { 12 | return config.getString(nodeName); 13 | } 14 | 15 | @Override 16 | public void setValue(Config config, String nodeName, Object value, String[] comments) { 17 | config.set(nodeName, value, comments); 18 | } 19 | 20 | @Override 21 | public String getDefault(Field field) { 22 | var defaultAnnotation = field.getAnnotation(NodeDefault.class); 23 | return defaultAnnotation != null ? defaultAnnotation.value() : ""; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/hook/Hook.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.hook; 2 | 3 | import com.cyr1en.commandprompter.util.Util.ConsumerFallback; 4 | import org.jetbrains.annotations.NotNull; 5 | 6 | import java.util.NoSuchElementException; 7 | import java.util.Objects; 8 | import java.util.function.Consumer; 9 | 10 | public final class Hook { 11 | 12 | private static final Hook EMPTY = new Hook<>(); 13 | 14 | private final T value; 15 | private String targetPlugin = ""; 16 | 17 | private Hook() { 18 | this.value = null; 19 | } 20 | 21 | private Hook(T val) { 22 | this.value = val; 23 | } 24 | 25 | public static Hook of(T val) { 26 | return new Hook<>(val); 27 | } 28 | 29 | public static Hook empty() { 30 | @SuppressWarnings("unchecked") var t = (Hook) EMPTY; 31 | return t; 32 | } 33 | 34 | public T get() { 35 | if (value == null) 36 | throw new NoSuchElementException("No value present"); 37 | return value; 38 | } 39 | 40 | public void setTargetPlugin(String targetPlugin) { 41 | this.targetPlugin = targetPlugin; 42 | } 43 | 44 | public String getTargetPluginName() { 45 | return targetPlugin; 46 | } 47 | 48 | /** 49 | * Accessor to check if the plugin is hooked. 50 | * 51 | * @return if this plugin hook is actually hooked. 52 | */ 53 | public boolean isHooked() { 54 | return Objects.nonNull(value); 55 | } 56 | 57 | public ConsumerFallback ifHooked(@NotNull Consumer consumer) { 58 | return new ConsumerFallback<>(value, consumer, this::isHooked); 59 | } 60 | 61 | @Override 62 | public String toString() { 63 | return "Hook{" + 64 | "targetPlugin='" + targetPlugin + '\'' + 65 | '}'; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/hook/HookContainer.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.hook; 2 | 3 | import com.cyr1en.commandprompter.CommandPrompter; 4 | import com.cyr1en.commandprompter.hook.annotations.TargetPlugin; 5 | import com.cyr1en.commandprompter.hook.hooks.*; 6 | import org.bukkit.Bukkit; 7 | import org.bukkit.event.Listener; 8 | import org.bukkit.plugin.IllegalPluginAccessException; 9 | import org.fusesource.jansi.Ansi; 10 | 11 | import java.lang.reflect.InvocationTargetException; 12 | import java.util.HashMap; 13 | import java.util.List; 14 | 15 | public class HookContainer extends HashMap, Hook> { 16 | 17 | private final CommandPrompter plugin; 18 | 19 | public HookContainer(CommandPrompter plugin) { 20 | this.plugin = plugin; 21 | } 22 | 23 | public void initHooks() { 24 | hook(PremiumVanishHook.class); 25 | hook(SuperVanishHook.class); 26 | hook(CarbonChatHook.class); 27 | hook(VanishNoPacketHook.class); 28 | hook(PapiHook.class); 29 | hook(TownyHook.class); 30 | hook(LuckPermsHook.class); 31 | hook(HuskTownsHook.class); 32 | } 33 | 34 | @Override 35 | public Hook put(Class key, Hook value) { 36 | 37 | if (value.isHooked()) { 38 | var prefix = new Ansi().fgRgb(153, 214, 90).a("✓").reset(); 39 | plugin.getPluginLogger().info(prefix + " " + key.getSimpleName() + " contained"); 40 | } 41 | 42 | return super.put(key, value); 43 | } 44 | 45 | private void hook(Class pluginHook) { 46 | var instance = constructHook(pluginHook); 47 | this.put(pluginHook, instance); 48 | } 49 | 50 | private Hook constructHook(Class pluginHook) { 51 | plugin.getPluginLogger().debug("Constructing hook: " + pluginHook.getSimpleName()); 52 | if (!pluginHook.isAnnotationPresent(TargetPlugin.class)) return Hook.empty(); 53 | 54 | var targetPluginName = pluginHook.getAnnotation(TargetPlugin.class).pluginName(); 55 | plugin.getPluginLogger().debug("Hook target plugin name: " + targetPluginName); 56 | var targetEnabled = Bukkit.getPluginManager().isPluginEnabled(targetPluginName); 57 | plugin.getPluginLogger().debug("Target enabled: " + targetEnabled); 58 | if (!targetEnabled) { 59 | plugin.getPluginLogger().debug(targetPluginName + " is not enabled. Could not hook."); 60 | return Hook.empty(); 61 | } 62 | 63 | try { 64 | var constructor = pluginHook.getDeclaredConstructor(CommandPrompter.class); 65 | constructor.setAccessible(true); 66 | plugin.getPluginLogger().debug("Hook construct: " + constructor.getName()); 67 | var instance = constructor.newInstance(plugin); 68 | if (instance instanceof Listener) 69 | plugin.getServer().getPluginManager().registerEvents((Listener) instance, plugin); 70 | plugin.getPluginLogger().debug("Hook instance: " + instance.getClass()); 71 | 72 | var hook = Hook.of(instance); 73 | hook.setTargetPlugin(targetPluginName); 74 | return hook; 75 | } catch (InvocationTargetException | NoSuchMethodException | IllegalAccessException | InstantiationException | 76 | IllegalPluginAccessException e) { 77 | plugin.getPluginLogger().debug(e.toString()); 78 | } 79 | return Hook.empty(); 80 | } 81 | 82 | @SuppressWarnings("unchecked") 83 | public Hook getVanishHook() { 84 | return values().stream() 85 | .filter(hook -> hook.isHooked() && (hook.get() instanceof VanishHook)) 86 | .map(hook -> (Hook) hook).findFirst().orElse(Hook.of(new VanishHook(plugin))); 87 | } 88 | 89 | public Hook getHook(Class hookClass) { 90 | @SuppressWarnings("unchecked") var t = (Hook) get(hookClass); 91 | if (t == null) return Hook.empty(); 92 | return t; 93 | } 94 | 95 | @SuppressWarnings("unchecked") 96 | public List> getFilterHooks() { 97 | return values().stream() 98 | .filter(hook -> hook.isHooked() && hook.get() instanceof FilterHook) 99 | .map(hook -> (Hook) hook) 100 | .toList(); 101 | } 102 | 103 | public boolean isHooked(Class hookClass) { 104 | return getHook(hookClass).isHooked(); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/hook/annotations/TargetPlugin.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.hook.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(value = ElementType.TYPE) 9 | @Retention(value = RetentionPolicy.RUNTIME) 10 | public @interface TargetPlugin { 11 | String pluginName(); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/hook/hooks/BaseHook.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.hook.hooks; 2 | 3 | import com.cyr1en.commandprompter.CommandPrompter; 4 | 5 | public class BaseHook { 6 | 7 | private final CommandPrompter plugin; 8 | 9 | public BaseHook(CommandPrompter plugin) { 10 | this.plugin = plugin; 11 | } 12 | 13 | public CommandPrompter getPlugin() { 14 | return this.plugin; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/hook/hooks/CarbonChatHook.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.hook.hooks; 2 | 3 | import com.cyr1en.commandprompter.CommandPrompter; 4 | import com.cyr1en.commandprompter.hook.annotations.TargetPlugin; 5 | import com.cyr1en.commandprompter.prompt.PromptContext; 6 | import com.cyr1en.commandprompter.prompt.PromptManager; 7 | import net.draycia.carbon.api.CarbonChatProvider; 8 | import net.draycia.carbon.api.event.events.CarbonChatEvent; 9 | import net.kyori.adventure.text.Component; 10 | import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; 11 | import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; 12 | import org.bukkit.Bukkit; 13 | import org.bukkit.event.Listener; 14 | 15 | import java.util.Arrays; 16 | import java.util.Objects; 17 | 18 | @TargetPlugin(pluginName = "CarbonChat") 19 | public class CarbonChatHook extends BaseHook implements Listener { 20 | private final PromptManager promptManager; 21 | 22 | public CarbonChatHook(CommandPrompter plugin) { 23 | super(plugin); 24 | this.promptManager = plugin.getPromptManager(); 25 | } 26 | 27 | public boolean subscribe() { 28 | var cc = CarbonChatProvider.carbonChat(); 29 | if (cc == null) { 30 | getPlugin().getPluginLogger().warn("No CarbonChat was provided, using default chat listener..."); 31 | return false; 32 | } 33 | cc.eventHandler().subscribe(CarbonChatEvent.class, -100, false, this::handle); 34 | return true; 35 | } 36 | 37 | public void handle(CarbonChatEvent event) { 38 | var player = Bukkit.getPlayer(event.sender().uuid()); 39 | if (Objects.isNull(player) || !promptManager.getPromptRegistry().inCommandProcess(player)) 40 | return; 41 | event.cancelled(true); 42 | event.recipients().clear(); 43 | Arrays.stream(event.getClass().getDeclaredMethods()).forEach(method -> getPlugin().getPluginLogger().debug(method.toGenericString())); 44 | var msg = event.message(); 45 | var serializedMsg = PlainTextComponentSerializer.plainText().serialize(msg); 46 | var cancel = getPlugin().getConfiguration().cancelKeyword(); 47 | 48 | if (cancel.equalsIgnoreCase(serializedMsg)) { 49 | promptManager.cancel(player); 50 | event.message(Component.empty()); 51 | return; 52 | } 53 | 54 | var queue = promptManager.getPromptRegistry().get(player); 55 | if (Objects.isNull(queue)) 56 | return; 57 | 58 | var prompt = queue.peek(); 59 | if (Objects.nonNull(prompt)) { 60 | var ds = LegacyComponentSerializer.legacyAmpersand().serialize(event.message()); 61 | serializedMsg = prompt.sanitizeInput() ? ds : serializedMsg; 62 | } 63 | var ctx = new PromptContext.Builder().setSender(player).setContent(serializedMsg).build(); 64 | Bukkit.getScheduler().runTask(getPlugin(), () -> promptManager.processPrompt(ctx)); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/hook/hooks/FilterHook.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.hook.hooks; 2 | 3 | import com.cyr1en.commandprompter.prompt.ui.HeadCache; 4 | 5 | /** 6 | * A hook that registers filters to the head cache. 7 | */ 8 | public interface FilterHook { 9 | 10 | /** 11 | * Register filters to the head cache. 12 | * 13 | * @param headCache the head cache 14 | */ 15 | void registerFilters(HeadCache headCache); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/hook/hooks/HuskTownsHook.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.hook.hooks; 2 | 3 | import com.cyr1en.commandprompter.CommandPrompter; 4 | import com.cyr1en.commandprompter.hook.annotations.TargetPlugin; 5 | import com.cyr1en.commandprompter.prompt.ui.CacheFilter; 6 | import com.cyr1en.commandprompter.prompt.ui.HeadCache; 7 | import net.william278.husktowns.api.HuskTownsAPI; 8 | import net.william278.husktowns.user.User; 9 | import org.bukkit.Bukkit; 10 | import org.bukkit.entity.Player; 11 | 12 | import java.util.List; 13 | import java.util.Objects; 14 | import java.util.regex.Pattern; 15 | 16 | @TargetPlugin(pluginName = "HuskTowns") 17 | public class HuskTownsHook extends BaseHook implements FilterHook { 18 | private static final HuskTownsAPI huskTownsAPI = HuskTownsAPI.getInstance(); 19 | 20 | public HuskTownsHook(CommandPrompter plugin) { 21 | super(plugin); 22 | } 23 | 24 | @Override 25 | public void registerFilters(HeadCache headCache) { 26 | headCache.registerFilter(new TownFilter()); 27 | } 28 | 29 | /* 30 | * A PlayerUI filter that would filter all players that are in the same town as the relative player. 31 | */ 32 | private static class TownFilter extends CacheFilter { 33 | 34 | public TownFilter() { 35 | super(Pattern.compile("ht"), "PlayerUI.Filter-Format.HuskTowns"); 36 | } 37 | 38 | @Override 39 | public CacheFilter reConstruct(String promptKey) { 40 | return this; 41 | } 42 | 43 | @Override 44 | public List filter(Player relativePlayer) { 45 | var town = huskTownsAPI.getUserTown(User.of(relativePlayer.getUniqueId(), relativePlayer.getName())); 46 | if (town.isEmpty()) return List.of(); 47 | if (town.get().town().getMembers().isEmpty()) return List.of(); 48 | return town.get().town().getMembers().keySet().stream() 49 | .map(Bukkit::getPlayer) 50 | .filter(Objects::nonNull) 51 | .toList(); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/hook/hooks/LuckPermsHook.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.hook.hooks; 2 | 3 | import com.cyr1en.commandprompter.CommandPrompter; 4 | import com.cyr1en.commandprompter.hook.Hook; 5 | import com.cyr1en.commandprompter.hook.annotations.TargetPlugin; 6 | import com.cyr1en.commandprompter.prompt.ui.CacheFilter; 7 | import com.cyr1en.commandprompter.prompt.ui.HeadCache; 8 | import net.luckperms.api.LuckPerms; 9 | import org.bukkit.Bukkit; 10 | import org.bukkit.entity.Player; 11 | import org.bukkit.plugin.RegisteredServiceProvider; 12 | 13 | import java.util.List; 14 | import java.util.regex.Pattern; 15 | 16 | /** 17 | * Hook for LuckPerms plugin. 18 | * 19 | *

20 | * Main component of this hook are the cache filters for LuckPerms groups. 21 | */ 22 | @TargetPlugin(pluginName = "LuckPerms") 23 | public class LuckPermsHook extends BaseHook implements FilterHook { 24 | 25 | private LuckPerms api; 26 | 27 | /** 28 | * Construct a new LuckPermsHook. 29 | * 30 | * @param plugin the plugin 31 | */ 32 | public LuckPermsHook(CommandPrompter plugin) { 33 | super(plugin); 34 | RegisteredServiceProvider provider = plugin.getServer().getServicesManager().getRegistration(LuckPerms.class); 35 | if (provider != null) { 36 | this.api = provider.getProvider(); 37 | } else { 38 | // If LuckPerms was loaded but the service provider was not found then we make sure that the container 39 | // is empty. 40 | plugin.getHookContainer().replace(LuckPermsHook.class, Hook.empty()); 41 | } 42 | } 43 | 44 | /** 45 | * Register the cache filters for LuckPerms groups. 46 | * 47 | * @param cache the head cache. 48 | */ 49 | public void registerFilters(HeadCache cache) { 50 | cache.registerFilter(new OwnGroupFilter(this)); 51 | cache.registerFilter(new GroupFilter(this)); 52 | } 53 | 54 | /** 55 | * Get all players that are in the same group as the relative player. 56 | * 57 | * @param groupName the group name 58 | * @return a list of players with the same group 59 | */ 60 | @SuppressWarnings("unchecked") 61 | private List getPlayersWithGroup(String groupName) { 62 | if (api == null || groupName.isBlank()) return List.of(); 63 | return (List) Bukkit.getOnlinePlayers().stream() 64 | .filter(p -> { 65 | var user = api.getUserManager().getUser(p.getName()); 66 | if (user == null) return false; 67 | var group = user.getPrimaryGroup(); 68 | return group.equals(groupName); 69 | }).toList(); 70 | } 71 | 72 | /** 73 | * Get the LuckPerms API. 74 | * 75 | * @return the LuckPerms API 76 | */ 77 | private LuckPerms getApi() { 78 | return api; 79 | } 80 | 81 | 82 | /** 83 | * A PlayerUI filter that would filter all players that are in the same group as the relative player. 84 | * 85 | *

86 | * This is a special case of {@link CacheFilter} where the regex key is 'og'. 87 | * This would filter all players that are in the same group as the relative player. 88 | */ 89 | private static class OwnGroupFilter extends CacheFilter { 90 | 91 | private final LuckPermsHook hook; 92 | 93 | /** 94 | * Construct a new own group filter. 95 | * 96 | * @param hook the LuckPerms hook 97 | */ 98 | public OwnGroupFilter(LuckPermsHook hook) { 99 | super(Pattern.compile("lpo"), "PlayerUI.Filter-Format.LuckPermsOwnGroup"); 100 | this.hook = hook; 101 | } 102 | 103 | @Override 104 | public CacheFilter reConstruct(String promptKey) { 105 | // No need to re-construct 106 | return this; 107 | } 108 | 109 | /** 110 | * Filter all players that are in the same group as the relative player. 111 | * 112 | * @param relativePlayer the players to filter 113 | * @return a list of players with the same group 114 | */ 115 | @Override 116 | public List filter(Player relativePlayer) { 117 | var user = hook.getApi().getUserManager().getUser(relativePlayer.getUniqueId()); 118 | if (user == null) return List.of(); 119 | var group = user.getPrimaryGroup(); 120 | return hook.getPlayersWithGroup(group); 121 | } 122 | } 123 | 124 | /** 125 | * A PlayerUI filter that would filter all players based on the group name. 126 | * 127 | *

128 | * The regex for this filter is 'g(\S+)'. In regex capturing group 1 is the group 129 | * name that would be used to filter. 130 | */ 131 | private static class GroupFilter extends CacheFilter { 132 | 133 | private final String groupName; 134 | private final LuckPermsHook hook; 135 | 136 | /** 137 | * Default construct a new group filter. 138 | * 139 | * @param hook the LuckPerms hook 140 | */ 141 | public GroupFilter(LuckPermsHook hook) { 142 | this("", hook); 143 | } 144 | 145 | /** 146 | * Constructor to construct a new group filter with a group name. 147 | * 148 | *

149 | * This constructor will be used by the {@link #reConstruct(String)} function. 150 | * 151 | * @param groupName the group name that would be used to filter 152 | * @param hook the LuckPerms hook 153 | */ 154 | public GroupFilter(String groupName, LuckPermsHook hook) { 155 | super(Pattern.compile("lpg(\\S+);"), "PlayerUI.Filter-Format.LuckPermsGroup", 1); 156 | this.groupName = groupName; 157 | this.hook = hook; 158 | } 159 | 160 | @Override 161 | public CacheFilter reConstruct(String promptKey) { 162 | var matcher = this.getRegexKey().matcher(promptKey); 163 | var found = matcher.find(); 164 | var groupName = found ? matcher.group(1) : ""; 165 | return new GroupFilter(groupName, hook); 166 | } 167 | 168 | @Override 169 | public List filter(Player relativePlayer) { 170 | var players = hook.getPlayersWithGroup(groupName); 171 | hook.getPlugin().getPluginLogger().debug("Players: %s", players); 172 | return players; 173 | } 174 | } 175 | 176 | } 177 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/hook/hooks/PapiHook.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.hook.hooks; 2 | 3 | import com.cyr1en.commandprompter.CommandPrompter; 4 | import com.cyr1en.commandprompter.hook.annotations.TargetPlugin; 5 | import me.clip.placeholderapi.PlaceholderAPI; 6 | import org.bukkit.entity.Player; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | @TargetPlugin(pluginName = "PlaceholderAPI") 10 | public class PapiHook extends BaseHook { 11 | public PapiHook(CommandPrompter plugin) { 12 | super(plugin); 13 | } 14 | 15 | public String setPlaceholder(@NotNull Player player, @NotNull String txt) { 16 | if (!papiPlaceholders(txt)) return txt; 17 | return PlaceholderAPI.setPlaceholders(player, txt); 18 | } 19 | 20 | public boolean papiPlaceholders(String str) { 21 | return PlaceholderAPI.containsPlaceholders(str); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/hook/hooks/PremiumVanishHook.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.hook.hooks; 2 | 3 | import com.cyr1en.commandprompter.CommandPrompter; 4 | import com.cyr1en.commandprompter.hook.annotations.TargetPlugin; 5 | import de.myzelyam.api.vanish.PlayerVanishStateChangeEvent; 6 | import de.myzelyam.api.vanish.VanishAPI; 7 | import org.bukkit.Bukkit; 8 | import org.bukkit.entity.Player; 9 | import org.bukkit.event.EventHandler; 10 | import org.bukkit.event.EventPriority; 11 | import org.bukkit.event.Listener; 12 | 13 | @TargetPlugin(pluginName = "PremiumVanish") 14 | public class PremiumVanishHook extends VanishHook implements Listener { 15 | 16 | public PremiumVanishHook(CommandPrompter plugin) { 17 | super(plugin); 18 | } 19 | 20 | @Override 21 | public boolean isInvisible(Player p) { 22 | return VanishAPI.isInvisible(p); 23 | } 24 | 25 | @EventHandler(priority = EventPriority.NORMAL) 26 | public void onStateChange(PlayerVanishStateChangeEvent e) { 27 | var player = Bukkit.getPlayer(e.getUUID()); 28 | if (player == null) return; 29 | onStateChange(player, e::isVanishing); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/hook/hooks/SuperVanishHook.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.hook.hooks; 2 | 3 | import com.cyr1en.commandprompter.CommandPrompter; 4 | import com.cyr1en.commandprompter.hook.annotations.TargetPlugin; 5 | import de.myzelyam.api.vanish.PlayerVanishStateChangeEvent; 6 | import de.myzelyam.api.vanish.VanishAPI; 7 | import org.bukkit.Bukkit; 8 | import org.bukkit.entity.Player; 9 | import org.bukkit.event.EventHandler; 10 | import org.bukkit.event.EventPriority; 11 | import org.bukkit.event.Listener; 12 | 13 | @TargetPlugin(pluginName = "SuperVanish") 14 | public class SuperVanishHook extends VanishHook implements Listener { 15 | 16 | private SuperVanishHook(CommandPrompter plugin) { 17 | super(plugin); 18 | } 19 | 20 | public boolean isInvisible(Player p) { 21 | return VanishAPI.isInvisible(p); 22 | } 23 | 24 | @EventHandler(priority = EventPriority.NORMAL) 25 | public void onVisibilityStateChange(PlayerVanishStateChangeEvent e) { 26 | var player = Bukkit.getPlayer(e.getUUID()); 27 | if (player == null) return; 28 | onStateChange(player, e::isVanishing); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/hook/hooks/TownyHook.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.hook.hooks; 2 | 3 | import com.cyr1en.commandprompter.CommandPrompter; 4 | import com.cyr1en.commandprompter.hook.annotations.TargetPlugin; 5 | import com.cyr1en.commandprompter.prompt.ui.CacheFilter; 6 | import com.cyr1en.commandprompter.prompt.ui.HeadCache; 7 | import com.palmergames.bukkit.towny.TownyAPI; 8 | import org.bukkit.Bukkit; 9 | import org.bukkit.entity.Player; 10 | 11 | import java.util.List; 12 | import java.util.Objects; 13 | import java.util.regex.Pattern; 14 | 15 | @TargetPlugin(pluginName = "Towny") 16 | public class TownyHook extends BaseHook implements FilterHook { 17 | public TownyHook(CommandPrompter plugin) { 18 | super(plugin); 19 | } 20 | 21 | public void registerFilters(HeadCache cache) { 22 | cache.registerFilter(new TownFilter()); 23 | cache.registerFilter(new NationFilter()); 24 | } 25 | 26 | /** 27 | * A PlayerUI filter that would filter all players that are in the same town as the relative player. 28 | */ 29 | private static class TownFilter extends CacheFilter { 30 | 31 | 32 | public TownFilter() { 33 | super(Pattern.compile("tt"), "PlayerUI.Filter-Format.TownyTown"); 34 | } 35 | 36 | @Override 37 | public CacheFilter reConstruct(String promptKey) { 38 | return this; 39 | } 40 | 41 | @Override 42 | public List filter(Player relativePlayer) { 43 | var town = TownyAPI.getInstance().getTown(relativePlayer); 44 | if (town == null) return List.of(); 45 | return town.getResidents().stream() 46 | .map(r -> Bukkit.getPlayer(r.getName())) 47 | .filter(Objects::nonNull) 48 | .toList(); 49 | } 50 | } 51 | 52 | /** 53 | * A PlayerUI filter that would filter all players that are in the same nation as the relative player. 54 | */ 55 | private static class NationFilter extends CacheFilter { 56 | 57 | public NationFilter() { 58 | super(Pattern.compile("tn"), "PlayerUI.Filter-Format.TownyNation"); 59 | } 60 | 61 | @Override 62 | public CacheFilter reConstruct(String promptKey) { 63 | return this; 64 | } 65 | 66 | @Override 67 | public List filter(Player relativePlayer) { 68 | var nation = TownyAPI.getInstance().getNation(relativePlayer); 69 | if (nation == null) return List.of(); 70 | return nation.getResidents().stream() 71 | .map(r -> Bukkit.getPlayer(r.getName())) 72 | .filter(Objects::nonNull) 73 | .toList(); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/hook/hooks/VanishHook.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.hook.hooks; 2 | 3 | import com.cyr1en.commandprompter.CommandPrompter; 4 | import com.cyr1en.commandprompter.prompt.ui.HeadCache; 5 | import org.bukkit.entity.Player; 6 | import org.bukkit.metadata.MetadataValue; 7 | 8 | import java.util.Objects; 9 | import java.util.function.Supplier; 10 | 11 | public class VanishHook extends BaseHook { 12 | 13 | private final HeadCache headCache; 14 | 15 | public VanishHook(CommandPrompter plugin) { 16 | super(plugin); 17 | this.headCache = plugin.getHeadCache(); 18 | } 19 | 20 | public boolean isInvisible(Player p) { 21 | var meta = p.getMetadata("vanished").stream().filter(MetadataValue::asBoolean).findAny(); 22 | return meta.isPresent(); 23 | } 24 | 25 | public void onStateChange(Player player, Supplier isGoingInvisible) { 26 | getPlugin().getPluginLogger().debug("Pre Vanish State Change: " + headCache.getHeads().stream().map(i -> 27 | Objects.requireNonNull(i.getItemMeta()).getDisplayName()).toList()); 28 | if (isGoingInvisible.get()) 29 | headCache.invalidate(player); 30 | else 31 | headCache.getHeadFor(Objects.requireNonNull(player)); 32 | getPlugin().getPluginLogger().debug("Post Vanish State Change: " + headCache.getHeads().stream().map(i -> 33 | Objects.requireNonNull(i.getItemMeta()).getDisplayName()).toList()); 34 | } 35 | 36 | 37 | @Override 38 | public String toString() { 39 | return this.getClass().getSimpleName(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/hook/hooks/VanishNoPacketHook.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.hook.hooks; 2 | 3 | import com.cyr1en.commandprompter.CommandPrompter; 4 | import com.cyr1en.commandprompter.hook.annotations.TargetPlugin; 5 | import org.bukkit.Bukkit; 6 | import org.bukkit.entity.Player; 7 | import org.bukkit.event.EventHandler; 8 | import org.bukkit.event.EventPriority; 9 | import org.bukkit.event.Listener; 10 | import org.kitteh.vanish.VanishPerms; 11 | import org.kitteh.vanish.VanishPlugin; 12 | import org.kitteh.vanish.event.VanishStatusChangeEvent; 13 | 14 | @TargetPlugin(pluginName = "VanishNoPacket") 15 | public class VanishNoPacketHook extends VanishHook implements Listener { 16 | 17 | private VanishPlugin vanishPlugin; 18 | 19 | private VanishNoPacketHook(CommandPrompter plugin) { 20 | super(plugin); 21 | var jPlugin = Bukkit.getServer().getPluginManager().getPlugin("VanishNoPacket"); 22 | if (jPlugin instanceof VanishPlugin) 23 | this.vanishPlugin = (VanishPlugin) jPlugin; 24 | else 25 | plugin.getPluginLogger().warn("VanishNoPacketHook cannot be initialized without VanishNoPacket"); 26 | } 27 | 28 | public boolean isInvisible(Player p) { 29 | if (vanishPlugin == null) return false; 30 | if (p.hasPermission("vanish.hooks.dynmap.alwayshidden") || VanishPerms.joinVanished(p)) 31 | return true; 32 | return vanishPlugin.getManager().isVanished(p); 33 | } 34 | 35 | @EventHandler(priority = EventPriority.NORMAL) 36 | public void onVisibilityStateChange(VanishStatusChangeEvent event) { 37 | onStateChange(event.getPlayer(), event::isVanishing); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/listener/CommandListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Ethan Bacurio 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.cyr1en.commandprompter.listener; 26 | 27 | import com.cyr1en.commandprompter.CommandPrompter; 28 | import com.cyr1en.commandprompter.prompt.ContextProcessor; 29 | import com.cyr1en.commandprompter.prompt.PromptManager; 30 | import org.bukkit.event.Listener; 31 | 32 | public class CommandListener extends ContextProcessor implements Listener { 33 | 34 | protected CommandPrompter plugin; 35 | protected PromptManager promptManager; 36 | 37 | public CommandListener(PromptManager promptManager) { 38 | super(promptManager.getPlugin(), promptManager); 39 | this.promptManager = promptManager; 40 | this.plugin = promptManager.getPlugin(); 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/listener/CommandSendListener.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.listener; 2 | 3 | import com.cyr1en.commandprompter.CommandPrompter; 4 | import org.bukkit.event.EventHandler; 5 | import org.bukkit.event.EventPriority; 6 | import org.bukkit.event.Listener; 7 | import org.bukkit.event.player.PlayerCommandSendEvent; 8 | 9 | import java.util.Arrays; 10 | 11 | public class CommandSendListener implements Listener { 12 | 13 | private final CommandPrompter plugin; 14 | 15 | private static final String[] keys = { 16 | "commandprompter:cmdp", 17 | "commandprompter:commandprompter", 18 | "commandprompter:cmdprompter", 19 | "cmdp", 20 | "commandprompter", 21 | "cmdprompter" 22 | }; 23 | 24 | public CommandSendListener(CommandPrompter plugin) { 25 | this.plugin = plugin; 26 | } 27 | 28 | @EventHandler(priority = EventPriority.HIGHEST) 29 | private void onCommandSend(PlayerCommandSendEvent event) { 30 | plugin.getPluginLogger().debug("CommandSendEvent caught."); 31 | plugin.getPluginLogger().debug("Complete: " + plugin.getConfiguration().commandTabComplete()); 32 | if (!plugin.getConfiguration().commandTabComplete()) 33 | event.getCommands().removeAll(Arrays.stream(keys).toList()); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/listener/VanillaListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Ethan Bacurio 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.cyr1en.commandprompter.listener; 26 | 27 | import com.cyr1en.commandprompter.prompt.PromptContext; 28 | import com.cyr1en.commandprompter.prompt.PromptManager; 29 | import org.bukkit.event.EventHandler; 30 | import org.bukkit.event.EventPriority; 31 | import org.bukkit.event.player.PlayerCommandPreprocessEvent; 32 | 33 | public class VanillaListener extends CommandListener { 34 | 35 | public VanillaListener(PromptManager manager) { 36 | super(manager); 37 | } 38 | 39 | @SuppressWarnings("unused") 40 | @EventHandler(priority = EventPriority.LOWEST) 41 | public void onCommand(PlayerCommandPreprocessEvent event) { 42 | var content = event.getMessage().replaceFirst("/", ""); 43 | var context = new PromptContext.Builder() 44 | .setCancellable(event) 45 | .setSender(event.getPlayer()) 46 | .setContent(content) 47 | .build(); 48 | this.process(context); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/prompt/ContextProcessor.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.prompt; 2 | 3 | import com.cyr1en.commandprompter.CommandPrompter; 4 | import com.cyr1en.commandprompter.commands.MainCommand.Cancel; 5 | import org.bukkit.entity.Player; 6 | import org.fusesource.jansi.Ansi; 7 | 8 | import java.util.concurrent.atomic.AtomicBoolean; 9 | 10 | public class ContextProcessor { 11 | 12 | private final CommandPrompter plugin; 13 | private final PromptManager promptManager; 14 | 15 | public ContextProcessor(CommandPrompter plugin, PromptManager promptManager) { 16 | this.plugin = plugin; 17 | this.promptManager = promptManager; 18 | } 19 | 20 | protected void process(PromptContext context) { 21 | // Sanity Checks 22 | plugin.getPluginLogger().debug("Command: " + context.getContent()); 23 | plugin.getPluginLogger().debug("Command Caught using: %s", this.getClass().getSimpleName()); 24 | 25 | if (isIgnored(context)) { 26 | plugin.getPluginLogger().debug("Caught command is ignored."); 27 | plugin.getPluginLogger().info(new Ansi().fgGreen().a(context.getContent()).reset() + " is configured to be ignored."); 28 | return; 29 | } 30 | 31 | // Check if the command is CommandPrompter's cancel command 32 | if (context.getContent().matches(Cancel.commandPattern.toString())) 33 | return; 34 | 35 | if (!context.getSender().hasPermission("commandprompter.use") && 36 | plugin.getConfiguration().enablePermission()) { 37 | plugin.getMessenger().sendMessage(context.getSender(), 38 | plugin.getI18N().getProperty("PromptNoPerm")); 39 | return; 40 | } 41 | if (shouldBlock(context)) { 42 | plugin.getMessenger().sendMessage(context.getSender(), 43 | plugin.getI18N().getFormattedProperty("PromptInProgress", 44 | plugin.getConfiguration().cancelKeyword())); 45 | if (context.getCancellable() != null) 46 | context.getCancellable().setCancelled(true); 47 | return; 48 | } 49 | 50 | if (!promptManager.getParser().isParsable(context)) return; 51 | if (!(context.getSender() instanceof Player)) { 52 | plugin.getMessenger().sendMessage(context.getSender(), 53 | plugin.getI18N().getProperty("PromptPlayerOnly")); 54 | return; 55 | } 56 | 57 | if (context.getCancellable() != null) 58 | context.getCancellable().setCancelled(true); 59 | 60 | plugin.getPluginLogger().debug("Ctx Before Parse: " + context); 61 | promptManager.parse(context); 62 | promptManager.sendPrompt(context.getSender()); 63 | } 64 | 65 | private boolean shouldBlock(PromptContext context) { 66 | var fulfilling = promptManager.getPromptRegistry().inCommandProcess(context.getSender()); 67 | var cmd = extractCommand(context.getContent()); 68 | var cmds = plugin.getConfiguration().allowedWhileInPrompt(); 69 | return fulfilling && (!cmds.contains(cmd) && promptManager.getParser().isParsable(context)); 70 | } 71 | 72 | private boolean isIgnored(PromptContext context) { 73 | var cmd = extractCommand(context.getContent()); 74 | return plugin.getConfiguration().ignoredCommands().contains(cmd); 75 | } 76 | 77 | private String extractCommand(String content) { 78 | var end = content.indexOf(" "); 79 | end = end == -1 ? content.length() : end; 80 | return content.substring(0, end); 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/prompt/PromptContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Ethan Bacurio 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.cyr1en.commandprompter.prompt; 26 | 27 | import org.bukkit.command.CommandSender; 28 | import org.bukkit.entity.Player; 29 | import org.bukkit.event.Cancellable; 30 | import org.bukkit.event.player.PlayerCommandPreprocessEvent; 31 | 32 | import javax.annotation.Nullable; 33 | import java.util.Objects; 34 | 35 | /** 36 | * A helper class that holds context for the prompt system. 37 | * 38 | *

39 | * We can think of it as a data container for various functions throughout the 40 | * prompt system. 41 | */ 42 | public class PromptContext { 43 | private final Cancellable cancellable; 44 | private final CommandSender sender; 45 | private String content; 46 | private String promptKey; 47 | private boolean isConsoleDelegate; 48 | 49 | private final String paKey; 50 | 51 | public PromptContext(PlayerCommandPreprocessEvent e) { 52 | this(e, e.getPlayer(), e.getMessage(), null, null, false); 53 | } 54 | 55 | public PromptContext(@Nullable Cancellable callable, 56 | Player sender, 57 | String content, 58 | @Nullable String promptKey, 59 | @Nullable String paKey, 60 | boolean isConsoleDelegate) { 61 | this.cancellable = callable; 62 | this.sender = sender; 63 | this.content = content; 64 | this.promptKey = promptKey; 65 | this.paKey = paKey; 66 | this.isConsoleDelegate = isConsoleDelegate; 67 | } 68 | 69 | public CommandSender getSender() { 70 | return sender; 71 | } 72 | 73 | public Cancellable getCancellable() { 74 | return cancellable; 75 | } 76 | 77 | public String getContent() { 78 | return content; 79 | } 80 | 81 | public String getPromptKey() { 82 | return promptKey; 83 | } 84 | 85 | public void setContent(String content) { 86 | this.content = content; 87 | } 88 | 89 | public void setPromptKey(String promptKey) { 90 | this.promptKey = promptKey; 91 | } 92 | 93 | public void setIsConsoleDelegate(boolean b) { 94 | this.isConsoleDelegate = b; 95 | } 96 | 97 | public boolean isConsoleDelegate() { 98 | return this.isConsoleDelegate; 99 | } 100 | 101 | public String getPaKey() { 102 | return Objects.isNull(paKey) ? "" : paKey; 103 | } 104 | 105 | @Override 106 | public String toString() { 107 | return "PromptContext{" + 108 | "cancellable=" + cancellable + 109 | ", sender=" + sender + 110 | ", content='" + content + '\'' + 111 | ", promptKey='" + promptKey + '\'' + 112 | ", paKey='" + paKey + '\'' + 113 | ", isConsoleDelegate=" + isConsoleDelegate + 114 | '}'; 115 | } 116 | 117 | // Builder for PromptContext 118 | public static class Builder { 119 | private Cancellable cancellable; 120 | private CommandSender sender; 121 | private String content; 122 | 123 | private String promptKey; 124 | 125 | private String paKey; 126 | private boolean isConsoleDelegate; 127 | 128 | public Builder() { 129 | this.cancellable = null; 130 | this.sender = null; 131 | this.content = null; 132 | this.promptKey = null; 133 | this.paKey = null; 134 | this.isConsoleDelegate = false; 135 | } 136 | 137 | public Builder setCancellable(Cancellable cancellable) { 138 | this.cancellable = cancellable; 139 | return this; 140 | } 141 | 142 | public Builder setSender(CommandSender sender) { 143 | this.sender = sender; 144 | return this; 145 | } 146 | 147 | public Builder setContent(String content) { 148 | this.content = content; 149 | return this; 150 | } 151 | 152 | public Builder setConsoleDelegate(boolean isConsoleDelegate) { 153 | this.isConsoleDelegate = isConsoleDelegate; 154 | return this; 155 | } 156 | 157 | public Builder setPromptKey(String promptKey) { 158 | this.promptKey = promptKey; 159 | return this; 160 | } 161 | 162 | public Builder setPaKey(String paKey) { 163 | this.paKey = paKey; 164 | return this; 165 | } 166 | 167 | public PromptContext build() { 168 | // check if sender and content is null. 169 | if (sender == null || content == null) 170 | throw new IllegalStateException("Sender and content must not be null!"); 171 | return new PromptContext(cancellable, (Player) sender, 172 | content, promptKey, paKey, isConsoleDelegate); 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/prompt/PromptQueue.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.prompt; 2 | 3 | import com.cyr1en.commandprompter.CommandPrompter; 4 | import com.cyr1en.commandprompter.PluginLogger; 5 | import com.cyr1en.commandprompter.api.Dispatcher; 6 | import com.cyr1en.commandprompter.api.prompt.Prompt; 7 | import com.cyr1en.commandprompter.util.MMUtil; 8 | import com.cyr1en.kiso.utils.SRegex; 9 | import org.bukkit.entity.Player; 10 | 11 | import java.util.Arrays; 12 | import java.util.LinkedList; 13 | import java.util.List; 14 | import java.util.function.Consumer; 15 | import java.util.regex.Pattern; 16 | 17 | public class PromptQueue extends LinkedList { 18 | 19 | private String command; 20 | private final LinkedList completed; 21 | private final String escapedRegex; 22 | 23 | private final boolean isOp; 24 | private final boolean isConsoleDelegate; 25 | private String permissionAttachmentKey; 26 | 27 | private final List postCommandMetas; 28 | 29 | private final PluginLogger logger; 30 | 31 | public PromptQueue(String command, boolean isOp, boolean isDelegate, 32 | String escapedRegex) { 33 | super(); 34 | this.command = command; 35 | this.escapedRegex = escapedRegex; 36 | this.completed = new LinkedList<>(); 37 | this.isOp = isOp; 38 | this.isConsoleDelegate = isDelegate; 39 | this.permissionAttachmentKey = ""; 40 | this.postCommandMetas = new LinkedList<>(); 41 | logger = CommandPrompter.getInstance().getPluginLogger(); 42 | } 43 | 44 | public void addCompleted(String s) { 45 | completed.add(s); 46 | } 47 | 48 | public boolean isOp() { 49 | return isOp; 50 | } 51 | 52 | public String getPermissionAttachmentKey() { 53 | return permissionAttachmentKey; 54 | } 55 | 56 | public void setPermissionAttachmentKey(String key) { 57 | this.permissionAttachmentKey = key; 58 | } 59 | 60 | public boolean isConsoleDelegate() { 61 | return isConsoleDelegate; 62 | } 63 | 64 | public String getCompleteCommand() { 65 | command = command.formatted(completed); 66 | LinkedList completedClone = new LinkedList<>(this.completed); 67 | 68 | // get all prompts that we have to replace in the command 69 | var sRegex = new SRegex(); 70 | var prompts = sRegex.find(Pattern.compile(escapedRegex), command).getResultsList(); 71 | prompts = MMUtil.filterOutMiniMessageTags(prompts); 72 | 73 | for (String prompt : prompts) { 74 | if (completedClone.isEmpty()) 75 | break; 76 | command = command.replace(prompt, completedClone.pollFirst()); 77 | } 78 | return "/" + command; 79 | } 80 | 81 | public void addPCM(PostCommandMeta pcm) { 82 | postCommandMetas.add(pcm); 83 | } 84 | 85 | public boolean containsPCM() { 86 | return postCommandMetas != null && !postCommandMetas.isEmpty(); 87 | } 88 | 89 | public List getPostCommandMetas() { 90 | return postCommandMetas; 91 | } 92 | 93 | public String getCommand() { 94 | return command; 95 | } 96 | 97 | public void setCommand(String command) { 98 | this.command = command; 99 | } 100 | 101 | public void dispatch(CommandPrompter plugin, Player sender) { 102 | if (isConsoleDelegate()) { 103 | logger.debug("Dispatching as console"); 104 | Dispatcher.dispatchConsole(getCompleteCommand()); 105 | } else if (!permissionAttachmentKey.isBlank()) { 106 | Dispatcher.dispatchWithAttachment(plugin, sender, getCompleteCommand(), 107 | plugin.getConfiguration().permissionAttachmentTicks(), 108 | plugin.getConfiguration().getPermissionAttachment(permissionAttachmentKey)); 109 | } else 110 | Dispatcher.dispatchCommand(plugin, sender, getCompleteCommand()); 111 | 112 | if (!postCommandMetas.isEmpty()) 113 | postCommandMetas.forEach(pcm -> { 114 | if (pcm.isOnCancel()) 115 | return; 116 | 117 | if (pcm.delayTicks() > 0) 118 | plugin.getServer().getScheduler().runTaskLater(plugin, () -> execPCM(pcm, sender), 119 | pcm.delayTicks()); 120 | else 121 | execPCM(pcm, sender); 122 | }); 123 | } 124 | 125 | void execPCM(PostCommandMeta postCommandMeta, Player sender) { 126 | logger.debug("Executing PCM: " + postCommandMeta.command()); 127 | 128 | var completedClone = new LinkedList<>(completed); 129 | var i18N = CommandPrompter.getInstance().getI18N(); 130 | var command = postCommandMeta.makeAsCommand(completedClone, index -> { 131 | var message = i18N.getFormattedProperty("PCMOutOfBounds", index); 132 | CommandPrompter.getInstance().getMessenger().sendMessage(sender, message); 133 | }); 134 | logger.debug("After parse: " + command); 135 | 136 | if (isConsoleDelegate()) { 137 | logger.debug("Dispatching PostCommand as console"); 138 | Dispatcher.dispatchConsole(command); 139 | } else { 140 | logger.debug("Dispatching PostCommand as player"); 141 | Dispatcher.dispatchCommand(CommandPrompter.getInstance(), sender, command); 142 | } 143 | 144 | } 145 | 146 | /** 147 | * @param promptIndex This will hold the index of the prompt answers to be 148 | * injected in this post command. 149 | */ 150 | public record PostCommandMeta(String command, int[] promptIndex, int delayTicks, boolean isOnCancel) { 151 | @Override 152 | public String toString() { 153 | return "PostCommandMeta{" + "command='" + command + '\'' + ", promptIndex=" + Arrays.toString(promptIndex) 154 | + ", delayTicks=" + delayTicks + ", isOnCancel=" + isOnCancel + '}'; 155 | } 156 | 157 | public String makeAsCommand(LinkedList promptAnswers) { 158 | return makeAsCommand(promptAnswers, index -> { 159 | }); 160 | } 161 | 162 | public String makeAsCommand(LinkedList promptAnswers, Consumer onOutOfBounds) { 163 | if (promptAnswers == null || promptAnswers.isEmpty()) 164 | return command; 165 | var command = this.command; 166 | var promptIndex = this.promptIndex; 167 | for (int index : promptIndex) { 168 | if (index >= promptAnswers.size() || index < 0) { 169 | onOutOfBounds.accept(String.valueOf(index)); 170 | continue; 171 | } 172 | command = command.replaceFirst("p:" + index, promptAnswers.get(index)); 173 | } 174 | return command; 175 | } 176 | } 177 | 178 | } 179 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/prompt/PromptRegistry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Ethan Bacurio 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | package com.cyr1en.commandprompter.prompt; 25 | 26 | import com.cyr1en.commandprompter.CommandPrompter; 27 | import com.cyr1en.commandprompter.api.prompt.Prompt; 28 | import org.bukkit.command.CommandSender; 29 | 30 | import java.util.HashMap; 31 | import java.util.Objects; 32 | 33 | /** 34 | * Class that will hold all ongoing prompts. 35 | *

36 | * The data structure of this class is a hash table. The key of this data struct 37 | * is the command 38 | * sender itself and the value is a {@link PromptQueue} 39 | */ 40 | public class PromptRegistry extends HashMap { 41 | 42 | private final CommandPrompter pluginInstance; 43 | 44 | public PromptRegistry(CommandPrompter pluginInstance) { 45 | this.pluginInstance = pluginInstance; 46 | } 47 | 48 | public void initRegistryFor(PromptContext context, String command, String escapedRegex) { 49 | if (containsKey(context.getSender())) 50 | return; 51 | 52 | var queue = new PromptQueue( 53 | command, 54 | context.getSender().isOp(), 55 | context.isConsoleDelegate(), 56 | escapedRegex); 57 | queue.setPermissionAttachmentKey(context.getPaKey()); 58 | 59 | put(context.getSender(), queue); 60 | } 61 | 62 | public void addPrompt(CommandSender sender, Prompt p) { 63 | if (!containsKey(sender)) 64 | return; 65 | get(sender).add(p); 66 | pluginInstance.getPluginLogger().debug("Registered: (%s : %s)", 67 | sender.getName(), p.getClass().getSimpleName()); 68 | } 69 | 70 | public void unregister(CommandSender sender) { 71 | if (!containsKey(sender)) 72 | return; 73 | remove(sender); 74 | pluginInstance.getPluginLogger().debug("Un-Registered: %s", sender.getName()); 75 | } 76 | 77 | public boolean inCommandProcess(CommandSender sender) { 78 | if (!containsKey(sender)) 79 | return false; 80 | if (Objects.isNull(get(sender))) { 81 | remove(sender); 82 | return false; 83 | } 84 | return true; 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/prompt/prompts/AbstractPrompt.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Ethan Bacurio 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.cyr1en.commandprompter.prompt.prompts; 26 | 27 | import com.cyr1en.commandprompter.CommandPrompter; 28 | import com.cyr1en.commandprompter.api.prompt.InputValidator; 29 | import com.cyr1en.commandprompter.api.prompt.Prompt; 30 | import com.cyr1en.commandprompter.prompt.PromptContext; 31 | import com.cyr1en.commandprompter.prompt.PromptManager; 32 | import com.cyr1en.commandprompter.prompt.PromptParser; 33 | import com.cyr1en.commandprompter.prompt.validators.NoopValidator; 34 | import com.cyr1en.commandprompter.util.Util; 35 | 36 | import java.util.List; 37 | 38 | public abstract class AbstractPrompt implements Prompt { 39 | 40 | private final CommandPrompter plugin; 41 | private final PromptContext context; 42 | private final String prompt; 43 | private final PromptManager promptManager; 44 | 45 | private final List args; 46 | 47 | private InputValidator validator; 48 | 49 | private boolean inputSanitation; 50 | 51 | public AbstractPrompt(CommandPrompter plugin, PromptContext context, 52 | String prompt, List args) { 53 | this.plugin = plugin; 54 | this.context = context; 55 | this.prompt = prompt; 56 | this.promptManager = plugin.getPromptManager(); 57 | this.args = args; 58 | this.inputSanitation = true; 59 | this.validator = new NoopValidator(); 60 | } 61 | 62 | protected String stripColor(String msg) { 63 | return Util.stripColor(msg); 64 | } 65 | 66 | protected String color(String msg) { 67 | return Util.color(msg); 68 | } 69 | 70 | @Override 71 | public abstract void sendPrompt(); 72 | 73 | @Override 74 | public PromptContext getContext() { 75 | return context; 76 | } 77 | 78 | @Override 79 | public CommandPrompter getPlugin() { 80 | return plugin; 81 | } 82 | 83 | @Override 84 | public String getPrompt() { 85 | return prompt; 86 | } 87 | 88 | @Override 89 | public PromptManager getPromptManager() { 90 | return promptManager; 91 | } 92 | 93 | @Override 94 | public List getArgs() { 95 | return args; 96 | } 97 | 98 | @Override 99 | public void setInputValidator(InputValidator inputValidator) { 100 | this.validator = inputValidator; 101 | } 102 | 103 | @Override 104 | public InputValidator getInputValidator() { 105 | return this.validator; 106 | } 107 | 108 | @Override 109 | public void setInputSanitization(boolean sanitize) { 110 | this.inputSanitation = sanitize; 111 | } 112 | 113 | @Override 114 | public boolean sanitizeInput() { 115 | return this.inputSanitation; 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/prompt/prompts/AnvilPrompt.java: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Ethan Bacurio 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | package com.cyr1en.commandprompter.prompt.prompts; 26 | 27 | import com.cyr1en.commandprompter.CommandPrompter; 28 | import com.cyr1en.commandprompter.prompt.PromptContext; 29 | import com.cyr1en.commandprompter.prompt.PromptParser; 30 | import com.cyr1en.commandprompter.util.ServerUtil; 31 | import com.cyr1en.commandprompter.util.Util; 32 | import net.wesjd.anvilgui.AnvilGUI; 33 | import org.bukkit.ChatColor; 34 | import org.bukkit.Material; 35 | import org.bukkit.enchantments.Enchantment; 36 | import org.bukkit.entity.Player; 37 | import org.bukkit.inventory.ItemFlag; 38 | import org.bukkit.inventory.ItemStack; 39 | import org.jetbrains.annotations.NotNull; 40 | 41 | import java.util.Arrays; 42 | import java.util.Collections; 43 | import java.util.List; 44 | import java.util.Objects; 45 | import java.util.concurrent.atomic.AtomicBoolean; 46 | 47 | public class AnvilPrompt extends AbstractPrompt { 48 | 49 | public static char BLANK_CHAR = '\u00A0'; // No-Break Space 50 | 51 | public AnvilPrompt(CommandPrompter plugin, PromptContext context, 52 | String prompt, List args) { 53 | super(plugin, context, prompt, args); 54 | } 55 | 56 | @Override 57 | public void sendPrompt() { 58 | List parts = Arrays.asList(getPrompt().split("\\{br}")); 59 | var item = makeAnvilItem(parts); 60 | var resultItem = makeResultItem(parts); 61 | var cancelItem = makeCancelItem(parts); 62 | makeAnvil(parts, item, resultItem, cancelItem).open((Player) getContext().getSender()); 63 | } 64 | 65 | private AnvilGUI.Builder makeAnvil(List parts, ItemStack item, ItemStack resultItem, ItemStack cancelItem) { 66 | var isComplete = new AtomicBoolean(false); 67 | var builder = getBuilder(isComplete); 68 | builder.onClose(p -> { 69 | if (isComplete.get()) 70 | return; 71 | getPromptManager().cancel(p.getPlayer()); 72 | }); 73 | 74 | var promptText = getPlugin().getPromptConfig().promptMessage(); 75 | 76 | var text = (promptText.isBlank()) ? color(parts.get(0)) : promptText.equals("BLANK") ? 77 | String.valueOf(BLANK_CHAR) : color(promptText); 78 | builder.text(text); 79 | 80 | if (getPlugin().getPromptConfig().enableTitle()) { 81 | var title = getPlugin().getPromptConfig().customTitle(); 82 | title = title.isEmpty() ? color(parts.get(0)) : color(title); 83 | builder.title(title); 84 | } 85 | 86 | if (getPlugin().getPromptConfig().enableCancelItem()) 87 | builder.itemRight(cancelItem); 88 | 89 | builder.itemLeft(item); 90 | builder.itemOutput(resultItem); 91 | builder.plugin(getPlugin()); 92 | return builder; 93 | } 94 | 95 | @NotNull 96 | private AnvilGUI.Builder getBuilder(AtomicBoolean isComplete) { 97 | var builder = new AnvilGUI.Builder(); 98 | builder.onClick((slot, stateSnapshot) -> { 99 | var cancelEnabled = getPlugin().getPromptConfig().enableCancelItem(); 100 | if (slot == AnvilGUI.Slot.INPUT_RIGHT && cancelEnabled) { 101 | getPromptManager().cancel(stateSnapshot.getPlayer()); 102 | return Collections.singletonList(AnvilGUI.ResponseAction.close()); 103 | } 104 | 105 | if (slot != AnvilGUI.Slot.OUTPUT) 106 | return Collections.emptyList(); 107 | 108 | var message = ChatColor.stripColor( 109 | ChatColor.translateAlternateColorCodes('&', stateSnapshot.getText())); 110 | var cancelKeyword = getPlugin().getConfiguration().cancelKeyword(); 111 | if (cancelKeyword.equalsIgnoreCase(message)) { 112 | getPromptManager().cancel(stateSnapshot.getPlayer()); 113 | return Collections.singletonList(AnvilGUI.ResponseAction.close()); 114 | } 115 | 116 | isComplete.getAndSet(true); 117 | var content = stateSnapshot.getText().replaceAll(String.valueOf(BLANK_CHAR), ""); 118 | var ctx = new PromptContext.Builder() 119 | .setSender(stateSnapshot.getPlayer()) 120 | .setContent(content).build(); 121 | 122 | getPromptManager().processPrompt(ctx); 123 | return Collections.singletonList(AnvilGUI.ResponseAction.close()); 124 | }); 125 | return builder; 126 | } 127 | 128 | private ItemStack makeItem(String prefix, List parts, String customTitle) { 129 | var config = getPlugin().getPromptConfig().rawConfig(); 130 | var material = config.getString(prefix + ".Material", "PAPER"); 131 | var enchanted = config.getBoolean(prefix + ".Enchanted", false); 132 | var customModelData = config.getInt(prefix + ".Custom-Model-Data", 0); 133 | var hideTooltips = config.getBoolean(prefix + ".HideTooltips", false); 134 | 135 | var item = new ItemStack(Util.getCheckedMaterial(material, Material.PAPER)); 136 | var meta = item.getItemMeta(); 137 | getPlugin().getPluginLogger().debug("ItemMeta: " + meta); 138 | 139 | if (enchanted) { 140 | Objects.requireNonNull(meta).addEnchant(Enchantment.LURE, 1, true); 141 | meta.addItemFlags(ItemFlag.HIDE_ENCHANTS); 142 | } 143 | 144 | Objects.requireNonNull(meta).addItemFlags(ItemFlag.HIDE_ATTRIBUTES); 145 | if (customTitle != null) 146 | meta.setDisplayName(customTitle); 147 | else { 148 | meta.setDisplayName(parts.get(0)); 149 | 150 | meta.setDisplayName(parts.get(0)); 151 | 152 | if (parts.size() > 1) 153 | meta.setLore(parts.subList(1, parts.size()).stream().map(this::color).toList()); 154 | 155 | if (customModelData != 0) 156 | meta.setCustomModelData(customModelData); 157 | 158 | if (ServerUtil.isAtOrAbove("1.21.2")) { 159 | meta.setHideTooltip(hideTooltips); 160 | } 161 | } 162 | 163 | item.setItemMeta(meta); 164 | return item; 165 | } 166 | 167 | private ItemStack makeItem(String prefix, List parts) { 168 | return makeItem(prefix, parts, null); 169 | } 170 | 171 | private ItemStack makeAnvilItem(List parts) { 172 | return makeItem("AnvilGUI.Item", parts); 173 | } 174 | 175 | private ItemStack makeResultItem(List parts) { 176 | return makeItem("AnvilGUI.ResultItem", parts); 177 | } 178 | 179 | private ItemStack makeCancelItem(List parts) { 180 | var cancelText = getPlugin().getPromptConfig().cancelItemHoverText(); 181 | return makeItem("AnvilGUI.CancelItem", parts, Util.color(cancelText)); 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/prompt/prompts/SignPrompt.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.prompt.prompts; 2 | 3 | import com.cyr1en.commandprompter.CommandPrompter; 4 | import com.cyr1en.commandprompter.prompt.PromptContext; 5 | import com.cyr1en.commandprompter.prompt.PromptParser; 6 | import com.cyr1en.commandprompter.util.Util; 7 | import com.cyr1en.kiso.utils.FastStrings; 8 | import de.rapha149.signgui.*; 9 | import de.rapha149.signgui.exception.SignGUIVersionException; 10 | import org.bukkit.Material; 11 | import org.bukkit.entity.Player; 12 | 13 | import java.util.Arrays; 14 | import java.util.Collections; 15 | import java.util.List; 16 | 17 | public class SignPrompt extends AbstractPrompt { 18 | 19 | private static final String MULTI_ARG_PATTERN_EMPTY = "[\\S+]+:"; 20 | private static final String MULTI_ARG_PATTERN_FILLED = MULTI_ARG_PATTERN_EMPTY + "\\s?+[\\S+]+"; 21 | 22 | private boolean isMultiArg; 23 | 24 | public SignPrompt(CommandPrompter plugin, PromptContext context, String prompt, 25 | List args) { 26 | super(plugin, context, prompt, args); 27 | isMultiArg = false; 28 | } 29 | 30 | @Override 31 | public void sendPrompt() { 32 | List parts = Arrays.asList(getPrompt().split("\\{br}")); 33 | checkMultiArg(parts); 34 | getPlugin().getPluginLogger().debug("Is Multi-Arg: " + isMultiArg); 35 | if (parts.size() > 3 && !isMultiArg) 36 | parts = parts.subList(0, 3); 37 | else if (parts.size() > 4) 38 | parts = parts.subList(0, 4); 39 | 40 | List finalParts = parts; 41 | 42 | var matStr = getPlugin().getPromptConfig().signMaterial(); 43 | var mat = Util.getCheckedMaterial(matStr, Material.OAK_SIGN); 44 | getPlugin().getPluginLogger().debug("Material: " + mat.name()); 45 | 46 | try { 47 | var gui = SignGUI.builder() 48 | .setLines(finalParts.toArray(String[]::new)) 49 | .setType(mat) 50 | .setHandler((p, r) -> process(finalParts, p, r.getLines())) 51 | .build(); 52 | 53 | gui.open((Player) getContext().getSender()); 54 | } catch (SignGUIVersionException e) { 55 | getPlugin().getPluginLogger().err("SignGUI version exception: " + e.getMessage()); 56 | getPromptManager().cancel(getContext().getSender()); 57 | } 58 | } 59 | 60 | private void checkMultiArg(List parts) { 61 | isMultiArg = parts.stream().map(String::trim).anyMatch(s -> s.matches(MULTI_ARG_PATTERN_EMPTY)); 62 | } 63 | 64 | private List process(List parts, Player p, String[] s) { 65 | var cleanedParts = parts.stream().map(this::stripColor).toList(); 66 | getPlugin().getPluginLogger().debug("Sign Strings: " + Arrays.toString(s)); 67 | 68 | var response = isMultiArg 69 | ? FastStrings.join(Arrays.stream(s).filter(str -> !str.isBlank() && !cleanedParts.contains(str)) 70 | .filter(str -> str.matches(MULTI_ARG_PATTERN_FILLED)) 71 | .map(str -> str.replaceAll(MULTI_ARG_PATTERN_EMPTY, "").trim()).toArray(), " ") 72 | : FastStrings.join(Arrays.stream(s) 73 | .filter(str -> !cleanedParts.contains(str) && !str.isBlank()).toArray(), " "); 74 | 75 | getPlugin().getPluginLogger().debug("Response: " + response); 76 | 77 | // If the sign contains the same message as the prompt 78 | // we'll consider the command completion cancelled. 79 | if (response.isBlank()) { 80 | getPromptManager().cancel(p); 81 | return Collections.emptyList(); 82 | } 83 | 84 | var cancelKeyword = getPlugin().getConfiguration().cancelKeyword(); 85 | if (cancelKeyword.equalsIgnoreCase(response)) { 86 | getPromptManager().cancel(p); 87 | return Collections.emptyList(); 88 | } 89 | var ctx = new PromptContext.Builder() 90 | .setSender(p) 91 | .setContent(response).build(); 92 | 93 | getPromptManager().processPrompt(ctx); 94 | 95 | return Collections.emptyList(); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/prompt/ui/CacheFilter.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.prompt.ui; 2 | 3 | import com.cyr1en.commandprompter.CommandPrompter; 4 | import com.cyr1en.commandprompter.PluginLogger; 5 | import com.cyr1en.commandprompter.config.PromptConfig; 6 | import com.cyr1en.commandprompter.prompt.PromptParser; 7 | import org.bukkit.Bukkit; 8 | import org.bukkit.OfflinePlayer; 9 | import org.bukkit.entity.Player; 10 | 11 | import java.util.List; 12 | import java.util.Objects; 13 | import java.util.regex.Pattern; 14 | 15 | /** 16 | * Class that would filter players based on a regex key. 17 | *

18 | * For example, if the regex key is 'w', then it would filter all players 19 | * that are in the same world as the relative player. 20 | */ 21 | public abstract class CacheFilter { 22 | 23 | 24 | /** 25 | * The regex key that would be used for parsing 26 | */ 27 | private final Pattern regexKey; 28 | 29 | /** 30 | * The config key for this cache filter's format 31 | */ 32 | private final String configKey; 33 | 34 | private final int capGroupOffset; 35 | 36 | /** 37 | * Construct a new cache filter. 38 | * 39 | * @param regexKey the regex key 40 | */ 41 | public CacheFilter(Pattern regexKey, String configKey) { 42 | this(regexKey, configKey, 0); 43 | } 44 | 45 | public CacheFilter(Pattern regexKey, String configKey, int capGroupOffset) { 46 | this.regexKey = regexKey; 47 | this.configKey = configKey; 48 | this.capGroupOffset = capGroupOffset; 49 | } 50 | 51 | /** 52 | * Get the regex key. 53 | * 54 | * @return the regex key 55 | */ 56 | public Pattern getRegexKey() { 57 | return regexKey; 58 | } 59 | 60 | /** 61 | * Get the config key. 62 | * 63 | * @return the config key 64 | */ 65 | public String getConfigKey() { 66 | return configKey; 67 | } 68 | 69 | /** 70 | * Get the format for this cache filter. 71 | * 72 | * @param config the prompt config 73 | * @return the format 74 | */ 75 | public String getFormat(PromptConfig config) { 76 | return config.getFilterFormat(this); 77 | 78 | } 79 | 80 | /** 81 | * Get the capture group offset. 82 | * 83 | * @return the capture group offset 84 | */ 85 | public int getCapGroupOffset() { 86 | return capGroupOffset; 87 | } 88 | 89 | @Override 90 | public String toString() { 91 | return this.getClass().getSimpleName(); 92 | } 93 | 94 | /** 95 | * A method that allows you to reconstruct a subclass of {@link CacheFilter} 96 | * based on the prompt key. 97 | * 98 | *

99 | * In cases where the prompt key contains additional information, this method 100 | * could be used to reconstruct a certain subclass of {@link CacheFilter}. 101 | * 102 | *

103 | * Additional filter information can be parsed from the {@link PromptParser} 104 | * but for better readability, it is recommended to use this method instead. 105 | * 106 | * @param promptKey the prompt key 107 | * @return the cloned cache filter 108 | */ 109 | public abstract CacheFilter reConstruct(String promptKey); 110 | 111 | /** 112 | * Abstract method that would filter player based on subclass implementation. 113 | * 114 | *

115 | * This method would be called by {@link HeadCache} when it needs to filter 116 | * players. 117 | * 118 | * @param relativePlayer the players to filter 119 | * @return the filtered players 120 | */ 121 | public abstract List filter(Player relativePlayer); 122 | 123 | 124 | /** 125 | * Filter players based on the world of the relative player. 126 | *

127 | * This is a special case of {@link CacheFilter} where the regex key is 'w'. 128 | * This would filter all players that are in the same world as the relative player. 129 | */ 130 | public static class WorldFilter extends CacheFilter { 131 | 132 | /** 133 | * Construct a new world filter. 134 | */ 135 | public WorldFilter() { 136 | super(Pattern.compile("w"), "PlayerUI.Filter-Format.World"); 137 | } 138 | 139 | @Override 140 | public List filter(Player relativePlayer) { 141 | return relativePlayer.getWorld().getPlayers(); 142 | } 143 | 144 | public CacheFilter reConstruct(String promptKey) { 145 | return new WorldFilter(); 146 | } 147 | } 148 | 149 | /** 150 | * Filter players based on the radius of the relative player. 151 | *

152 | * This is a special case of {@link CacheFilter} where the regex key is 'r'. 153 | * This would filter all players that are within the radius of the relative player. 154 | */ 155 | public static class RadialFilter extends CacheFilter { 156 | 157 | /** 158 | * The radius to check relative to the relative player 159 | */ 160 | private final int radius; 161 | 162 | /** 163 | * Construct a new radius filter. 164 | * 165 | * @param radius the radius 166 | */ 167 | public RadialFilter(int radius) { 168 | super(Pattern.compile("r(\\d+)"), "PlayerUI.Filter-Format.Radial", 1); 169 | this.radius = radius; 170 | } 171 | 172 | /** 173 | * Construct a new radius filter with a radius of 0. 174 | */ 175 | public RadialFilter() { 176 | this(0); 177 | } 178 | 179 | public CacheFilter reConstruct(String promptKey) { 180 | var matcher = this.getRegexKey().matcher(promptKey); 181 | var found = matcher.find(); 182 | //print all groups 183 | for (int i = 0; i <= matcher.groupCount(); i++) { 184 | CommandPrompter.getInstance().getPluginLogger().debug("Group %d: %s", i, matcher.group(i)); 185 | } 186 | var radius = found ? Integer.parseInt(matcher.group(1)) : 0; 187 | return new RadialFilter(radius); 188 | } 189 | 190 | @Override 191 | public List filter(Player relativePlayer) { 192 | return relativePlayer.getWorld().getPlayers().stream() 193 | .filter(p -> p.getLocation().distance(relativePlayer.getLocation()) <= radius) 194 | .toList(); 195 | } 196 | } 197 | 198 | public static class SelfFilter extends CacheFilter { 199 | 200 | public SelfFilter() { 201 | super(Pattern.compile("s"), "PlayerUI.Filter-Format.Self"); 202 | } 203 | 204 | @Override 205 | public List filter(Player relativePlayer) { 206 | return Bukkit.getOnlinePlayers().stream() 207 | .map(OfflinePlayer::getPlayer) 208 | .filter(Objects::nonNull) 209 | .filter(p -> !p.equals(relativePlayer)) 210 | .toList(); 211 | } 212 | 213 | public CacheFilter reConstruct(String promptKey) { 214 | return new SelfFilter(); 215 | } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/prompt/ui/inventory/ControlPane.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.prompt.ui.inventory; 2 | 3 | import com.cyr1en.commandprompter.CommandPrompter; 4 | import com.cyr1en.commandprompter.prompt.PromptContext; 5 | import com.cyr1en.commandprompter.prompt.prompts.PlayerUIPrompt; 6 | import com.cyr1en.commandprompter.util.ServerUtil; 7 | import com.cyr1en.commandprompter.util.Util; 8 | import com.github.stefvanschie.inventoryframework.gui.GuiItem; 9 | import com.github.stefvanschie.inventoryframework.gui.type.ChestGui; 10 | import com.github.stefvanschie.inventoryframework.pane.PaginatedPane; 11 | import com.github.stefvanschie.inventoryframework.pane.StaticPane; 12 | import net.wesjd.anvilgui.AnvilGUI; 13 | import org.bukkit.Material; 14 | import org.bukkit.entity.Player; 15 | import org.bukkit.event.inventory.InventoryClickEvent; 16 | import org.bukkit.inventory.ItemStack; 17 | 18 | import java.util.Collections; 19 | import java.util.Objects; 20 | import java.util.function.Consumer; 21 | 22 | public class ControlPane extends StaticPane { 23 | private static final int DEFAULT_PREV_LOC = 2; 24 | private static final int DEFAULT_NEXT_LOC = 6; 25 | private static final int DEFAULT_CANCEL_LOC = 4; 26 | private static final int DEFAULT_SEARCH_LOC = 8; 27 | 28 | private final CommandPrompter plugin; 29 | private final PaginatedPane paginatedPane; 30 | private final ChestGui gui; 31 | private final PromptContext ctx; 32 | private final PlayerUIPrompt playerUIPrompt; 33 | 34 | private int prevLoc; 35 | private int nextLoc; 36 | private int cancelLoc; 37 | private int searchLoc; 38 | 39 | public ControlPane(CommandPrompter plugin, PaginatedPane pane, ChestGui gui, PromptContext ctx, int numCols, PlayerUIPrompt playerUIPrompt) { 40 | super(0, numCols - 1, 9, 1); 41 | this.plugin = plugin; 42 | this.playerUIPrompt = playerUIPrompt; 43 | prevLoc = plugin.getPromptConfig().previousColumn() - 1; 44 | nextLoc = plugin.getPromptConfig().nextColumn() - 1; 45 | cancelLoc = plugin.getPromptConfig().cancelColumn() - 1; 46 | searchLoc = plugin.getPromptConfig().searchColumn() - 1; 47 | 48 | this.paginatedPane = pane; 49 | this.ctx = ctx; 50 | this.gui = gui; 51 | verifyLocs(); 52 | setupButtons(); 53 | } 54 | 55 | private void verifyLocs() { 56 | if (prevLoc == nextLoc || prevLoc == cancelLoc || nextLoc == cancelLoc) { 57 | this.prevLoc = DEFAULT_PREV_LOC; 58 | this.nextLoc = DEFAULT_NEXT_LOC; 59 | this.cancelLoc = DEFAULT_CANCEL_LOC; 60 | this.searchLoc = DEFAULT_SEARCH_LOC; 61 | } 62 | } 63 | 64 | private void updatePage(InventoryClickEvent event, int nextPage) { 65 | event.setCancelled(true); 66 | try { 67 | paginatedPane.setPage(nextPage); 68 | gui.update(); 69 | } catch (IndexOutOfBoundsException ignore) { 70 | plugin.getPluginLogger().debug("Could not update page."); 71 | } 72 | } 73 | 74 | private void setupButtons() { 75 | var pages = paginatedPane.getPages() - 1; 76 | 77 | var prevMatString = plugin.getPromptConfig().previousItem(); 78 | var prevCMD = plugin.getPromptConfig().previousCustomModelData(); 79 | var prevIS = new ItemStack(Util.getCheckedMaterial(prevMatString, Material.FEATHER)); 80 | addItem(plugin.getPromptConfig().previousText(), prevIS, prevLoc, prevCMD, 81 | c -> updatePage(c, Math.max((paginatedPane.getPage() - 1), 0))); 82 | 83 | var nextMatString = plugin.getPromptConfig().nextItem(); 84 | var nextCMD = plugin.getPromptConfig().nextCustomModelData(); 85 | var nextIS = new ItemStack(Util.getCheckedMaterial(nextMatString, Material.FEATHER)); 86 | addItem(plugin.getPromptConfig().nextText(), nextIS, nextLoc, nextCMD, 87 | c -> updatePage(c, Math.min((paginatedPane.getPage() + 1), pages))); 88 | 89 | var cancelMatString = plugin.getPromptConfig().cancelItem(); 90 | var cancelCMD = plugin.getPromptConfig().cancelCustomModelData(); 91 | var cancelIS = new ItemStack(Util.getCheckedMaterial(cancelMatString, Material.FEATHER)); 92 | addItem(plugin.getPromptConfig().cancelText(), cancelIS, cancelLoc, cancelCMD, 93 | c -> { 94 | c.setCancelled(true); 95 | plugin.getPromptManager().cancel(ctx.getSender()); 96 | ((Player) ctx.getSender()).closeInventory(); 97 | }); 98 | 99 | var searchMatString = plugin.getPromptConfig().searchItem(); 100 | var searchCMD = plugin.getPromptConfig().searchCustomModelData(); 101 | var searchIS = new ItemStack(Util.getCheckedMaterial(searchMatString, Material.NAME_TAG)); 102 | addItem(plugin.getPromptConfig().searchText(), searchIS, searchLoc, searchCMD, this::search); 103 | } 104 | 105 | private void search(InventoryClickEvent e) { 106 | playerUIPrompt.setSearching(true); 107 | var builder = new AnvilGUI.Builder(); 108 | builder.onClick((slot, stateSnapshot) -> { 109 | if (slot != AnvilGUI.Slot.OUTPUT) { 110 | return Collections.emptyList(); 111 | } 112 | 113 | var paginatedPane = gui.getPanes().stream().filter(pane -> pane instanceof PaginatedPane) 114 | .map(pane -> (PaginatedPane) pane).findFirst().orElse(null); 115 | if (paginatedPane == null) { 116 | plugin.getPluginLogger().debug("PaginatedPane not found."); 117 | return Collections.singletonList(AnvilGUI.ResponseAction.close()); 118 | } 119 | 120 | var input = Util.stripColor(stateSnapshot.getText()); 121 | plugin.getPluginLogger().debug("Search: " + input); 122 | 123 | var heads = paginatedPane.getItems().stream().map(GuiItem::getItem).filter(item -> { 124 | var meta = item.getItemMeta(); 125 | if (meta == null) return false; 126 | var displayName = Util.stripColor(meta.getDisplayName()); 127 | return displayName.toLowerCase().contains(input.toLowerCase()); 128 | }).toList(); 129 | paginatedPane.clear(); 130 | paginatedPane.populateWithItemStacks(heads); 131 | gui.show(e.getWhoClicked()); 132 | return Collections.singletonList(AnvilGUI.ResponseAction.close()); 133 | }); 134 | 135 | builder.title(Util.color(plugin.getPromptConfig().searchAnvilItemTitle())); 136 | builder.plugin(plugin); 137 | builder.itemLeft(getSearchLeftItem()); 138 | builder.onClose(c -> playerUIPrompt.setSearching(false)); 139 | builder.open((Player) e.getWhoClicked()); 140 | } 141 | 142 | private ItemStack getSearchLeftItem() { 143 | var item = new ItemStack(Material.PAPER); 144 | var itemMeta = item.getItemMeta(); 145 | if (itemMeta == null) { 146 | plugin.getPluginLogger().debug("ItemMeta is null."); 147 | return item; 148 | } 149 | var configText = plugin.getPromptConfig().searchAnvilItemText(); 150 | itemMeta.setDisplayName(Util.color(configText)); 151 | 152 | itemMeta.setCustomModelData(plugin.getPromptConfig().searchAnvilItemCustomModelData()); 153 | 154 | if (ServerUtil.isAtOrAbove("1.21.2")) 155 | itemMeta.setHideTooltip(true); 156 | 157 | item.setItemMeta(itemMeta); 158 | return item; 159 | } 160 | 161 | private void addItem(String name, ItemStack itemStack, int x, int customModelData, 162 | Consumer consumer) { 163 | var itemMeta = itemStack.getItemMeta(); 164 | Objects.requireNonNull(itemMeta).setDisplayName(Util.color(name)); 165 | itemMeta.setCustomModelData(customModelData == 0 ? null : customModelData); 166 | itemStack.setItemMeta(itemMeta); 167 | addItem(new GuiItem(itemStack, consumer), x, 0); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/prompt/validators/CompoundedValidator.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.prompt.validators; 2 | 3 | import com.cyr1en.commandprompter.api.prompt.CompoundableValidator; 4 | import com.cyr1en.commandprompter.api.prompt.InputValidator; 5 | import org.bukkit.entity.Player; 6 | 7 | import java.util.stream.Stream; 8 | 9 | public class CompoundedValidator implements InputValidator { 10 | 11 | private final String alias; 12 | private final String messageOnFail; 13 | private final Player inputPlayer; 14 | private final InputValidator[] validators; 15 | 16 | 17 | public CompoundedValidator(String alias, String messageOnFail, Player inputPlayer, InputValidator... validators) { 18 | this.alias = alias; 19 | this.messageOnFail = messageOnFail; 20 | this.inputPlayer = inputPlayer; 21 | this.validators = validators; 22 | } 23 | 24 | @Override 25 | public boolean validate(String input) { 26 | if (input == null || input.isBlank()) 27 | return false; 28 | 29 | var andValidators = getValidatorsWithType(CompoundableValidator.Type.AND); 30 | var orValidators = getValidatorsWithType(CompoundableValidator.Type.OR); 31 | 32 | return andValidators.allMatch(validator -> validator.validate(input)) 33 | || orValidators.anyMatch(validator -> validator.validate(input)); 34 | } 35 | 36 | private Stream getValidatorsWithType(CompoundableValidator.Type type) { 37 | return Stream.of(validators) 38 | .filter(validator -> validator instanceof CompoundableValidator) 39 | .filter(validator -> ((CompoundableValidator) validator).getType() == type); 40 | } 41 | 42 | @Override 43 | public String alias() { 44 | return this.alias; 45 | } 46 | 47 | @Override 48 | public String messageOnFail() { 49 | return this.messageOnFail; 50 | } 51 | 52 | @Override 53 | public Player inputPlayer() { 54 | return this.inputPlayer; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/prompt/validators/JSExprValidator.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.prompt.validators; 2 | 3 | import com.cyr1en.commandprompter.CommandPrompter; 4 | import com.cyr1en.commandprompter.PluginLogger; 5 | import com.cyr1en.commandprompter.api.prompt.CompoundableValidator; 6 | import com.cyr1en.commandprompter.api.prompt.InputValidator; 7 | import com.cyr1en.commandprompter.hook.hooks.PapiHook; 8 | import org.bukkit.Bukkit; 9 | import org.bukkit.entity.Player; 10 | import org.bukkit.plugin.RegisteredServiceProvider; 11 | import org.bukkit.plugin.ServicePriority; 12 | import org.openjdk.nashorn.api.scripting.NashornScriptEngineFactory; 13 | 14 | import javax.script.ScriptEngineManager; 15 | 16 | public class JSExprValidator implements InputValidator, CompoundableValidator { 17 | 18 | public static final CompoundableValidator.Type DEFAULT_TYPE = Type.AND; 19 | private final String alias; 20 | private final String expression; 21 | private final String messageOnFail; 22 | private final Player inputPlayer; 23 | private final PluginLogger logger; 24 | private ScriptEngineManager engine; 25 | 26 | private CompoundableValidator.Type type = DEFAULT_TYPE; 27 | 28 | public JSExprValidator(String alias, String expression, String messageOnFail, Player inputPlayer) { 29 | this.alias = alias; 30 | this.expression = expression; 31 | this.messageOnFail = messageOnFail; 32 | this.inputPlayer = inputPlayer; 33 | this.logger = CommandPrompter.getInstance().getPluginLogger(); 34 | initEngine(); 35 | } 36 | 37 | private void initEngine() { 38 | var manager = Bukkit.getServer().getServicesManager(); 39 | var factory = new NashornScriptEngineFactory(); 40 | if (engine == null) { 41 | if (manager.isProvidedFor(ScriptEngineManager.class)) { 42 | final RegisteredServiceProvider provider = manager.getRegistration(ScriptEngineManager.class); 43 | if (provider == null) { 44 | logger.debug("ScriptEngineManager provider is null"); 45 | return; 46 | } 47 | engine = provider.getProvider(); 48 | } else { 49 | engine = new ScriptEngineManager(); 50 | manager.register(ScriptEngineManager.class, engine, CommandPrompter.getInstance(), ServicePriority.Highest); 51 | } 52 | engine.registerEngineName("JavaScript", factory); 53 | engine.put("BukkitServer", Bukkit.getServer()); 54 | } 55 | } 56 | 57 | @Override 58 | public boolean validate(String input) { 59 | var exprStr = expression.replace("%prompt_input%", input); 60 | 61 | var hook = CommandPrompter.getInstance().getHookContainer().getHook(PapiHook.class); 62 | if (hook.isHooked()) 63 | exprStr = hook.get().setPlaceholder(inputPlayer, exprStr); 64 | 65 | logger.debug("JS expression: " + exprStr); 66 | if (exprStr.isBlank()) { 67 | logger.debug("JS expression is blank"); 68 | return false; 69 | } 70 | 71 | return evaluate(exprStr); 72 | } 73 | 74 | private boolean evaluate(String exprStr) { 75 | try { 76 | engine.put("BukkitPlayer", inputPlayer); 77 | logger.debug("Evaluating JS expression: " + exprStr); 78 | var result = engine.getEngineByName("JavaScript").eval(exprStr); 79 | if (result instanceof Boolean) { 80 | return (Boolean) result; 81 | } else { 82 | logger.debug("JS expression did not return a boolean"); 83 | return false; 84 | } 85 | } catch (Exception e) { 86 | logger.debug("JS expression failed to evaluate: " + e.getMessage()); 87 | return false; 88 | } 89 | } 90 | 91 | @Override 92 | public String alias() { 93 | return alias; 94 | } 95 | 96 | @Override 97 | public String messageOnFail() { 98 | return messageOnFail; 99 | } 100 | 101 | @Override 102 | public Player inputPlayer() { 103 | return inputPlayer; 104 | } 105 | 106 | @Override 107 | public Type getType() { 108 | return type; 109 | } 110 | 111 | @Override 112 | public void setType(Type type) { 113 | this.type = type; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/prompt/validators/NoopValidator.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.prompt.validators; 2 | 3 | import com.cyr1en.commandprompter.api.prompt.InputValidator; 4 | import org.bukkit.entity.Player; 5 | 6 | /** 7 | * A validator that does nothing. 8 | *

9 | * This will always return true. 10 | */ 11 | public class NoopValidator implements InputValidator { 12 | @Override 13 | public boolean validate(String input) { 14 | return true; 15 | } 16 | 17 | @Override 18 | public String alias() { 19 | return "noop"; 20 | } 21 | 22 | @Override 23 | public String messageOnFail() { 24 | return ""; 25 | } 26 | 27 | @Override 28 | public Player inputPlayer() { 29 | return null; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/prompt/validators/OnlinePlayerValidator.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.prompt.validators; 2 | 3 | import com.cyr1en.commandprompter.api.prompt.CompoundableValidator; 4 | import com.cyr1en.commandprompter.api.prompt.InputValidator; 5 | import org.bukkit.Bukkit; 6 | import org.bukkit.entity.Player; 7 | 8 | /** 9 | * A validator that checks if the input is an online player. 10 | **/ 11 | public class OnlinePlayerValidator implements InputValidator, CompoundableValidator { 12 | 13 | 14 | public static final CompoundableValidator.Type DEFAULT_TYPE = Type.AND; 15 | private CompoundableValidator.Type type = DEFAULT_TYPE; 16 | 17 | private final String alias; 18 | private final String messageOnFail; 19 | private final Player inputPlayer; 20 | 21 | public OnlinePlayerValidator(String alias, String messageOnFail, Player inputPlayer) { 22 | this.alias = alias; 23 | this.messageOnFail = messageOnFail; 24 | this.inputPlayer = inputPlayer; 25 | } 26 | 27 | @Override 28 | public boolean validate(String input) { 29 | if (input == null || input.isBlank()) 30 | return false; 31 | var player = Bukkit.getPlayer(input); 32 | return player != null && player.isOnline(); 33 | } 34 | 35 | @Override 36 | public String alias() { 37 | return this.alias; 38 | } 39 | 40 | @Override 41 | public String messageOnFail() { 42 | return this.messageOnFail; 43 | } 44 | 45 | @Override 46 | public Player inputPlayer() { 47 | return this.inputPlayer; 48 | } 49 | 50 | @Override 51 | public Type getType() { 52 | return this.type; 53 | } 54 | 55 | @Override 56 | public void setType(Type type) { 57 | this.type = type; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/prompt/validators/RegexValidator.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.prompt.validators; 2 | 3 | import com.cyr1en.commandprompter.api.prompt.CompoundableValidator; 4 | import com.cyr1en.commandprompter.api.prompt.InputValidator; 5 | import org.bukkit.entity.Player; 6 | 7 | import java.util.regex.Pattern; 8 | 9 | /** 10 | * A validator that uses regex to validate input. 11 | *

12 | * This validator is used to validate input based on a regex pattern. 13 | */ 14 | public class RegexValidator implements InputValidator, CompoundableValidator { 15 | 16 | public static final CompoundableValidator.Type DEFAULT_TYPE = CompoundableValidator.Type.AND; 17 | private CompoundableValidator.Type type = DEFAULT_TYPE; 18 | 19 | private final String alias; 20 | private final Pattern regex; 21 | private final String messageOnFail; 22 | private final Player inputPlayer; 23 | 24 | 25 | public RegexValidator(String alias, Pattern regex, String messageOnFail, Player inputPlayer) { 26 | this.alias = alias; 27 | this.regex = regex; 28 | this.messageOnFail = messageOnFail; 29 | this.inputPlayer = inputPlayer; 30 | } 31 | 32 | @Override 33 | public boolean validate(String input) { 34 | if (input == null || regex == null) 35 | return false; 36 | return regex.matcher(input).matches(); 37 | } 38 | 39 | @Override 40 | public String alias() { 41 | return this.alias; 42 | } 43 | 44 | @Override 45 | public String messageOnFail() { 46 | return this.messageOnFail; 47 | } 48 | 49 | @Override 50 | public Player inputPlayer() { 51 | return this.inputPlayer; 52 | } 53 | 54 | /** 55 | * Get the regex pattern. 56 | * 57 | * @return the regex pattern. 58 | */ 59 | public Pattern regex() { 60 | return regex; 61 | } 62 | 63 | @Override 64 | public Type getType() { 65 | return this.type; 66 | } 67 | 68 | @Override 69 | public void setType(Type type) { 70 | this.type = type; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/util/MMUtil.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.util; 2 | 3 | import net.kyori.adventure.text.Component; 4 | import net.kyori.adventure.text.minimessage.MiniMessage; 5 | import net.kyori.adventure.text.minimessage.tag.standard.StandardTags; 6 | 7 | import java.util.List; 8 | 9 | public class MMUtil { 10 | 11 | public static List filterOutMiniMessageTags(List strs) { 12 | return strs.stream().filter(str -> !isMiniMessageTag(str)).toList(); 13 | } 14 | 15 | /** 16 | * Function that checks if the prompt is a mini message tag. 17 | * 18 | * @param str Prompt to check 19 | * @return true if the prompt is a mini message tag, false otherwise. 20 | */ 21 | public static boolean isMiniMessageTag(String str) { 22 | var serializer = MiniMessage.builder() 23 | .tags(StandardTags.defaults()).build(); 24 | var parsed = serializer.deserialize(str); 25 | return !Component.text(str).equals(parsed); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/util/ServerUtil.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.util; 2 | 3 | import com.cyr1en.kiso.mc.Version; 4 | import org.bukkit.Bukkit; 5 | 6 | public class ServerUtil { 7 | 8 | 9 | public static boolean isAtOrAbove(String versionString) { 10 | var current = parsedVersion(); 11 | var target = Version.parse(versionString); 12 | return current.isNewerThan(target) || current.equals(target); 13 | } 14 | 15 | public static String version() { 16 | return Bukkit.getServer().getVersion(); 17 | } 18 | 19 | public static Version parsedVersion() { 20 | var version = version(); 21 | version = version.substring(version.indexOf("MC: ") + 4, version.length() - 1); 22 | return Version.parse(version); 23 | } 24 | 25 | public static boolean isMojangMapped() { 26 | var resolved = ServerType.resolve(); 27 | var ver = parsedVersion(); 28 | return (resolved == ServerType.Paper || resolved == ServerType.Purpur) && ver.isNewerThan(Version.parse("1.20.4")); 29 | } 30 | 31 | public static boolean BUNGEE_CHAT_AVAILABLE() { 32 | try { 33 | Class.forName("net.md_5.bungee.api.ChatColor"); 34 | return true; 35 | } catch (ClassNotFoundException e) { 36 | return false; 37 | } 38 | } 39 | 40 | public static ServerType resolve() { 41 | return ServerType.resolve(); 42 | } 43 | 44 | /** 45 | * Pretty much useless as of now. But I'm keeping it just in case we need 46 | * some logic for different server types in the future. 47 | */ 48 | public enum ServerType { 49 | CraftBukkit, 50 | Spigot, 51 | Paper, 52 | Purpur, 53 | CatServer, 54 | Mohist, 55 | Other; 56 | 57 | private static ServerType resolved; 58 | 59 | public static ServerType resolve() { 60 | if (resolved != null) return resolved; 61 | 62 | for (ServerType type : values()) { 63 | var typeName = type.name().toLowerCase(); 64 | var serverName = Bukkit.getServer().getName().toLowerCase(); 65 | if (serverName.contains(typeName)) { 66 | resolved = type; 67 | } 68 | } 69 | resolved = Other; 70 | return resolved; 71 | } 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/util/Util.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.util; 2 | 3 | import com.cyr1en.commandprompter.CommandPrompter; 4 | import com.cyr1en.commandprompter.PluginLogger; 5 | import com.cyr1en.kiso.mc.Version; 6 | import com.google.common.base.Charsets; 7 | import com.google.common.io.CharStreams; 8 | import net.md_5.bungee.api.ChatColor; 9 | 10 | import org.bukkit.Material; 11 | import org.bukkit.plugin.Plugin; 12 | 13 | import java.io.File; 14 | import java.io.IOException; 15 | import java.io.InputStreamReader; 16 | import java.nio.file.Files; 17 | import java.security.MessageDigest; 18 | import java.security.NoSuchAlgorithmException; 19 | import java.util.Locale; 20 | import java.util.Objects; 21 | import java.util.Optional; 22 | import java.util.function.Consumer; 23 | import java.util.function.Supplier; 24 | import java.util.regex.Pattern; 25 | 26 | public class Util { 27 | 28 | public static String stripColor(String msg) { 29 | return ChatColor.stripColor(color(msg)); 30 | } 31 | 32 | private static Optional getLogger() { 33 | if (CommandPrompter.getInstance() == null) 34 | return Optional.empty(); 35 | return Optional.of(CommandPrompter.getInstance().getPluginLogger()); 36 | } 37 | 38 | 39 | public static String color(String msg) { 40 | if (!ServerUtil.BUNGEE_CHAT_AVAILABLE()) 41 | return org.bukkit.ChatColor.translateAlternateColorCodes('&', msg); 42 | 43 | var supportedHex = ServerUtil.parsedVersion().isNewerThan(Version.parse("1.15.0")); 44 | if (supportedHex) { 45 | var pattern = Pattern.compile("#[a-fA-F0-9]{6}"); 46 | var matcher = pattern.matcher(msg); 47 | 48 | while (matcher.find()) { 49 | String color = msg.substring(matcher.start(), matcher.end()); 50 | msg = msg.replace(color, ChatColor.of(color) + ""); 51 | matcher = pattern.matcher(msg); 52 | } 53 | } 54 | return ChatColor.translateAlternateColorCodes('&', msg); 55 | } 56 | 57 | public static Material getCheckedMaterial(String materialString, Material defaultMaterial) { 58 | materialString = materialString.toUpperCase(Locale.ROOT); 59 | var mat = Material.getMaterial(materialString); 60 | return Objects.isNull(mat) ? defaultMaterial : mat; 61 | } 62 | 63 | public static boolean checkSHA1(File file, String checksum) { 64 | try { 65 | var data = Files.readAllBytes(file.toPath()); 66 | var hash = MessageDigest.getInstance("SHA-1").digest(data); 67 | var sb = new StringBuilder(); 68 | for (byte b : hash) 69 | sb.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1)); 70 | return sb.toString().equals(checksum); 71 | } catch (NoSuchAlgorithmException | IOException e) { 72 | return false; 73 | } 74 | } 75 | 76 | public static void deleteFile(File file, Consumer onFail) { 77 | if (file.exists()) 78 | if (!file.delete()) 79 | onFail.accept(file); 80 | } 81 | 82 | public static boolean isBundledVersion(Plugin plugin) { 83 | var is = plugin.getResource("META-INF/MANIFEST.MF"); 84 | if (is == null) return false; 85 | try { 86 | var str = CharStreams.toString(new InputStreamReader(is, Charsets.UTF_8)); 87 | return str.contains("Bundled: true"); 88 | } catch (IOException e) { 89 | return false; 90 | } 91 | } 92 | 93 | public static class ConsumerFallback { 94 | 95 | private final T val; 96 | private final Consumer consumer; 97 | private final Supplier test; 98 | private Runnable runnable; 99 | 100 | public ConsumerFallback(T val, Consumer consumer, Supplier test) { 101 | this.val = val; 102 | this.consumer = consumer; 103 | this.test = test; 104 | this.runnable = () -> { 105 | }; 106 | } 107 | 108 | public void complete() { 109 | if (test.get()) consumer.accept(val); 110 | else runnable.run(); 111 | } 112 | 113 | public ConsumerFallback orElse(Runnable runnable) { 114 | this.runnable = runnable; 115 | return this; 116 | } 117 | } 118 | 119 | } -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/util/unsafe/PvtFieldMutationException.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.util.unsafe; 2 | 3 | public class PvtFieldMutationException extends RuntimeException { 4 | public PvtFieldMutationException(Class targetClass) { 5 | super("Failed to set '" + targetClass.getSimpleName() + "' to private target field"); 6 | } 7 | 8 | public PvtFieldMutationException(String newVal) { 9 | super("Failed to set '" + newVal + "' to private target field"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/cyr1en/commandprompter/util/unsafe/PvtFieldMutator.java: -------------------------------------------------------------------------------- 1 | package com.cyr1en.commandprompter.util.unsafe; 2 | 3 | import sun.misc.Unsafe; 4 | 5 | import java.util.Objects; 6 | 7 | /** 8 | * A utility class that allows you to easily change the value of private final fields. 9 | *

10 | * WARNING: Use this class with caution. Only use it for cases that justifies unsafe operations. 11 | * First, check if there's another way to accomplish 12 | * 13 | *

14 | * This class uses Sun's {@link Unsafe Unsafe} class to put a new {@link Object object} 15 | * on an {@link Object object's} private final encapsulated field. 16 | *

17 | * Usage: 18 | *

{@code
 19 |  *     // let's say there's a private final field named "targetField" in instanceOfTarget
 20 |  *     var instanceOfTarget = new Target();
 21 |  *
 22 |  *     var newFieldVal = new SomeObject();
 23 |  *
 24 |  *     var mutator = new PvtFieldMutator();
 25 |  *     mutator.forField("targetField").in(instanceOfTarget).with(newFieldVal);
 26 |  * }
27 | */ 28 | public class PvtFieldMutator { 29 | 30 | private final Unsafe unsafe; 31 | private String targetName; 32 | private Object targetInstance; 33 | 34 | /** 35 | * PvtFieldMutator default constructor. 36 | * 37 | *

38 | * Using classes in {@link java.lang.reflect reflect}, initialize the {@link Unsafe unsafe} 39 | * instance variable. 40 | * 41 | * @throws NoSuchFieldException when the private field "theUnsafe" is not found. 42 | * @throws IllegalAccessException when access to the field "theUnsafe" is prevented. 43 | */ 44 | public PvtFieldMutator() throws NoSuchFieldException, IllegalAccessException { 45 | var unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); 46 | unsafeField.setAccessible(true); 47 | this.unsafe = (Unsafe) unsafeField.get(null); 48 | } 49 | 50 | /** 51 | * Function that defines the name of the target. 52 | * 53 | * @param targetFieldName name of the target field. 54 | * @return instance of this class. 55 | */ 56 | public PvtFieldMutator forField(String targetFieldName) { 57 | this.targetName = targetFieldName; 58 | return this; 59 | } 60 | 61 | /** 62 | * Function that defines the instance of the target object. 63 | * 64 | * @param objInstance instance of the target object. 65 | * @return instance of this class. 66 | */ 67 | public PvtFieldMutator in(Object objInstance) { 68 | this.targetInstance = objInstance; 69 | return this; 70 | } 71 | 72 | /** 73 | * Function that defines the new object for the target field 74 | * and executes the mutation 75 | * 76 | * @param newObject object to replace the private final field with. 77 | * @throws NullPointerException when one of the target field name or target instance is null. 78 | * @throws NoSuchFieldException when the target field cannot be found in the target instance. 79 | */ 80 | public void replaceWith(Object newObject) throws NoSuchFieldException, IllegalStateException, IllegalAccessException { 81 | assertTargetNotNull(); 82 | var targetField = targetInstance.getClass().getDeclaredField(targetName); 83 | var targetFieldOffset = unsafe.objectFieldOffset(targetField); 84 | unsafe.putObject(targetInstance, targetFieldOffset, newObject); 85 | 86 | // Assert that we actually have it done correctly. 87 | targetField.setAccessible(true); 88 | var newFieldObject = targetField.get(targetInstance); 89 | 90 | if(Objects.isNull(newObject)) { 91 | if(!Objects.isNull(newFieldObject)) 92 | throw new PvtFieldMutationException("null"); 93 | return; 94 | } 95 | 96 | if (!newFieldObject.getClass().getCanonicalName().equals(newObject.getClass().getCanonicalName())) 97 | throw new PvtFieldMutationException(newObject.getClass()); 98 | } 99 | 100 | /** 101 | * Helper function to easily get the current class name for the target field. 102 | * 103 | *

104 | * Uses reflection to set the target field accessible from the instance of the defining 105 | * object. 106 | * 107 | * @return The canonical name of the current class of the target field. 108 | * @throws NullPointerException when one of the target field name or target instance is null. 109 | * @throws NoSuchFieldException when the target field cannot be found in the target instance. 110 | */ 111 | public String getClassName() throws NoSuchFieldException, IllegalAccessException { 112 | assertTargetNotNull(); 113 | var targetField = targetInstance.getClass().getDeclaredField(targetName); 114 | targetField.setAccessible(true); 115 | var ob = targetField.get(targetInstance); 116 | return ob.getClass().getCanonicalName(); 117 | } 118 | 119 | public int getHashCode() throws NoSuchFieldException, IllegalAccessException { 120 | assertTargetNotNull(); 121 | var targetField = targetInstance.getClass().getDeclaredField(targetName); 122 | targetField.setAccessible(true); 123 | var ob = targetField.get(targetInstance); 124 | return ob.hashCode(); 125 | } 126 | 127 | /** 128 | * Asserts that both target field name and target instance is not null. 129 | * 130 | * @throws NullPointerException when the target field cannot be found in the target instance. 131 | */ 132 | private void assertTargetNotNull() throws IllegalStateException { 133 | if (this.targetInstance == null || this.targetName == null) 134 | throw new IllegalStateException("Target field name or target instance is null."); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/main/resources/CommandPrompter.properties: -------------------------------------------------------------------------------- 1 | PromptInProgress = &6You are still trying to complete a command. Type &c"%s"&6 to cancel. 2 | SignPromptMultiArg = &6Sent a multi argument sign prompt. Type the response after the &a&l: 3 | SignPromptReminder = &6Sent a sign prompt. Every line that you put will be combined into a single response separated by a space 4 | CompletedCommand = &6Command to execute: &3%s 5 | PromptNoPerm = &cYou don't have permission to use CommandPrompter. 6 | PromptPlayerOnly = &6CommandPrompter can only be used by a player.! 7 | PromptCancel = &6Command completion cancelled! 8 | CommandCancelNotInCompletion = &cYou are not in command completion. 9 | CommandReloadSuccess = &aSuccessfully reloaded configuration! 10 | CommandRebuildHeadCacheSuccess = &aSuccessfully rebuilt head cache! (%sms) 11 | DelegateInvalidArgs = &cInvalid arguments for console delegate command! 12 | DelegateUsage = &6Usage: &3%s 13 | DelegateInvalidPlayer = &cThe player &6%s&c is not online! 14 | DelegateConsoleOnly = &cThis command can only be executed by the console! 15 | PluginVersion = &6Installed Version: &2&l%s 16 | PCMOutOfBounds = &cThe index &6%s&c is out of bounds! -------------------------------------------------------------------------------- /src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: ${projectName} 2 | version: ${projectVersion} 3 | author: ${projectAuthor} 4 | description: ${projectDescription} 5 | softdepend: [SuperVanish,PremiumVanish, PlaceholderAPI, CarbonChat, Towny, HuskTowns] 6 | 7 | main: ${projectEntry} 8 | api-version: 1.13 9 | 10 | permissions: 11 | commandprompter.reload: 12 | description: Allow sender to reload plugin. 13 | commandprompter.use: 14 | description: Allow sender to use CommandPrompter argument feature. 15 | commandprompter.cancel: 16 | description: Allow sender to cancel their own command completion. 17 | commandprompter.consoledelegate: 18 | description: Allow sender to delegate command to console. 19 | commandprompter.rebuildheadcache: 20 | description: Allow sender to rebuild head cache. --------------------------------------------------------------------------------