├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── feature_request.yml ├── dependabot.yml └── workflows │ ├── PR-master.yml │ ├── push-dev.yml │ └── push-main.yml ├── .gitignore ├── CONTRIBUTING.md ├── Jenkinsfile ├── LICENSE ├── README.md ├── access-item ├── build.gradle.kts └── src │ └── main │ ├── java │ └── dev │ │ └── kejona │ │ └── crossplatforms │ │ └── accessitem │ │ ├── AccessItem.java │ │ ├── AccessItemConfig.java │ │ ├── AccessItemRegistry.java │ │ ├── GiveCommand.java │ │ └── InspectItemCommand.java │ └── resources │ └── access-items.yml ├── build.gradle.kts ├── bungeecord ├── build.gradle.kts └── src │ └── main │ ├── java │ └── dev │ │ └── kejona │ │ └── crossplatforms │ │ └── bungeecord │ │ ├── CrossplatFormsBungeeCord.java │ │ └── handler │ │ ├── BungeeCommandOrigin.java │ │ ├── BungeeCordHandler.java │ │ └── BungeeCordPlayer.java │ └── resources │ └── bungee.yml ├── core ├── build.gradle.kts └── src │ ├── main │ ├── java │ │ └── dev │ │ │ └── kejona │ │ │ └── crossplatforms │ │ │ ├── Constants.java │ │ │ ├── CrossplatForms.java │ │ │ ├── CrossplatFormsBootstrap.java │ │ │ ├── IllegalValueException.java │ │ │ ├── JavaUtilLogger.java │ │ │ ├── Logger.java │ │ │ ├── Platform.java │ │ │ ├── SkinCache.java │ │ │ ├── action │ │ │ ├── Action.java │ │ │ ├── ActionSerializer.java │ │ │ ├── BedrockTransferAction.java │ │ │ ├── CommandsAction.java │ │ │ ├── GenericAction.java │ │ │ ├── InterfaceAction.java │ │ │ ├── MessageAction.java │ │ │ └── ServerAction.java │ │ │ ├── command │ │ │ ├── CommandOrigin.java │ │ │ ├── CommandType.java │ │ │ ├── DispatchableCommand.java │ │ │ ├── DispatchableCommandSerializer.java │ │ │ ├── FormsCommand.java │ │ │ ├── custom │ │ │ │ ├── CustomCommand.java │ │ │ │ ├── CustomCommandManager.java │ │ │ │ ├── CustomCommandSerializer.java │ │ │ │ ├── InterceptCommand.java │ │ │ │ ├── InterceptCommandCache.java │ │ │ │ ├── Literals.java │ │ │ │ └── RegisteredCommand.java │ │ │ └── defaults │ │ │ │ ├── DefaultCommands.java │ │ │ │ ├── HelpCommand.java │ │ │ │ ├── IdentifyCommand.java │ │ │ │ ├── InspectCommand.java │ │ │ │ ├── ListCommand.java │ │ │ │ ├── OpenCommand.java │ │ │ │ ├── ReloadCommand.java │ │ │ │ └── VersionCommand.java │ │ │ ├── config │ │ │ ├── ConfigId.java │ │ │ ├── ConfigManager.java │ │ │ ├── Configuration.java │ │ │ ├── ConfigurationModule.java │ │ │ ├── GeneralConfig.java │ │ │ └── PrettyPrinter.java │ │ │ ├── context │ │ │ ├── Context.java │ │ │ └── PlayerContext.java │ │ │ ├── filler │ │ │ ├── FillerSerializer.java │ │ │ ├── FillerUtils.java │ │ │ ├── OptionFiller.java │ │ │ ├── PlayerFiller.java │ │ │ ├── SimpleFormFiller.java │ │ │ ├── SplitterFiller.java │ │ │ └── UniversalFiller.java │ │ │ ├── handler │ │ │ ├── BasicPlaceholders.java │ │ │ ├── BedrockHandler.java │ │ │ ├── FloodgateHandler.java │ │ │ ├── FormPlayer.java │ │ │ ├── GeyserHandler.java │ │ │ ├── Placeholders.java │ │ │ └── ServerHandler.java │ │ │ ├── interfacing │ │ │ ├── Argument.java │ │ │ ├── ArgumentException.java │ │ │ ├── Interface.java │ │ │ ├── InterfaceConfig.java │ │ │ ├── Interfacer.java │ │ │ ├── bedrock │ │ │ │ ├── BedrockForm.java │ │ │ │ ├── BedrockFormRegistry.java │ │ │ │ ├── BedrockFormSerializer.java │ │ │ │ ├── FormConfig.java │ │ │ │ ├── OptionalElement.java │ │ │ │ ├── custom │ │ │ │ │ ├── AbstractComponent.java │ │ │ │ │ ├── ComponentSerializer.java │ │ │ │ │ ├── CustomBedrockForm.java │ │ │ │ │ ├── CustomComponent.java │ │ │ │ │ ├── Dropdown.java │ │ │ │ │ ├── Input.java │ │ │ │ │ ├── Label.java │ │ │ │ │ ├── Option.java │ │ │ │ │ ├── OptionSerializer.java │ │ │ │ │ ├── Slider.java │ │ │ │ │ ├── StepSlider.java │ │ │ │ │ ├── Toggle.java │ │ │ │ │ └── lombok.config │ │ │ │ ├── modal │ │ │ │ │ ├── ModalBedrockForm.java │ │ │ │ │ └── ModalButton.java │ │ │ │ └── simple │ │ │ │ │ ├── SimpleBedrockForm.java │ │ │ │ │ └── SimpleButton.java │ │ │ └── java │ │ │ │ ├── ItemButton.java │ │ │ │ ├── JavaMenu.java │ │ │ │ ├── JavaMenuRegistry.java │ │ │ │ └── MenuConfig.java │ │ │ ├── inventory │ │ │ ├── ClickHandler.java │ │ │ ├── ConfiguredItem.java │ │ │ ├── InventoryController.java │ │ │ ├── InventoryFactory.java │ │ │ ├── InventoryHandle.java │ │ │ ├── InventoryLayout.java │ │ │ ├── ItemHandle.java │ │ │ └── SkullProfile.java │ │ │ ├── parser │ │ │ ├── BlockPlaceholderParser.java │ │ │ ├── Parser.java │ │ │ ├── ParserSerializer.java │ │ │ ├── PlaceholderParser.java │ │ │ └── ReplacementParser.java │ │ │ ├── permission │ │ │ ├── EmptyPermissions.java │ │ │ ├── LuckPermsHook.java │ │ │ ├── Permission.java │ │ │ ├── PermissionDefault.java │ │ │ ├── Permissions.java │ │ │ └── lombok.config │ │ │ ├── reloadable │ │ │ ├── Reloadable.java │ │ │ └── ReloadableRegistry.java │ │ │ ├── resolver │ │ │ ├── MapResolver.java │ │ │ ├── PlayerResolver.java │ │ │ └── Resolver.java │ │ │ ├── serialize │ │ │ ├── AsNodePath.java │ │ │ ├── KeyedType.java │ │ │ ├── KeyedTypeSerializer.java │ │ │ ├── PathNodeResolver.java │ │ │ ├── StreamSerializer.java │ │ │ ├── TypeRegistry.java │ │ │ ├── TypeResolver.java │ │ │ └── UnaryNodes.java │ │ │ └── utils │ │ │ ├── ConfigurateUtils.java │ │ │ ├── FileUtils.java │ │ │ ├── ParseUtils.java │ │ │ ├── ReflectionUtils.java │ │ │ ├── SkinUtils.java │ │ │ └── StringUtils.java │ ├── java16 │ │ └── dev │ │ │ └── kejona │ │ │ └── crossplatforms │ │ │ └── handler │ │ │ └── GeyserHandler.java │ └── resources │ │ ├── bedrock-forms.yml │ │ ├── build.properties │ │ ├── config.yml │ │ └── java-menus.yml │ ├── test │ ├── java │ │ └── dev │ │ │ └── kejona │ │ │ └── crossplatforms │ │ │ ├── ParseUtilsTest.java │ │ │ ├── command │ │ │ └── DispatchableCommandTest.java │ │ │ ├── config │ │ │ ├── ConfigManagerTest.java │ │ │ ├── PrettyPrintTest.java │ │ │ ├── form │ │ │ │ └── FormConfigUpdaterTest.java │ │ │ └── valuedserializer │ │ │ │ ├── Integer.java │ │ │ │ ├── KeyedTypeSerializerTest.java │ │ │ │ ├── Number.java │ │ │ │ └── ScientificNotationNumber.java │ │ │ ├── form │ │ │ └── component │ │ │ │ ├── InputComponentTest.java │ │ │ │ └── ResolvePlaceholdersTest.java │ │ │ └── util │ │ │ ├── ConfigurateUtilsTest.java │ │ │ └── StringUtilsTest.java │ └── resources │ │ ├── KeyedTypeConfig.yml │ │ ├── ValuedTypeConfig.yml │ │ ├── bedrock-forms.yml │ │ └── configs │ │ └── forms │ │ ├── bedrock-forms-1.yml │ │ ├── bedrock-forms-2.yml │ │ ├── bedrock-forms-3.yml │ │ ├── bedrock-forms-4.yml │ │ └── bedrock-forms-5.yml │ └── testFixtures │ └── java │ └── dev │ └── kejona │ └── crossplatforms │ ├── FakePlayer.java │ ├── TestLogger.java │ └── TestModule.java ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images ├── README.md ├── clan-1.png ├── clan-2.png ├── clan-3.png ├── crossplatForms.svg ├── twitch-form.png └── twitch-menu.png ├── proxy ├── build.gradle.kts └── src │ └── main │ └── java │ └── dev │ └── kejona │ └── crossplatforms │ └── proxy │ ├── CloseMenuAction.java │ ├── ProtocolizeModule.java │ └── inventory │ ├── ProtocolizeInventory.java │ ├── ProtocolizeInventoryController.java │ ├── ProtocolizeInventoryFactory.java │ └── ProtocolizeItem.java ├── settings.gradle.kts ├── spigot-common ├── common │ ├── build.gradle.kts │ └── src │ │ ├── main │ │ └── java │ │ │ └── dev │ │ │ └── kejona │ │ │ └── crossplatforms │ │ │ └── spigot │ │ │ ├── ClassNames.java │ │ │ ├── CloseMenuAction.java │ │ │ ├── GeyserHubConverter.java │ │ │ ├── SpigotAccessItems.java │ │ │ ├── SpigotBase.java │ │ │ ├── adapter │ │ │ ├── NbtAccessor.java │ │ │ ├── SpigotAdapter.java │ │ │ ├── Version.java │ │ │ ├── VersionMap.java │ │ │ └── Versioned.java │ │ │ ├── handler │ │ │ ├── PlaceholderAPIHandler.java │ │ │ ├── SpigotCommandOrigin.java │ │ │ ├── SpigotHandler.java │ │ │ ├── SpigotPermissions.java │ │ │ └── SpigotPlayer.java │ │ │ ├── item │ │ │ ├── SpigotInventoryController.java │ │ │ └── SpigotInventoryFactory.java │ │ │ └── utils │ │ │ └── InventoryUtils.java │ │ └── test │ │ ├── java │ │ └── dev │ │ │ └── kejona │ │ │ └── crossplatforms │ │ │ └── spigot │ │ │ ├── GeyserHubConvertTest.java │ │ │ └── VersionMapTest.java │ │ └── resources │ │ ├── access-items.yml │ │ ├── bedrock-forms.yml │ │ ├── java-menus.yml │ │ └── selector.yml ├── v1_12_R1 │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── java │ │ └── dev │ │ └── kejona │ │ └── crossplatforms │ │ └── spigot │ │ └── v1_12_R1 │ │ ├── Adapter_v1_12_R1.java │ │ └── EntityPickupItemListener.java ├── v1_13_R2 │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── java │ │ └── dev │ │ └── kejona │ │ └── crossplatforms │ │ └── spigot │ │ └── v1_13_R2 │ │ └── Adapter_v1_13_R2.java ├── v1_14_R1 │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── java │ │ └── dev │ │ └── kejona │ │ └── crossplatforms │ │ └── spigot │ │ └── v1_14_R1 │ │ ├── Adapter_v1_14_R1.java │ │ └── ModernNbtAccessor.java ├── v1_20_R2 │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── java │ │ └── dev │ │ └── kejona │ │ └── crossplatforms │ │ └── spigot │ │ └── v1_20_R2 │ │ └── Adapter_v1_20_R2.java ├── v1_8_R3 │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── java │ │ └── dev │ │ └── kejona │ │ └── crossplatforms │ │ └── spigot │ │ └── v1_8_R3 │ │ ├── Adapter_v1_8_R3.java │ │ ├── LegacyNbtAccessor.java │ │ └── PlayerPickupItemListener.java └── v1_9_R2 │ ├── build.gradle.kts │ └── src │ └── main │ └── java │ └── dev │ └── kejona │ └── crossplatforms │ └── spigot │ └── v1_9_R2 │ ├── Adapter_v1_9_R2.java │ └── SwapHandItemsListener.java ├── spigot ├── build.gradle.kts └── src │ └── main │ ├── java │ └── dev │ │ └── kejona │ │ └── crossplatforms │ │ └── spigot │ │ └── CrossplatFormsSpigot.java │ └── resources │ └── plugin.yml └── velocity ├── build.gradle.kts └── src └── main ├── java └── dev │ └── kejona │ └── crossplatforms │ └── velocity │ ├── CrossplatFormsVelocity.java │ ├── SLF4JLogger.java │ └── handler │ ├── VelocityCommandOrigin.java │ ├── VelocityHandler.java │ └── VelocityPlayer.java └── resources └── velocity-plugin.json /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Create a report to help us improve 3 | body: 4 | - type: textarea 5 | attributes: 6 | label: Describe the bug 7 | description: A clear and concise description of what the bug is. 8 | validations: 9 | required: true 10 | - type: textarea 11 | attributes: 12 | label: To Reproduce 13 | description: Steps to reproduce this behaviour 14 | placeholder: | 15 | 1. Go to '...' 16 | 2. Click on '...' 17 | 3. Scroll down to '...' 18 | 4. See error 19 | validations: 20 | required: true 21 | - type: textarea 22 | attributes: 23 | label: Expected behaviour 24 | description: A clear and concise description of what you expected to happen. 25 | validations: 26 | required: true 27 | - type: textarea 28 | attributes: 29 | label: Screenshots / Videos 30 | description: If applicable, add screenshots to help explain your problem. 31 | - type: textarea 32 | attributes: 33 | label: Server Version and Plugins 34 | description: | 35 | If you just run Geyser-Spigot, you can leave this area blank as the next section covers this information. 36 | - type: input 37 | attributes: 38 | label: Geyser Dump 39 | description: If Geyser starts correctly, please include the link to a dump using `/geyser dump`. If you're using the Standalone GUI, you can find the option under `Commands` => `Dump`. Doing this provides us information about your server that we can use to debug your issue. 40 | - type: input 41 | attributes: 42 | label: CrossplatForms Version 43 | description: What build number of CrossplatForms are you running? 44 | validations: 45 | required: true 46 | - type: textarea 47 | attributes: 48 | label: Additional Context 49 | description: Add any other context about the problem here 50 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest an idea for this project 3 | labels: "Feature Request" 4 | body: 5 | - type: textarea 6 | attributes: 7 | label: What feature do you want to see added? 8 | description: A clear and concise description of your feature request. 9 | validations: 10 | required: true 11 | - type: textarea 12 | attributes: 13 | label: Are there any alternatives? 14 | description: List any alternatives you might have tried 15 | validations: 16 | required: true 17 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "gradle" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | target-branch: "dev" 11 | schedule: 12 | interval: "daily" 13 | 14 | - package-ecosystem: "github-actions" 15 | directory: "/" 16 | target-branch: "dev" 17 | schedule: 18 | interval: "weekly" 19 | 20 | -------------------------------------------------------------------------------- /.github/workflows/PR-master.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: PR 5 | 6 | on: 7 | pull_request: 8 | branches: 9 | - "main" 10 | - "dev" 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Set up JDK 16 20 | uses: actions/setup-java@v3 21 | with: 22 | java-version: '16' 23 | distribution: 'adopt' 24 | - name: Build with Gradle 25 | run: ./gradlew clean build 26 | 27 | - name: Upload a Build Artifact 28 | uses: actions/upload-artifact@v3 29 | if: success() 30 | with: 31 | name: CrossplatForms 32 | path: | 33 | spigot/build/libs/CrossplatForms-Spigot.jar 34 | spigot-legacy/build/libs/CrossplatForms-SpigotLegacy.jar 35 | bungeecord/build/libs/CrossplatForms-BungeeCord.jar 36 | velocity/build/libs/CrossplatForms-Velocity.jar 37 | -------------------------------------------------------------------------------- /.github/workflows/push-dev.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: push-dev 5 | 6 | on: 7 | push: 8 | branches: [ dev ] 9 | 10 | jobs: 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: Set up JDK 16 18 | uses: actions/setup-java@v3 19 | with: 20 | java-version: '16' 21 | distribution: 'adopt' 22 | - name: Build with Gradle 23 | run: ./gradlew clean build 24 | 25 | - name: Upload a Build Artifact 26 | uses: actions/upload-artifact@v3 27 | if: success() 28 | with: 29 | name: CrossplatForms 30 | path: | 31 | spigot/build/libs/CrossplatForms-Spigot.jar 32 | spigot-legacy/build/libs/CrossplatForms-SpigotLegacy.jar 33 | bungeecord/build/libs/CrossplatForms-BungeeCord.jar 34 | velocity/build/libs/CrossplatForms-Velocity.jar 35 | -------------------------------------------------------------------------------- /.github/workflows/push-main.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: push 5 | 6 | on: 7 | push: 8 | branches: 9 | - main 10 | - cumulus-bump-showcase 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Set up JDK 16 20 | uses: actions/setup-java@v3 21 | with: 22 | java-version: '16' 23 | distribution: 'adopt' 24 | - name: Build with Gradle 25 | run: ./gradlew clean build 26 | 27 | - name: Upload a Build Artifact 28 | uses: actions/upload-artifact@v3 29 | if: success() 30 | with: 31 | name: CrossplatForms 32 | path: | 33 | spigot/build/libs/CrossplatForms-Spigot.jar 34 | spigot-legacy/build/libs/CrossplatForms-SpigotLegacy.jar 35 | bungeecord/build/libs/CrossplatForms-BungeeCord.jar 36 | velocity/build/libs/CrossplatForms-Velocity.jar 37 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to build 2 | 3 | Executing `./gradlew clean build` will execute typical building, as well as tests and shading required for each platform. 4 | Once finished the build for a given platform is available in `/build/libs`. `Crossplatforms-.jar` is the built plugin that 5 | can run a server. `-.jar` contains only native sources. 6 | 7 | # Contribution guidelines 8 | 9 | - Follow general best practices. 10 | - Follow the existing indentation and space styling. 11 | - The only nullability annotation used should be from javax. You can configure IntelliJ IDEA to use javax annotations when automatically adding nullability annotations. 12 | - Avoid using the static getters on `Crossplatforms.class` or any of the platform main classes, unless other options aren't feasible. 13 | - Keep breaking changes to configuration classes as small as possible, but don't let it hold better design back. 14 | - If you make any breaking changes to configuration classes, please write a version translator to automatically update older versions to the newer one. Configurate has 15 | examples of this as well as the existing version translators that we have. 16 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent any 3 | 4 | tools { 5 | jdk 'Jdk17' 6 | gradle 'gradle' 7 | } 8 | 9 | stages { 10 | stage('Build') { 11 | steps { 12 | echo 'Building..' 13 | sh './gradlew clean build' 14 | } 15 | post { 16 | success { 17 | archiveArtifacts 'spigot/build/libs/CrossplatForms-Spigot.jar' 18 | archiveArtifacts 'bungeecord/build/libs/CrossplatForms-BungeeCord.jar' 19 | archiveArtifacts 'velocity/build/libs/CrossplatForms-Velocity.jar' 20 | } 21 | } 22 | } 23 | } 24 | 25 | post { 26 | always { 27 | deleteDir() 28 | } 29 | 30 | success { 31 | script { 32 | def changeLogSets = currentBuild.changeSets 33 | def message = "**Changes:**" 34 | 35 | if (changeLogSets.size() == 0) { 36 | message += "\n*No changes.*" 37 | } else { 38 | def repositoryUrl = scm.userRemoteConfigs[0].url.replace(".git", "") 39 | def count = 0; 40 | def extra = 0; 41 | for (int i = 0; i < changeLogSets.size(); i++) { 42 | def entries = changeLogSets[i].items 43 | for (int j = 0; j < entries.length; j++) { 44 | if (count <= 10) { 45 | def entry = entries[j] 46 | def commitId = entry.commitId.substring(0, 6) 47 | message += "\n - [`${commitId}`](${repositoryUrl}/commit/${entry.commitId}) ${entry.msg}" 48 | count++ 49 | } else { 50 | extra++; 51 | } 52 | } 53 | } 54 | 55 | if (extra != 0) { 56 | message += "\n - ${extra} more commits" 57 | } 58 | } 59 | env.changes = message 60 | } 61 | 62 | discordSend description: "**Build:** [${currentBuild.id}](${env.BUILD_URL})\n**Status:** [${currentBuild.currentResult}](${env.BUILD_URL})\n${changes}\n\n[**Artifacts on Jenkins**](https://ci.kejonamc.dev/job/CrossplatForms/)", footer: 'KejonaMC', link: env.BUILD_URL, successful: currentBuild.resultIsBetterOrEqualTo('SUCCESS'), result: currentBuild.currentResult, title: "${env.JOB_NAME}", webhookURL: "${env.DISCORD_WEBHOOK}" 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /access-item/build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | description = "access-item" 3 | 4 | dependencies { 5 | compileOnly(projects.core) 6 | } -------------------------------------------------------------------------------- /access-item/src/main/java/dev/kejona/crossplatforms/accessitem/InspectItemCommand.java: -------------------------------------------------------------------------------- 1 | package dev.kejona.crossplatforms.accessitem; 2 | 3 | import cloud.commandframework.Command; 4 | import cloud.commandframework.CommandManager; 5 | import cloud.commandframework.arguments.standard.StringArgument; 6 | import dev.kejona.crossplatforms.CrossplatForms; 7 | import dev.kejona.crossplatforms.command.CommandOrigin; 8 | import dev.kejona.crossplatforms.command.FormsCommand; 9 | import dev.kejona.crossplatforms.command.defaults.InspectCommand; 10 | 11 | import java.util.stream.Collectors; 12 | 13 | public class InspectItemCommand extends FormsCommand { 14 | 15 | private final AccessItemRegistry itemRegistry; 16 | 17 | public InspectItemCommand(CrossplatForms crossplatForms, AccessItemRegistry itemRegistry) { 18 | super(crossplatForms); 19 | this.itemRegistry = itemRegistry; 20 | } 21 | 22 | @Override 23 | public void register(CommandManager manager, Command.Builder defaultBuilder) { 24 | // addon to the inspect command 25 | manager.command(crossplatForms.getCommandBuilder() 26 | .literal(InspectCommand.NAME) 27 | .permission(InspectCommand.PERMISSION) 28 | .literal("item") 29 | .argument(StringArgument.builder("item") 30 | .withSuggestionsProvider(((context, s) -> itemRegistry.getItems().values() 31 | .stream() 32 | .map(AccessItem::getIdentifier) 33 | .collect(Collectors.toList())))) 34 | .handler(context -> { 35 | CommandOrigin origin = context.getSender(); 36 | String name = context.get("item"); 37 | AccessItem item = itemRegistry.getItems().get(name); 38 | if (item == null) { 39 | origin.warn("That Access Item doesn't exist!"); 40 | } else { 41 | origin.sendMessage("Inspection of access item: " + name); 42 | origin.sendMessage(item.toString()); 43 | } 44 | }) 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /access-item/src/main/resources/access-items.yml: -------------------------------------------------------------------------------- 1 | # Configuration of access items, items in hotbars that are shortcuts for forms and menus. 2 | 3 | enable: true 4 | # If players should switch their selected item to the access item that they were just given to them through events. 5 | set-held-slot: false 6 | 7 | # global permission defaults can be defined here, which will be used if not listed explicitly in an individual entry 8 | global-permission-defaults: 9 | # Default value for crossplatforms.item..possess , permission to simply have it 10 | POSSESS: TRUE 11 | # Default value for crossplatforms.item..event, regulates who the events gives the access item to 12 | EVENT: TRUE 13 | # Default value for crossplatforms.give..command, Regulates if the access item can be used for /forms give 14 | COMMAND: OP 15 | # Stop the player from dropping the item 16 | DROP: FALSE 17 | # Destroy the item if the player drops it 18 | PRESERVE: FALSE 19 | # Stop the player from moving the compass in their inventory 20 | MOVE: FALSE 21 | 22 | items: 23 | servers: 24 | # "action", "bedrock-action", and "java-action" are all allowed. These should be condensed into a single "action" since they are the same. 25 | actions: 26 | # The form/menu to open 27 | - form: servers 28 | material: COMPASS 29 | display-name: "§6Server Selector" 30 | lore: 31 | - "Right click me!" 32 | # First hotbar slot is 0 33 | slot: 4 34 | # Which platform the access item should be available to. JAVA for Java Edition only, BEDROCK for Bedrock Edition only, ALL for both. 35 | platform: ALL 36 | # Give the access item on server join 37 | on-join: true 38 | # Give the access item on player respawn 39 | on-respawn: true 40 | # Give the access item on world change 41 | on-world-change: false 42 | # Remove on player leave 43 | persist: false 44 | # This can be removed since it used the exact same values as the global-permission-defaults. 45 | permission-defaults: 46 | POSSESS: TRUE 47 | EVENT: TRUE 48 | COMMAND: OP 49 | PRESERVE: FALSE 50 | MOVE: FALSE 51 | 52 | minigames: 53 | material: CARROT_ON_A_STICK 54 | display-name: "§6Minigames" 55 | slot: 5 56 | actions: 57 | - form: minigames 58 | on-join: true 59 | on-respawn: true 60 | 61 | config-version: 4 -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | java 4 | `java-library` 5 | `maven-publish` 6 | id("net.kyori.indra.git") // used for getting branch/commit info 7 | id("idea") // used to download sources and documentation 8 | } 9 | 10 | allprojects{ 11 | apply(plugin = "java") 12 | apply(plugin = "java-library") 13 | apply(plugin = "net.kyori.indra.git") 14 | 15 | group = "dev.kejona" 16 | version = "1.5.0" 17 | 18 | java { 19 | toolchain { 20 | languageVersion.set(JavaLanguageVersion.of(8)) 21 | } 22 | } 23 | 24 | tasks.withType { 25 | options.encoding = "UTF-8" 26 | } 27 | 28 | tasks.withType().configureEach { 29 | useJUnitPlatform() 30 | // Disable creating of test report files 31 | reports.html.required.set(false) 32 | reports.junitXml.required.set(false) 33 | } 34 | 35 | tasks.named("build") { 36 | dependsOn(tasks.named("test")) 37 | } 38 | 39 | // Ensure platform jars are flagged as multi release 40 | tasks.jar { 41 | manifest { 42 | attributes("Multi-Release" to "true") 43 | } 44 | } 45 | 46 | tasks.processResources { 47 | expand( 48 | "project_description" to "Bedrock Edition forms, inventory menus, and more.", 49 | "project_url" to "https://github.com/kejonaMC/CrossplatForms", 50 | "project_version" to project.version, 51 | 52 | // indra branch works locally, environment variable should work on jenkins. 53 | "git_branch" to (indraGit.branchName() ?: System.getenv("GIT_BRANCH")), 54 | "git_commit" to (indraGit.commit()?.abbreviate(7)?.name() ?: "UNKNOWN"), 55 | "build_number" to (System.getenv("BUILD_NUMBER") ?: "UNKNOWN") 56 | ) 57 | } 58 | 59 | // disable javadocs 60 | tasks.withType().all { enabled = false } 61 | } 62 | 63 | subprojects { 64 | dependencies { 65 | testAnnotationProcessor("org.projectlombok:lombok:1.18.26") 66 | testCompileOnly("org.projectlombok:lombok:1.18.26") 67 | testImplementation("org.junit.jupiter:junit-jupiter:5.9.2") 68 | 69 | annotationProcessor("org.projectlombok:lombok:1.18.26") 70 | compileOnly("org.projectlombok:lombok:1.18.26") 71 | compileOnly("com.google.code.findbugs:jsr305:3.0.2") // nullability annotations 72 | } 73 | } 74 | 75 | publishing { 76 | publications.create("maven") { 77 | from(components["java"]) 78 | } 79 | } 80 | 81 | idea { 82 | module { 83 | isDownloadJavadoc = true 84 | isDownloadSources = true 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /bungeecord/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar 2 | 3 | plugins { 4 | id("com.github.johnrengelman.shadow") 5 | } 6 | 7 | dependencies { 8 | compileOnly("net.md-5:bungeecord-api:1.19-R0.1-SNAPSHOT") 9 | compileOnly("com.github.SpigotMC.BungeeCord:bungeecord-proxy:b23a518") // For getting skins (dependency through jitpack) 10 | api("cloud.commandframework:cloud-bungee:1.8.3") 11 | api("net.kyori:adventure-platform-bungeecord:4.3.0") 12 | implementation("org.bstats:bstats-bungeecord:3.0.2") 13 | api(projects.proxy) 14 | api(projects.core) 15 | } 16 | 17 | tasks.withType { 18 | dependencies { 19 | shadow { 20 | relocate("com.google.inject", "dev.kejona.crossplatforms.shaded.guice") 21 | relocate("cloud.commandframework", "dev.kejona.crossplatforms.shaded.cloud") 22 | relocate("net.kyori", "dev.kejona.crossplatforms.shaded.kyori") 23 | relocate("org.spongepowered.configurate", "dev.kejona.crossplatforms.shaded.configurate") 24 | // Used by cloud and configurate 25 | relocate("io.leangen.geantyref", "dev.kejona.crossplatforms.shaded.typetoken") 26 | relocate("org.bstats", "dev.kejona.crossplatforms.shaded.bstats") 27 | // bungeecord doesn't have good snakeyaml anymore 28 | relocate("org.yaml.snakeyaml", "dev.kejona.crossplatforms.shaded.snakeyaml") 29 | } 30 | exclude { 31 | e -> 32 | val name = e.name 33 | name.startsWith("com.mojang") // all available on bungee 34 | // Guice must be relocated, everything else is available 35 | || (name.startsWith("com.google") && !name.startsWith("com.google.inject")) 36 | || name.startsWith("javax.inject") 37 | } 38 | } 39 | 40 | archiveFileName.set("CrossplatForms-BungeeCord.jar") 41 | } 42 | 43 | tasks.named("build") { 44 | dependsOn(tasks.named("shadowJar")) 45 | } 46 | 47 | description = "bungeecord" 48 | -------------------------------------------------------------------------------- /bungeecord/src/main/java/dev/kejona/crossplatforms/bungeecord/handler/BungeeCommandOrigin.java: -------------------------------------------------------------------------------- 1 | package dev.kejona.crossplatforms.bungeecord.handler; 2 | 3 | import dev.kejona.crossplatforms.bungeecord.CrossplatFormsBungeeCord; 4 | import dev.kejona.crossplatforms.command.CommandOrigin; 5 | import lombok.AllArgsConstructor; 6 | import net.kyori.adventure.text.TextComponent; 7 | import net.md_5.bungee.api.CommandSender; 8 | import net.md_5.bungee.api.connection.ProxiedPlayer; 9 | 10 | import javax.annotation.Nonnull; 11 | import java.util.Optional; 12 | import java.util.UUID; 13 | 14 | @AllArgsConstructor 15 | public class BungeeCommandOrigin implements CommandOrigin { 16 | 17 | @Nonnull 18 | private final CommandSender sender; 19 | 20 | @Override 21 | public boolean hasPermission(String permission) { 22 | return sender.hasPermission(permission); 23 | } 24 | 25 | @Override 26 | public void sendRaw(TextComponent message) { 27 | sender.sendMessage(CrossplatFormsBungeeCord.COMPONENT_SERIALIZER.serialize(message)); 28 | } 29 | 30 | @Override 31 | public boolean isPlayer() { 32 | return sender instanceof ProxiedPlayer; 33 | } 34 | 35 | @Override 36 | public boolean isConsole() { 37 | return !(sender instanceof ProxiedPlayer); 38 | } 39 | 40 | @Override 41 | public Object getHandle() { 42 | return sender; 43 | } 44 | 45 | @Override 46 | public Optional getUUID() { 47 | if (sender instanceof ProxiedPlayer) { 48 | return Optional.of(((ProxiedPlayer) sender).getUniqueId()); 49 | } else { 50 | return Optional.empty(); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /bungeecord/src/main/resources/bungee.yml: -------------------------------------------------------------------------------- 1 | name: CrossplatForms 2 | main: dev.kejona.crossplatforms.bungeecord.CrossplatFormsBungeeCord 3 | version: ${project_version} 4 | description: ${project_description} 5 | author: Konicai, Jens 6 | softDepends: 7 | - Geyser-BungeeCord 8 | - floodgate 9 | - Protocolize 10 | - LuckPerms 11 | -------------------------------------------------------------------------------- /core/src/main/java/dev/kejona/crossplatforms/Constants.java: -------------------------------------------------------------------------------- 1 | package dev.kejona.crossplatforms; 2 | 3 | import dev.kejona.crossplatforms.utils.FileUtils; 4 | import net.kyori.adventure.text.Component; 5 | import net.kyori.adventure.text.TextComponent; 6 | import net.kyori.adventure.text.format.TextColor; 7 | 8 | import java.io.IOException; 9 | import java.util.Properties; 10 | 11 | public final class Constants { 12 | public static final String NAME = "CrossplatForms"; 13 | private static String ID = "crossplatforms"; 14 | public static final TextComponent MESSAGE_PREFIX = Component.text() 15 | .append(Component.text("[C", TextColor.color(128, 184, 224))) 16 | .append(Component.text("Forms] ", TextColor.color(203, 93, 128))) 17 | .build(); 18 | 19 | private static String VERSION = "UNKNOWN"; 20 | private static String BRANCH = "UNKNOWN"; 21 | private static String COMMIT = "UNKNOWN"; 22 | private static int BUILD_NUMBER = -1; 23 | 24 | public static String Id() { 25 | return ID; 26 | } 27 | public static void setId(String id) { 28 | ID = id; 29 | } 30 | 31 | public static String version() { 32 | return VERSION; 33 | } 34 | public static String branch() { 35 | return BRANCH; 36 | } 37 | public static String commit() { 38 | return COMMIT; 39 | } 40 | public static int buildNumber() { 41 | return BUILD_NUMBER; 42 | } 43 | 44 | public static void fetch() { 45 | Properties properties; 46 | try { 47 | properties = FileUtils.getProperties("build.properties"); 48 | } catch (IOException e) { 49 | Logger.get().warn("Failed to get build properties"); 50 | e.printStackTrace(); 51 | return; 52 | } 53 | 54 | VERSION = properties.getProperty("project_version", "UNKNOWN"); 55 | BRANCH = properties.getProperty("git_branch", "UNKNOWN"); 56 | COMMIT = properties.getProperty("git_commit", "UNKNOWN"); 57 | try { 58 | BUILD_NUMBER = Integer.parseUnsignedInt(properties.getProperty("build_number", "")); 59 | } catch (NumberFormatException ignored) { 60 | // already has default 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /core/src/main/java/dev/kejona/crossplatforms/CrossplatFormsBootstrap.java: -------------------------------------------------------------------------------- 1 | package dev.kejona.crossplatforms; 2 | 3 | import com.google.inject.Module; 4 | import dev.kejona.crossplatforms.config.ConfigId; 5 | import dev.kejona.crossplatforms.config.ConfigManager; 6 | import org.bstats.charts.CustomChart; 7 | import org.jetbrains.annotations.Contract; 8 | 9 | import java.util.List; 10 | 11 | public interface CrossplatFormsBootstrap { 12 | 13 | /** 14 | * Returns A List of modules that should be used for injection of configuration instances. It is expected that this 15 | * List can be added to without any consequences. It is not expected that these modules provide bindings for interfaces 16 | * and classes already provided in the {@link CrossplatForms} constructor. 17 | * 18 | * @return a List of modules as described 19 | */ 20 | @Contract(" -> new") 21 | List configModules(); 22 | 23 | /** 24 | * Perform any operations on the {@link ConfigManager} before {@link ConfigManager#load()} is called. For example, register 25 | * additional {@link ConfigId}, or register additional type serializers. 26 | */ 27 | void preConfigLoad(ConfigManager configManager); 28 | 29 | void addCustomChart(CustomChart chart); 30 | } 31 | -------------------------------------------------------------------------------- /core/src/main/java/dev/kejona/crossplatforms/IllegalValueException.java: -------------------------------------------------------------------------------- 1 | package dev.kejona.crossplatforms; 2 | 3 | import lombok.Getter; 4 | import lombok.experimental.Accessors; 5 | 6 | import javax.annotation.Nullable; 7 | 8 | @Accessors(fluent = true) 9 | @Getter 10 | public class IllegalValueException extends Exception { 11 | private static final long serialVersionUID = 0L; 12 | 13 | private final String value; 14 | private final String expectedType; 15 | private final String identifier; 16 | 17 | public IllegalValueException(@Nullable String value, String expectedType, String identifier) { 18 | super("Expected a " + expectedType + " for '" + identifier + "' which the following could not be converted to: " + value); 19 | this.value = value; 20 | this.expectedType = expectedType; 21 | this.identifier = identifier; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /core/src/main/java/dev/kejona/crossplatforms/JavaUtilLogger.java: -------------------------------------------------------------------------------- 1 | package dev.kejona.crossplatforms; 2 | 3 | public class JavaUtilLogger extends Logger { 4 | 5 | private final java.util.logging.Logger handle; 6 | private boolean debug = false; 7 | 8 | public JavaUtilLogger(java.util.logging.Logger logger) { 9 | handle = logger; 10 | } 11 | 12 | @Override 13 | public void info(String message) { 14 | handle.info(message); 15 | } 16 | 17 | @Override 18 | public void warn(String message) { 19 | handle.warning(message); 20 | } 21 | 22 | @Override 23 | public void severe(String message) { 24 | handle.severe(message); 25 | } 26 | 27 | @Override 28 | public void debug(String message) { 29 | if (debug) { 30 | handle.info(message); 31 | } 32 | } 33 | 34 | @Override 35 | public boolean isDebug() { 36 | return debug; 37 | } 38 | 39 | @Override 40 | public void setDebug(boolean debug) { 41 | this.debug = debug; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /core/src/main/java/dev/kejona/crossplatforms/Logger.java: -------------------------------------------------------------------------------- 1 | package dev.kejona.crossplatforms; 2 | 3 | public abstract class Logger { 4 | 5 | private static Logger INSTANCE; 6 | 7 | public Logger() { 8 | INSTANCE = this; 9 | if (Boolean.getBoolean("CrossplatForms.Debug")) { 10 | setDebug(true); 11 | } 12 | } 13 | 14 | public static Logger get() { 15 | if (INSTANCE == null) { 16 | throw new IllegalStateException("A logger has not yet been constructed."); 17 | } 18 | return INSTANCE; 19 | } 20 | 21 | public void log(Level level, String message) { 22 | switch (level) { 23 | case WARN: 24 | warn(message); 25 | break; 26 | case SEVERE: 27 | severe(message); 28 | break; 29 | case DEBUG: 30 | debug(message); 31 | break; 32 | default: 33 | info(message); 34 | break; 35 | } 36 | } 37 | 38 | /** 39 | * Dumps the current stack if debug mode is enabled. 40 | */ 41 | public void debugStack() { 42 | if (isDebug()) { 43 | Thread.dumpStack(); 44 | } 45 | } 46 | 47 | public abstract void info(String message); 48 | public abstract void warn(String message); 49 | public abstract void severe(String message); 50 | public abstract void debug(String message); 51 | 52 | public abstract boolean isDebug(); 53 | public abstract void setDebug(boolean debug); 54 | 55 | public enum Level { 56 | INFO, 57 | WARN, 58 | SEVERE, 59 | DEBUG 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /core/src/main/java/dev/kejona/crossplatforms/Platform.java: -------------------------------------------------------------------------------- 1 | package dev.kejona.crossplatforms; 2 | 3 | import dev.kejona.crossplatforms.handler.BedrockHandler; 4 | import dev.kejona.crossplatforms.handler.FormPlayer; 5 | 6 | import java.util.UUID; 7 | 8 | public enum Platform { 9 | BEDROCK, 10 | JAVA, 11 | ALL; 12 | 13 | /** 14 | * Checks if a given {@link Platform} matches the player's platform. 15 | * @param player The player to lookup 16 | * @param platform The platform to compare to the player 17 | * @param handler Used to check if the player is Bedrock or not 18 | * @return True if the player is considered to be in, or match the given {@link Platform}. 19 | */ 20 | public static boolean matches(UUID player, Platform platform, BedrockHandler handler) { 21 | if (platform == Platform.ALL) { 22 | return true; 23 | } 24 | 25 | boolean isBedrock = handler.isBedrockPlayer(player); 26 | if (platform == Platform.BEDROCK) { 27 | return isBedrock; 28 | } else { 29 | return !isBedrock; 30 | } 31 | } 32 | 33 | public static boolean matches(FormPlayer player, Platform platform, BedrockHandler handler) { 34 | return matches(player.getUuid(), platform, handler); 35 | } 36 | 37 | public boolean matches(UUID player, BedrockHandler handler) { 38 | return matches(player, this, handler); 39 | } 40 | 41 | public boolean matches(FormPlayer player, BedrockHandler handler) { 42 | return matches(player.getUuid(), handler); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /core/src/main/java/dev/kejona/crossplatforms/SkinCache.java: -------------------------------------------------------------------------------- 1 | package dev.kejona.crossplatforms; 2 | 3 | import com.google.common.cache.Cache; 4 | import com.google.common.cache.CacheBuilder; 5 | import dev.kejona.crossplatforms.handler.FormPlayer; 6 | import dev.kejona.crossplatforms.utils.SkinUtils; 7 | import org.checkerframework.checker.nullness.qual.Nullable; 8 | 9 | import javax.annotation.Nonnull; 10 | import java.util.UUID; 11 | import java.util.concurrent.TimeUnit; 12 | 13 | public class SkinCache { 14 | 15 | // See https://mc-heads.net/ 16 | private static final String AVATAR_ENDPOINT = "https://mc-heads.net/avatar/"; 17 | // See https://mc-heads.net/minecraft/mhf 18 | private static final String STEVE = "MHF_Steve"; 19 | 20 | private static final Logger LOGGER = Logger.get(); 21 | 22 | private final Cache avatars = CacheBuilder.newBuilder() 23 | .expireAfterWrite(5, TimeUnit.MINUTES) 24 | .build(); 25 | 26 | @Nullable 27 | public String getAvatarUrl(FormPlayer player) { 28 | UUID uuid = player.getUuid(); 29 | try { 30 | return avatars.get(uuid, () -> getAvatarUrl(uuid, player.getEncodedSkinData())); 31 | } catch (Throwable e) { 32 | LOGGER.warn("Exception while computing avatar url of " + player.getName()); 33 | e.printStackTrace(); 34 | return null; 35 | } 36 | } 37 | 38 | @Nonnull 39 | public static String getAvatarUrl(UUID uuid, @Nullable String encodedData) { 40 | if (encodedData == null) { 41 | return AVATAR_ENDPOINT + STEVE; 42 | } 43 | // todo: calculate default skin if no encoded data or failed to read 44 | 45 | try { 46 | String avatarUrl = AVATAR_ENDPOINT + SkinUtils.idFromEncoding(encodedData); 47 | LOGGER.debug("Avatar URL for " + uuid + ": " + avatarUrl); 48 | return avatarUrl; 49 | } catch (Exception e) { 50 | if (LOGGER.isDebug()) { 51 | LOGGER.debug("Failed to get avatar url for " + uuid); 52 | e.printStackTrace(); 53 | } 54 | return AVATAR_ENDPOINT + STEVE; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /core/src/main/java/dev/kejona/crossplatforms/action/Action.java: -------------------------------------------------------------------------------- 1 | package dev.kejona.crossplatforms.action; 2 | 3 | import dev.kejona.crossplatforms.handler.FormPlayer; 4 | import dev.kejona.crossplatforms.resolver.Resolver; 5 | import dev.kejona.crossplatforms.serialize.KeyedType; 6 | 7 | import javax.annotation.Nonnull; 8 | 9 | public interface Action extends KeyedType { 10 | 11 | /** 12 | * Affects a player 13 | * @param player The player to affect 14 | */ 15 | void affectPlayer(@Nonnull FormPlayer player, @Nonnull Resolver resolver, @Nonnull T executor); 16 | 17 | // Static methods for batching multiple actions together: 18 | 19 | static void affectPlayer(@Nonnull FormPlayer player, 20 | @Nonnull Iterable> actions, 21 | @Nonnull Resolver resolver, 22 | @Nonnull T executor) { 23 | 24 | actions.forEach(a -> a.affectPlayer(player, resolver, executor)); 25 | } 26 | 27 | /** 28 | * Native actions whose type will always be successfully inferred 29 | */ 30 | static boolean typeInferrable(String type) { 31 | return type.equals("form") 32 | || type.equals("server") 33 | || type.equals("message") || type.equals("messages") 34 | || type.equals("commands"); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /core/src/main/java/dev/kejona/crossplatforms/action/ActionSerializer.java: -------------------------------------------------------------------------------- 1 | package dev.kejona.crossplatforms.action; 2 | 3 | import dev.kejona.crossplatforms.serialize.TypeResolver; 4 | import dev.kejona.crossplatforms.serialize.KeyedTypeSerializer; 5 | import io.leangen.geantyref.TypeToken; 6 | import org.spongepowered.configurate.serialize.TypeSerializerCollection; 7 | 8 | public class ActionSerializer { 9 | 10 | public static final TypeToken> TYPE = new TypeToken>() {}; 11 | 12 | private final KeyedTypeSerializer> serializer = new KeyedTypeSerializer<>(); 13 | 14 | public void register(String typeId, Class> type) { 15 | serializer.registerType(typeId, type); 16 | } 17 | 18 | public void register(String typeId, Class> type, TypeResolver resolver) { 19 | serializer.registerType(typeId, type, resolver); 20 | } 21 | 22 | public void register(TypeSerializerCollection.Builder builder) { 23 | builder.register(TYPE, serializer); // can't register exact because Action has type parameters 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /core/src/main/java/dev/kejona/crossplatforms/action/CommandsAction.java: -------------------------------------------------------------------------------- 1 | package dev.kejona.crossplatforms.action; 2 | 3 | import com.google.inject.Inject; 4 | import dev.kejona.crossplatforms.command.DispatchableCommand; 5 | import dev.kejona.crossplatforms.handler.FormPlayer; 6 | import dev.kejona.crossplatforms.handler.ServerHandler; 7 | import dev.kejona.crossplatforms.resolver.Resolver; 8 | import dev.kejona.crossplatforms.serialize.TypeResolver; 9 | import org.spongepowered.configurate.objectmapping.ConfigSerializable; 10 | import org.spongepowered.configurate.objectmapping.meta.Required; 11 | 12 | import javax.annotation.Nonnull; 13 | import java.util.List; 14 | import java.util.stream.Collectors; 15 | 16 | @ConfigSerializable 17 | public class CommandsAction implements GenericAction { 18 | 19 | public static final String TYPE = "commands"; 20 | 21 | private final transient ServerHandler serverHandler; 22 | 23 | @Required 24 | private List commands; 25 | 26 | @Inject 27 | public CommandsAction(ServerHandler serverHandler) { 28 | this.serverHandler = serverHandler; 29 | } 30 | 31 | @Override 32 | public void affectPlayer(@Nonnull FormPlayer player, @Nonnull Resolver resolver) { 33 | if (commands != null) { 34 | List resolved = commands.stream() 35 | .map(cmd -> cmd.withCommand(resolver.apply(cmd.getCommand()))) 36 | .collect(Collectors.toList()); 37 | 38 | serverHandler.dispatchCommands(player.getUuid(), resolved); 39 | } 40 | } 41 | 42 | @Override 43 | public String type() { 44 | return TYPE; 45 | } 46 | 47 | @Override 48 | public boolean serializeWithType() { 49 | return false; // can infer based off commands node 50 | } 51 | 52 | public static void register(ActionSerializer serializer) { 53 | serializer.register(TYPE, CommandsAction.class, typeResolver()); 54 | } 55 | 56 | private static TypeResolver typeResolver() { 57 | return TypeResolver.listOrScalar("commands", TYPE); 58 | } 59 | } 60 | 61 | -------------------------------------------------------------------------------- /core/src/main/java/dev/kejona/crossplatforms/action/GenericAction.java: -------------------------------------------------------------------------------- 1 | package dev.kejona.crossplatforms.action; 2 | 3 | import dev.kejona.crossplatforms.handler.FormPlayer; 4 | import dev.kejona.crossplatforms.resolver.Resolver; 5 | 6 | import javax.annotation.Nonnull; 7 | 8 | public interface GenericAction extends Action { 9 | 10 | @Override 11 | default void affectPlayer(@Nonnull FormPlayer player, @Nonnull Resolver resolver, @Nonnull Object executor) { 12 | affectPlayer(player, resolver); 13 | } 14 | 15 | void affectPlayer(@Nonnull FormPlayer player, @Nonnull Resolver resolver); 16 | } 17 | -------------------------------------------------------------------------------- /core/src/main/java/dev/kejona/crossplatforms/action/ServerAction.java: -------------------------------------------------------------------------------- 1 | package dev.kejona.crossplatforms.action; 2 | 3 | import dev.kejona.crossplatforms.Logger; 4 | import dev.kejona.crossplatforms.handler.FormPlayer; 5 | import dev.kejona.crossplatforms.resolver.Resolver; 6 | import dev.kejona.crossplatforms.serialize.TypeResolver; 7 | import net.kyori.adventure.text.Component; 8 | import net.kyori.adventure.text.format.NamedTextColor; 9 | import org.spongepowered.configurate.objectmapping.ConfigSerializable; 10 | import org.spongepowered.configurate.objectmapping.meta.Required; 11 | 12 | import javax.annotation.Nonnull; 13 | 14 | @ConfigSerializable 15 | public class ServerAction implements GenericAction { 16 | 17 | private static final String TYPE = "server"; 18 | 19 | @Required 20 | private String server; 21 | 22 | @Override 23 | public void affectPlayer(@Nonnull FormPlayer player, @Nonnull Resolver resolver) { 24 | String server = resolver.apply(this.server); 25 | if (!player.switchBackendServer(server)) { 26 | Logger.get().warn("Server '" + server + "' does not exist! Not transferring " + player.getName()); 27 | player.sendMessage(Component.text("Server ", NamedTextColor.RED) 28 | .append(Component.text(server)) 29 | .append(Component.text(" doesn't exist.", NamedTextColor.RED))); 30 | } 31 | } 32 | 33 | @Override 34 | public String type() { 35 | return TYPE; 36 | } 37 | 38 | @Override 39 | public boolean serializeWithType() { 40 | return false; // can infer based off server node 41 | } 42 | 43 | public static void register(ActionSerializer serializer) { 44 | serializer.register(TYPE, ServerAction.class, typeResolver()); 45 | } 46 | 47 | private static TypeResolver typeResolver() { 48 | return node -> { 49 | if (node.node("server").getString() != null) { 50 | return TYPE; 51 | } else { 52 | return null; 53 | } 54 | }; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /core/src/main/java/dev/kejona/crossplatforms/command/CommandType.java: -------------------------------------------------------------------------------- 1 | package dev.kejona.crossplatforms.command; 2 | 3 | public enum CommandType { 4 | REGISTER, 5 | INTERCEPT_PASS, 6 | INTERCEPT_CANCEL 7 | } 8 | -------------------------------------------------------------------------------- /core/src/main/java/dev/kejona/crossplatforms/command/DispatchableCommand.java: -------------------------------------------------------------------------------- 1 | package dev.kejona.crossplatforms.command; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.Getter; 5 | import lombok.ToString; 6 | 7 | @ToString 8 | @EqualsAndHashCode 9 | @Getter 10 | public class DispatchableCommand { 11 | 12 | private final boolean player; 13 | private final String command; 14 | private final boolean op; 15 | 16 | /** 17 | * Used for running commands as a player or as the console 18 | * 19 | * @param player The player to run the command as. null for console. 20 | * @param command The command string to run 21 | * @param op If given a player, whether or not the player should be temporarily op'd when running the command. 22 | */ 23 | public DispatchableCommand(boolean player, String command, boolean op) { 24 | if (!player && op) { 25 | throw new IllegalArgumentException("player is false but op is true"); 26 | } 27 | 28 | this.player = player; 29 | this.command = command; 30 | this.op = op; 31 | } 32 | 33 | /** 34 | * Create a dispatchable command for the server console 35 | * @param command The command to run 36 | */ 37 | public DispatchableCommand(String command) { 38 | this(false, command, false); 39 | } 40 | 41 | public DispatchableCommand withCommand(String command) { 42 | if (this.command.equals(command)) { 43 | return this; 44 | } else { 45 | return new DispatchableCommand(this.player, command, this.op); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /core/src/main/java/dev/kejona/crossplatforms/command/DispatchableCommandSerializer.java: -------------------------------------------------------------------------------- 1 | package dev.kejona.crossplatforms.command; 2 | 3 | import org.checkerframework.checker.nullness.qual.Nullable; 4 | import org.spongepowered.configurate.ConfigurationNode; 5 | import org.spongepowered.configurate.serialize.SerializationException; 6 | import org.spongepowered.configurate.serialize.TypeSerializer; 7 | 8 | import java.lang.reflect.Type; 9 | 10 | public class DispatchableCommandSerializer implements TypeSerializer { 11 | 12 | private static final String PLAYER_PREFIX = "player;"; 13 | private static final String OP_PREFIX = "op;"; 14 | private static final String CONSOLE_PREFIX = "console;"; 15 | 16 | public static DispatchableCommand deserialize(String value) { 17 | boolean player = value.startsWith(PLAYER_PREFIX); 18 | boolean op = value.startsWith(OP_PREFIX); 19 | boolean console = value.startsWith(CONSOLE_PREFIX); 20 | if (player || op || console) { 21 | // Split the input into two strings between ";" and get the second string 22 | String command = value.split(";", 2)[1].trim(); 23 | if (player) { 24 | return new DispatchableCommand(true, command, false); 25 | } else if (op) { 26 | return new DispatchableCommand(true, command, true); 27 | } else { 28 | return new DispatchableCommand(command); 29 | } 30 | } else { 31 | return new DispatchableCommand(value); // console 32 | } 33 | } 34 | 35 | @Override 36 | public DispatchableCommand deserialize(Type type, ConfigurationNode node) throws SerializationException { 37 | String raw = node.getString(); 38 | if (raw == null || raw.isEmpty()) { 39 | throw new SerializationException("Command at " + node.path() + " is empty!"); 40 | } 41 | return deserialize(raw); 42 | } 43 | 44 | @Override 45 | public void serialize(Type type, @Nullable DispatchableCommand command, ConfigurationNode node) throws SerializationException { 46 | if (command == null) { 47 | node.raw(null); 48 | return; 49 | } 50 | 51 | String prefix; 52 | if (command.isPlayer()) { 53 | if (command.isOp()) { 54 | prefix = OP_PREFIX; 55 | } else { 56 | prefix = PLAYER_PREFIX; 57 | } 58 | } else { 59 | prefix = CONSOLE_PREFIX; 60 | } 61 | 62 | node.set(prefix + " " + command.getCommand()); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /core/src/main/java/dev/kejona/crossplatforms/command/FormsCommand.java: -------------------------------------------------------------------------------- 1 | package dev.kejona.crossplatforms.command; 2 | 3 | import cloud.commandframework.Command; 4 | import cloud.commandframework.CommandManager; 5 | import dev.kejona.crossplatforms.Constants; 6 | import dev.kejona.crossplatforms.CrossplatForms; 7 | import lombok.RequiredArgsConstructor; 8 | 9 | /** 10 | * Extending classes are expected to register one or more commands with the {@link CommandManager} given in {@link FormsCommand#register(CommandManager, Command.Builder)} 11 | * Additionally, it is expected that any necessary permissions will be registered using {@link CrossplatForms#getServerHandler()} 12 | */ 13 | @RequiredArgsConstructor 14 | public abstract class FormsCommand { 15 | 16 | public static final String PERMISSION_BASE = Constants.Id() + ".command."; 17 | 18 | protected final CrossplatForms crossplatForms; 19 | 20 | /** 21 | * Register a command 22 | * @param manager The command manager to register to 23 | * @param defaultBuilder The command builder to be used if registering a subcommand to the default base command 24 | */ 25 | public abstract void register(CommandManager manager, Command.Builder defaultBuilder); 26 | } 27 | -------------------------------------------------------------------------------- /core/src/main/java/dev/kejona/crossplatforms/command/custom/CustomCommand.java: -------------------------------------------------------------------------------- 1 | package dev.kejona.crossplatforms.command.custom; 2 | 3 | import com.google.inject.Inject; 4 | import dev.kejona.crossplatforms.Platform; 5 | import dev.kejona.crossplatforms.action.Action; 6 | import dev.kejona.crossplatforms.command.CommandType; 7 | import dev.kejona.crossplatforms.handler.BedrockHandler; 8 | import dev.kejona.crossplatforms.handler.FormPlayer; 9 | import dev.kejona.crossplatforms.handler.Placeholders; 10 | import dev.kejona.crossplatforms.resolver.Resolver; 11 | import dev.kejona.crossplatforms.serialize.KeyedType; 12 | import lombok.Getter; 13 | import lombok.Setter; 14 | import lombok.ToString; 15 | import org.spongepowered.configurate.objectmapping.meta.NodeKey; 16 | import org.spongepowered.configurate.objectmapping.meta.Required; 17 | 18 | import java.util.Collections; 19 | import java.util.List; 20 | 21 | @ToString 22 | @Getter 23 | @SuppressWarnings("FieldMayBeFinal") 24 | public abstract class CustomCommand implements KeyedType { 25 | 26 | @Inject 27 | private transient BedrockHandler bedrockHandler; 28 | 29 | @Inject 30 | private transient Placeholders placeholders; 31 | 32 | @Required 33 | @NodeKey 34 | private String identifier = null; 35 | 36 | private Platform platform = Platform.ALL; 37 | 38 | @Required 39 | private CommandType method = null; 40 | 41 | private List> actions = Collections.emptyList(); 42 | private List> bedrockActions = Collections.emptyList(); 43 | private List> javaActions = Collections.emptyList(); 44 | 45 | @Setter 46 | private String permission; 47 | 48 | public void run(FormPlayer player) { 49 | Resolver resolver = placeholders.resolver(player); 50 | Action.affectPlayer(player, actions, resolver, this); 51 | 52 | if (bedrockHandler.isBedrockPlayer(player.getUuid())) { 53 | Action.affectPlayer(player, bedrockActions, resolver, this); 54 | } else { 55 | Action.affectPlayer(player, javaActions, resolver, this); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /core/src/main/java/dev/kejona/crossplatforms/command/custom/CustomCommandSerializer.java: -------------------------------------------------------------------------------- 1 | package dev.kejona.crossplatforms.command.custom; 2 | 3 | import dev.kejona.crossplatforms.serialize.KeyedTypeSerializer; 4 | 5 | public class CustomCommandSerializer extends KeyedTypeSerializer { 6 | 7 | public CustomCommandSerializer() { 8 | super("method"); 9 | registerType(RegisteredCommand.TYPE, RegisteredCommand.class); 10 | registerType(InterceptCommand.INTERCEPT_PASS_TYPE, InterceptCommand.class); 11 | registerType(InterceptCommand.INTERCEPT_CANCEL_TYPE, InterceptCommand.class); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /core/src/main/java/dev/kejona/crossplatforms/command/custom/InterceptCommand.java: -------------------------------------------------------------------------------- 1 | package dev.kejona.crossplatforms.command.custom; 2 | 3 | import dev.kejona.crossplatforms.command.CommandType; 4 | import lombok.Getter; 5 | import lombok.ToString; 6 | import org.checkerframework.checker.nullness.qual.Nullable; 7 | import org.spongepowered.configurate.objectmapping.ConfigSerializable; 8 | 9 | import java.util.Locale; 10 | import java.util.regex.Pattern; 11 | 12 | @ToString(callSuper = true) 13 | @Getter 14 | @ConfigSerializable 15 | @SuppressWarnings("FieldMayBeFinal") 16 | public class InterceptCommand extends CustomCommand { 17 | 18 | public static final String INTERCEPT_PASS_TYPE = CommandType.INTERCEPT_PASS.name().toLowerCase(Locale.ROOT); 19 | public static final String INTERCEPT_CANCEL_TYPE = CommandType.INTERCEPT_CANCEL.name().toLowerCase(Locale.ROOT); 20 | 21 | @Nullable 22 | private Pattern pattern; 23 | 24 | @Nullable 25 | private String exact; 26 | 27 | @Nullable 28 | @Override 29 | public String getPermission() { 30 | return super.getPermission(); 31 | } 32 | 33 | @Override 34 | public String type() { 35 | return getMethod().name().toLowerCase(Locale.ROOT); 36 | // either "intercept_pass" or "intercept_cancel" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /core/src/main/java/dev/kejona/crossplatforms/command/custom/InterceptCommandCache.java: -------------------------------------------------------------------------------- 1 | package dev.kejona.crossplatforms.command.custom; 2 | 3 | import dev.kejona.crossplatforms.handler.ServerHandler; 4 | import org.checkerframework.checker.nullness.qual.Nullable; 5 | 6 | import java.util.ArrayList; 7 | import java.util.HashMap; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.Objects; 11 | 12 | /** 13 | * Small map-wrapper class to facilitate {@link ServerHandler} implementations if desired 14 | */ 15 | public abstract class InterceptCommandCache implements ServerHandler { 16 | 17 | private final Map exactCommands = new HashMap<>(); 18 | private final List patternCommands = new ArrayList<>(); 19 | 20 | @Override 21 | public void registerInterceptCommand(InterceptCommand proxyCommand) { 22 | String exact = proxyCommand.getExact(); 23 | if (exact != null) { 24 | exactCommands.put(proxyCommand.getExact(), proxyCommand); 25 | } else { 26 | Objects.requireNonNull(proxyCommand.getPattern()); 27 | patternCommands.add(proxyCommand); 28 | } 29 | } 30 | 31 | @Override 32 | public void clearInterceptCommands() { 33 | exactCommands.clear(); 34 | patternCommands.clear(); 35 | } 36 | 37 | @Nullable 38 | public InterceptCommand findCommand(String input) { 39 | // attempt to find an exact match 40 | InterceptCommand command = exactCommands.get(input); 41 | if (command == null) { 42 | // no command found for this exact string, try finding something that matches 43 | for (InterceptCommand patternCommand : patternCommands) { 44 | if (Objects.requireNonNull(patternCommand.getPattern()).matcher(input).matches()) { 45 | command = patternCommand; 46 | break; 47 | } 48 | } 49 | } 50 | return command; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /core/src/main/java/dev/kejona/crossplatforms/command/custom/RegisteredCommand.java: -------------------------------------------------------------------------------- 1 | package dev.kejona.crossplatforms.command.custom; 2 | 3 | import dev.kejona.crossplatforms.command.CommandType; 4 | import lombok.ToString; 5 | import org.spongepowered.configurate.objectmapping.ConfigSerializable; 6 | import org.spongepowered.configurate.objectmapping.meta.PostProcess; 7 | 8 | import java.util.Locale; 9 | 10 | @ToString(callSuper = true) 11 | @ConfigSerializable 12 | public class RegisteredCommand extends CustomCommand { 13 | 14 | public static final String TYPE = CommandType.REGISTER.name().toLowerCase(Locale.ROOT); 15 | 16 | private Literals command; 17 | 18 | private transient boolean enable = true; 19 | 20 | public Literals literals() { 21 | return command; 22 | } 23 | 24 | public void enable(boolean state) { 25 | this.enable = state; 26 | } 27 | 28 | public boolean isEnabled() { 29 | return enable; 30 | } 31 | 32 | @Override 33 | public String type() { 34 | return TYPE; 35 | } 36 | 37 | @PostProcess 38 | private void postProcess() { 39 | if (command == null) { 40 | command = Literals.of(new String[]{getIdentifier()}); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /core/src/main/java/dev/kejona/crossplatforms/command/defaults/DefaultCommands.java: -------------------------------------------------------------------------------- 1 | package dev.kejona.crossplatforms.command.defaults; 2 | 3 | import cloud.commandframework.minecraft.extras.MinecraftHelp; 4 | import com.google.common.collect.ImmutableList; 5 | import dev.kejona.crossplatforms.CrossplatForms; 6 | import dev.kejona.crossplatforms.command.CommandOrigin; 7 | import dev.kejona.crossplatforms.command.FormsCommand; 8 | import lombok.Getter; 9 | 10 | import java.util.List; 11 | 12 | public final class DefaultCommands { 13 | 14 | @Getter 15 | private final List commands; 16 | 17 | public DefaultCommands(CrossplatForms instance, MinecraftHelp minecraftHelp) { 18 | 19 | commands = ImmutableList.of( 20 | new HelpCommand(instance, minecraftHelp), 21 | new ListCommand(instance), 22 | new OpenCommand(instance), 23 | new InspectCommand(instance), 24 | new IdentifyCommand(instance), 25 | new VersionCommand(instance), 26 | new ReloadCommand(instance) 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /core/src/main/java/dev/kejona/crossplatforms/command/defaults/HelpCommand.java: -------------------------------------------------------------------------------- 1 | package dev.kejona.crossplatforms.command.defaults; 2 | 3 | import cloud.commandframework.Command; 4 | import cloud.commandframework.CommandManager; 5 | import cloud.commandframework.arguments.standard.StringArgument; 6 | import cloud.commandframework.minecraft.extras.MinecraftHelp; 7 | import dev.kejona.crossplatforms.CrossplatForms; 8 | import dev.kejona.crossplatforms.command.CommandOrigin; 9 | import dev.kejona.crossplatforms.command.FormsCommand; 10 | 11 | public class HelpCommand extends FormsCommand { 12 | 13 | public static final String NAME = "help"; 14 | public static final String PERMISSION = PERMISSION_BASE + NAME; 15 | 16 | private final MinecraftHelp minecraftHelp; 17 | 18 | public HelpCommand(CrossplatForms crossplatForms, MinecraftHelp minecraftHelp) { 19 | super(crossplatForms); 20 | this.minecraftHelp = minecraftHelp; 21 | } 22 | 23 | @Override 24 | public void register(CommandManager manager, Command.Builder defaultBuilder) { 25 | manager.command(defaultBuilder 26 | .literal(NAME) 27 | .argument(StringArgument.optional("query", StringArgument.StringMode.GREEDY)) 28 | .handler(context -> minecraftHelp.queryCommands(context.getOrDefault("query", ""), context.getSender())) 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /core/src/main/java/dev/kejona/crossplatforms/command/defaults/ReloadCommand.java: -------------------------------------------------------------------------------- 1 | package dev.kejona.crossplatforms.command.defaults; 2 | 3 | import cloud.commandframework.Command; 4 | import cloud.commandframework.CommandManager; 5 | import dev.kejona.crossplatforms.CrossplatForms; 6 | import dev.kejona.crossplatforms.command.CommandOrigin; 7 | import dev.kejona.crossplatforms.command.FormsCommand; 8 | import dev.kejona.crossplatforms.reloadable.ReloadableRegistry; 9 | 10 | public class ReloadCommand extends FormsCommand { 11 | 12 | public static final String NAME = "reload"; 13 | public static final String PERMISSION = PERMISSION_BASE + NAME; 14 | 15 | public ReloadCommand(CrossplatForms crossplatForms) { 16 | super(crossplatForms); 17 | } 18 | 19 | @Override 20 | public void register(CommandManager manager, Command.Builder defaultBuilder) { 21 | manager.command(defaultBuilder 22 | .literal(NAME) 23 | .permission(PERMISSION) 24 | .handler(context -> { 25 | CommandOrigin origin = context.getSender(); 26 | boolean success = ReloadableRegistry.reloadAll(); 27 | if (!origin.isConsole()) { 28 | // reloadable registry handles console messages 29 | if (success) { 30 | origin.sendMessage("Successfully reloaded"); 31 | } else { 32 | origin.severe("There was an error reloading something! Please check the server console for further information."); 33 | } 34 | } 35 | }) 36 | .build()); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /core/src/main/java/dev/kejona/crossplatforms/command/defaults/VersionCommand.java: -------------------------------------------------------------------------------- 1 | package dev.kejona.crossplatforms.command.defaults; 2 | 3 | import cloud.commandframework.Command; 4 | import cloud.commandframework.CommandManager; 5 | import dev.kejona.crossplatforms.Constants; 6 | import dev.kejona.crossplatforms.CrossplatForms; 7 | import dev.kejona.crossplatforms.command.CommandOrigin; 8 | import dev.kejona.crossplatforms.command.FormsCommand; 9 | 10 | public class VersionCommand extends FormsCommand { 11 | 12 | public static final String NAME = "version"; 13 | public static final String PERMISSION = PERMISSION_BASE + NAME; 14 | 15 | public VersionCommand(CrossplatForms crossplatForms) { 16 | super(crossplatForms); 17 | } 18 | 19 | @Override 20 | public void register(CommandManager manager, Command.Builder defaultBuilder) { 21 | manager.command(defaultBuilder.literal(NAME) 22 | .permission(PERMISSION) 23 | .handler(context -> { 24 | CommandOrigin origin = context.getSender(); 25 | origin.sendMessage("CrossplatForms version:"); 26 | origin.sendMessage("Version: " + Constants.version() + ", Branch: " + Constants.branch() + ", Build: " + Constants.buildNumber() + ", Commit: " + Constants.commit()); 27 | }) 28 | .build()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /core/src/main/java/dev/kejona/crossplatforms/config/Configuration.java: -------------------------------------------------------------------------------- 1 | package dev.kejona.crossplatforms.config; 2 | 3 | import lombok.Getter; 4 | import org.spongepowered.configurate.objectmapping.ConfigSerializable; 5 | import org.spongepowered.configurate.objectmapping.meta.Required; 6 | import org.spongepowered.configurate.objectmapping.meta.Setting; 7 | 8 | @Getter 9 | @ConfigSerializable 10 | public abstract class Configuration { 11 | 12 | public static final String VERSION_KEY = "config-version"; 13 | 14 | @Required 15 | @Setting(VERSION_KEY) 16 | private int version; 17 | } 18 | -------------------------------------------------------------------------------- /core/src/main/java/dev/kejona/crossplatforms/config/ConfigurationModule.java: -------------------------------------------------------------------------------- 1 | package dev.kejona.crossplatforms.config; 2 | 3 | import com.google.inject.AbstractModule; 4 | import com.google.inject.util.Providers; 5 | import dev.kejona.crossplatforms.SkinCache; 6 | import dev.kejona.crossplatforms.handler.BedrockHandler; 7 | import dev.kejona.crossplatforms.handler.Placeholders; 8 | import dev.kejona.crossplatforms.handler.ServerHandler; 9 | import dev.kejona.crossplatforms.interfacing.Interfacer; 10 | import lombok.AllArgsConstructor; 11 | 12 | @AllArgsConstructor 13 | public class ConfigurationModule extends AbstractModule { 14 | 15 | private final Interfacer interfacer; 16 | private final BedrockHandler bedrockHandler; 17 | private final ServerHandler serverHandler; 18 | private final Placeholders placeholders; 19 | 20 | @Override 21 | protected void configure() { 22 | // remember: explicit bindings cannot be set as required using binder().requireExplicitBindings() 23 | // because this module is used for creating configs - bindings are created on the fly (just in time) 24 | 25 | bind(Interfacer.class).toInstance(interfacer); 26 | 27 | // Hack to stop the instance from having its members being injected 28 | // which causes a ClassDefNotFound error if Cumulus is not present (EmptyBedrockHandler) 29 | bind(BedrockHandler.class).toProvider(Providers.of(bedrockHandler)); 30 | bind(ServerHandler.class).toInstance(serverHandler); 31 | bind(Placeholders.class).toInstance(placeholders); 32 | bind(SkinCache.class).asEagerSingleton(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /core/src/main/java/dev/kejona/crossplatforms/context/Context.java: -------------------------------------------------------------------------------- 1 | package dev.kejona.crossplatforms.context; 2 | 3 | import dev.kejona.crossplatforms.resolver.Resolver; 4 | 5 | public interface Context { 6 | 7 | Resolver resolver(); 8 | } 9 | -------------------------------------------------------------------------------- /core/src/main/java/dev/kejona/crossplatforms/context/PlayerContext.java: -------------------------------------------------------------------------------- 1 | package dev.kejona.crossplatforms.context; 2 | 3 | import dev.kejona.crossplatforms.handler.FormPlayer; 4 | import dev.kejona.crossplatforms.resolver.Resolver; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Getter; 7 | import lombok.experimental.Accessors; 8 | 9 | @Getter 10 | @Accessors(fluent = true) 11 | @AllArgsConstructor 12 | public class PlayerContext implements Context { 13 | 14 | private final FormPlayer player; 15 | private final Resolver resolver; 16 | } 17 | -------------------------------------------------------------------------------- /core/src/main/java/dev/kejona/crossplatforms/filler/FillerSerializer.java: -------------------------------------------------------------------------------- 1 | package dev.kejona.crossplatforms.filler; 2 | 3 | import dev.kejona.crossplatforms.serialize.TypeResolver; 4 | import dev.kejona.crossplatforms.serialize.KeyedTypeSerializer; 5 | import lombok.Getter; 6 | import lombok.experimental.Accessors; 7 | import org.spongepowered.configurate.serialize.TypeSerializerCollection; 8 | 9 | @Accessors(fluent = true) 10 | @Getter 11 | public class FillerSerializer { 12 | 13 | private final KeyedTypeSerializer optionFillerSerializer = new KeyedTypeSerializer<>(); 14 | private final KeyedTypeSerializer simpleFormFillerSerializer = new KeyedTypeSerializer<>(); 15 | 16 | public void filler(String typeId, Class fillerType) { 17 | optionFillerSerializer.registerType(typeId, fillerType); 18 | simpleFormFillerSerializer.registerType(typeId, fillerType); 19 | } 20 | 21 | public void filler(String typeId, Class fillerType, TypeResolver resolver) { 22 | optionFillerSerializer.registerType(typeId, fillerType, resolver); 23 | simpleFormFillerSerializer.registerType(typeId, fillerType, resolver); 24 | } 25 | 26 | public void register(TypeSerializerCollection.Builder builder) { 27 | builder.registerExact(OptionFiller.class, optionFillerSerializer); 28 | builder.registerExact(SimpleFormFiller.class, simpleFormFillerSerializer); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /core/src/main/java/dev/kejona/crossplatforms/filler/FillerUtils.java: -------------------------------------------------------------------------------- 1 | package dev.kejona.crossplatforms.filler; 2 | 3 | import org.checkerframework.checker.nullness.qual.Nullable; 4 | import org.jetbrains.annotations.Contract; 5 | 6 | import javax.annotation.Nonnull; 7 | import java.util.List; 8 | import java.util.regex.Matcher; 9 | import java.util.regex.Pattern; 10 | import java.util.stream.Stream; 11 | 12 | public final class FillerUtils { 13 | 14 | /** 15 | * Replaces part(s) of a "template" string with a given value. This is primarily meant for formatting defaultValue 16 | * with the optional template. 17 | * 18 | * @param template the template string to apply the key Pattern to 19 | * @param key the Pattern used for doing replacement in the template String 20 | * @param defaultValue The String that matches will be replaced with 21 | * @return a new string with the replacements performed if both template and default value are not null. 22 | * If defaultValue is null, template is returned. If template is null, it is considered that 23 | * not formatting required, so defaultValue is returned. 24 | */ 25 | @Nullable 26 | @Contract("_, _, null -> param1; null, _, !null -> param3; !null, _, !null -> !null") 27 | public static String replace(@Nullable String template, @Nonnull Pattern key, @Nullable String defaultValue) { 28 | if (defaultValue == null) { 29 | return template; // value is not present. return either an override the user specified, or null. 30 | } else if (template == null) { 31 | return defaultValue; // user did not specify formatting, still use the raw value 32 | } 33 | 34 | return key.matcher(template).replaceAll(Matcher.quoteReplacement(defaultValue)); 35 | } 36 | 37 | protected static void addAtIndex(Stream src, List dest, int index) { 38 | if (index < 0) { 39 | // index invalid or not set - silently ignore for config purposes 40 | src.forEachOrdered(dest::add); 41 | } else { 42 | src.forEachOrdered(e -> dest.add(index, e)); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /core/src/main/java/dev/kejona/crossplatforms/filler/OptionFiller.java: -------------------------------------------------------------------------------- 1 | package dev.kejona.crossplatforms.filler; 2 | 3 | import dev.kejona.crossplatforms.context.PlayerContext; 4 | import dev.kejona.crossplatforms.interfacing.bedrock.custom.Option; 5 | import dev.kejona.crossplatforms.serialize.KeyedType; 6 | 7 | import javax.annotation.Nonnull; 8 | import javax.annotation.Nullable; 9 | import java.util.List; 10 | import java.util.regex.Pattern; 11 | import java.util.stream.Stream; 12 | 13 | public interface OptionFiller extends KeyedType { 14 | 15 | Pattern PLACEHOLDER = Pattern.compile("%raw_text%"); 16 | 17 | default void fillOptions(List