diff --git a/.config/checkstyle/checkstyle.xml b/.config/checkstyle/checkstyle.xml new file mode 100644 index 00000000..eaf0a660 --- /dev/null +++ b/.config/checkstyle/checkstyle.xml @@ -0,0 +1,491 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.config/checkstyle/suppressions.xml b/.config/checkstyle/suppressions.xml new file mode 100644 index 00000000..24b61959 --- /dev/null +++ b/.config/checkstyle/suppressions.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + diff --git a/.config/spotbugs/suppressions.xml b/.config/spotbugs/suppressions.xml new file mode 100644 index 00000000..2ab22102 --- /dev/null +++ b/.config/spotbugs/suppressions.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..a87d264c --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# Linux start script should use lf +/gradlew text eol=lf + +# These are Windows script files and should use crlf +*.bat text eol=crlf + +# Binary files should be left untouched +*.jar binary diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 96fb9219..00000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -name: Bug Report -about: Report the problem that has occurred in our project. -title: "[BUG] " -labels: bug -assignees: '' ---- - -**Describe the bug** -A clear and concise description of the bug. - -**To Reproduce** -Steps to reproduce the behavior: -1. Set '...' in config to '...' -2. Do in game '....' -3. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Server Info (please complete the following information):** - - All Limbo plugins versions: - - [e.g. LimboAPI 1.0.4-SNAPSHOT, downloaded from https://github.com/Elytrium/LimboAPI/actions/runs/xxxxxx] - - [e.g. LimboFilter 1.0.3-rc3, downloaded from https://github.com/Elytrium/LimboAPI/actions/runs/xxxxxx] - - - /velocity dump link [e.g. https://dump.velocitypowered.com/abcdef.json] - -**Additional context** -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 00000000..23be9630 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,78 @@ +# Borrowed from ViaVersion +name: Bug Report +description: Report a bug or console error +labels: [ Unconfirmed ] + +body: + - type: markdown + attributes: + value: | + **Before reporting a bug, please see if using master/dev builds from https://modrinth.com/plugin/limboapi/ fixes your issue.** + Whenever you see fit, you can upload images or videos to any of the text fields. + - type: textarea + attributes: + label: "`/velocity dump` Output" + description: | + Run `/velocity dump` in the console or in the chat, then attach produced report file here or upload it to the mclo.gs. + value: | + ``` + Attach report file here or put the mclo.gs link or text here. + ``` + placeholder: Please do not remove the grave accents; simply replace the line of text in the middle. + validations: + required: true + - type: textarea + attributes: + label: Console Error + description: | + If you encounter warnings/errors in your console, **paste them with https://mclo.gs/ and put the paste link here**. + If the error is small/less than 10 lines, you may put it directly into this field. + value: | + ``` + Put the mclo.gs link or text here. + ``` + placeholder: Please do not remove the grave accents; simply replace the line of text in the middle. + validations: + required: false + - type: textarea + attributes: + label: Bug Description + description: | + Describe the unexpected behavior. + If you want to attach screenshots, use the comment field at the bottom of the page. + placeholder: | + Example: "Changed uuid causes tamed parrots and cats cannot be sat down." + validations: + required: true + - type: textarea + attributes: + label: Steps to Reproduce + description: | + List the steps on how we can reproduce the issue. Make sure we can easily understand what you mean with each step. + placeholder: | + Example: + 1. Подключиться к оффлайн серверу с LimboAuth с лицензионного клиента + 2. Приручить кота + 3. При правом клике кота нельзя посадить + validations: + required: true + - type: textarea + attributes: + label: Expected Behavior + description: | + Describe what exactly you expected to happen. + placeholder: | + Example: "Кот должен сесть." + validations: + required: true + - type: checkboxes + attributes: + label: Checklist + description: Make sure you have followed each of the steps outlined here. + options: + - label: I have included a Velocity dump. + required: true + - label: If applicable, I have included a paste (**not a screenshot**) of the error. + required: true + - label: I have tried the latest build(s) from https://modrinth.com/plugin/limboapi/ and the issue still persists. + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..9c646613 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,9 @@ +# Borrowed from ViaVersion +blank_issues_enabled: false +contact_links: + - name: Dev builds + url: https://modrinth.com/plugin/limboapi/ + about: Before reporting a bug, please check if using master/dev builds from our modrinth page fixes your issue. + - name: Discord + url: https://discord.gg/sxVzYv2dbR + about: For smaller issues or questions, you can also join our Discord server. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 5c4c3e89..00000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -name: Feature Request -about: Suggest an idea to improve our project. -title: "[ENHANCEMENT] " -labels: enhancement -assignees: '' ---- - -**Describe the feature you'd like to have implemented** -A clear and concise description of what you want to be added. - -**Is your feature request related to an existing problem? Please describe.** -A clear and concise description of what the problem is. - -**Additional context** -Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 00000000..8ab2b460 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,36 @@ +# Borrowed from ViaVersion +name: Feature Request +description: Suggest a feature to be added +labels: [ Feature Request ] + +body: + - type: textarea + attributes: + label: Problem Description + description: | + Describe the issue you are facing or why you need the feature to be added. + placeholder: | + I am always frustrated with... + validations: + required: true + - type: textarea + attributes: + label: Solution Description + description: | + Describe the solution you would like to see. + validations: + required: true + - type: textarea + attributes: + label: Alternatives + description: | + Describe alternatives you have considered. + validations: + required: false + - type: textarea + attributes: + label: Additional Info + description: | + Does the feature apply to any specific version or environment? + validations: + required: false diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 5e2a94dc..00000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,67 +0,0 @@ -name: Java CI with Gradle - -on: - push: - branches: - - master - pull_request: - branches: - - master - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4.2.2 - - name: Set up JDK - uses: actions/setup-java@v4.7.0 - with: - distribution: adopt - java-version: 21 - - name: Build LimboAPI - run: ./gradlew build - - name: Upload LimboAPI - uses: actions/upload-artifact@v4.6.2 - with: - name: LimboAPI - path: "*/build/libs/*.jar" - - uses: dev-drprasad/delete-tag-and-release@v0.2.1 - if: ${{ github.event_name == 'push' }} - with: - delete_release: true - tag_name: dev-build - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Find git version - id: git-version - run: echo "id=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT - - name: Find correct JAR - if: ${{ github.event_name == 'push' }} - id: find-jar - run: | - output="$(find plugin/build/libs/ ! -name "*-javadoc.jar" ! -name "*-sources.jar" -type f -printf "%f\n")" - echo "::set-output name=jarname::$output" - - name: Release the build - if: ${{ github.event_name == 'push' }} - uses: ncipollo/release-action@v1 - with: - artifacts: plugin/build/libs/${{ steps.find-jar.outputs.jarname }} - body: ${{ join(github.event.commits.*.message, '\n') }} - prerelease: true - name: Dev-build ${{ steps.git-version.outputs.id }} - tag: dev-build - - name: Upload to Modrinth - if: ${{ github.event_name == 'push' }} - uses: RubixDev/modrinth-upload@v1.0.0 - with: - token: ${{ secrets.MODRINTH_TOKEN }} - file_path: plugin/build/libs/${{ steps.find-jar.outputs.jarname }} - name: Dev-build ${{ steps.git-version.outputs.id }} - version: ${{ steps.git-version.outputs.id }} - changelog: ${{ join(github.event.commits.*.message, '\n') }} - game_versions: 1.7.2 - release_type: beta - loaders: velocity - featured: false - project_id: TZOteSf2 diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml new file mode 100644 index 00000000..15d6aa0a --- /dev/null +++ b/.github/workflows/gradle.yml @@ -0,0 +1,34 @@ +name: Java CI with Gradle + +on: [ push, pull_request ] + +jobs: + build: + # Only run on PRs if the source branch is on a different repo. We do not need to run everything twice. (From ViaVersion) + if: ${{ github.event_name != 'pull_request' || github.repository != github.event.pull_request.head.repo.full_name }} + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4.2.2 + - name: Cache Minecraft reports + uses: actions/cache@v4.1.2 + with: + path: | + plugin/build/minecraft/ + plugin/build/generated/minecraft/mappings/ + key: minecraft-${{ hashFiles('gradle.properties') }} # Cache key is based on gradle.properties because of gameVersions property + - name: Setup Java + uses: actions/setup-java@v4.5.0 + with: + distribution: temurin + java-version: 21 + cache: gradle + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v3.3.2 + - name: Build with Gradle + run: ./gradlew build + - name: Upload a Build Artifact + uses: actions/upload-artifact@v4.4.3 + with: + name: LimboAPI + path: "build/libs/limboapi-*.jar" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 00000000..53718c90 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,32 @@ +name: Publish to Modrinth + +on: + push: + branches: [ master ] + +jobs: + publish: + if: github.repository_owner == 'Elytrium' + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4.2.2 + - name: Cache Minecraft reports + uses: actions/cache@v4.1.2 + with: + path: | + plugin/build/minecraft/ + plugin/build/generated/minecraft/mappings/ + key: minecraft-${{ hashFiles('gradle.properties') }} # Cache key is based on gradle.properties because of gameVersions property + - name: Setup Java + uses: actions/setup-java@v4.5.0 + with: + distribution: temurin + java-version: 21 + cache: gradle + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v3.3.2 + - name: Publish + env: + MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }} + run: ./gradlew build modrinth diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 091ef034..00000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: Java CI with Gradle - -on: - release: - types: [published] - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4.2.2 - - name: Set up JDK - uses: actions/setup-java@v4.7.0 - with: - distribution: adopt - java-version: 21 - - name: Build LimboAPI - run: ./gradlew build - - name: Upload LimboAPI - uses: actions/upload-artifact@v4.6.2 - with: - name: LimboAPI - path: "*/build/libs/*.jar" - - name: Find correct JAR - id: find-jar - run: | - output="$(find plugin/build/libs/ ! -name "*-javadoc.jar" ! -name "*-sources.jar" -type f -printf "%f\n")" - echo "::set-output name=jarname::$output" - - name: Upload to the GitHub release - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ github.event.release.upload_url }} - asset_path: plugin/build/libs/${{ steps.find-jar.outputs.jarname }} - asset_name: ${{ steps.find-jar.outputs.jarname }} - asset_content_type: application/java-archive - - name: Upload to Modrinth - uses: RubixDev/modrinth-upload@v1.0.0 - with: - token: ${{ secrets.MODRINTH_TOKEN }} - file_path: plugin/build/libs/${{ steps.find-jar.outputs.jarname }} - name: Release ${{ github.event.release.tag_name }} - version: ${{ github.event.release.tag_name }} - changelog: ${{ github.event.release.body }} - game_versions: 1.7.2 - release_type: release - loaders: velocity - featured: true - project_id: TZOteSf2 diff --git a/.gitignore b/.gitignore index a8e27a8a..095f3435 100644 --- a/.gitignore +++ b/.gitignore @@ -1,25 +1,65 @@ -# IntelliJ user-specific stuff -.idea/ -*.iml - +### Java ### # Compiled class file *.class # Log file *.log -# Package Files +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # *.jar +*.war +*.nar +*.ear *.zip *.tar.gz *.rar -# Virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* +replay_pid* + +### JetBrains ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/* + +!.idea/codeStyles +!.idea/runConfigurations + +*.iml +modules.xml +*.ipr + +# File-based project format +*.iws +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +### Linux ### *~ -# Temporary files which can be created if a process still has a handle open of a deleted file +# temporary files which can be created if a process still has a handle open of a deleted file .fuse_hidden* # KDE directory preferences @@ -31,13 +71,14 @@ hs_err_pid* # .nfs files are created when an open file is removed but is still being accessed .nfs* +### macOS ### # General .DS_Store .AppleDouble .LSOverride # Icon must end with two \r -Icon +Icon # Thumbnails ._* @@ -58,6 +99,11 @@ Network Trash Folder Temporary Items .apdisk +### macOS Patch ### +# iCloud generated files +*.icloud + +### Windows ### # Windows thumbnail cache files Thumbs.db Thumbs.db:encryptable @@ -83,15 +129,29 @@ $RECYCLE.BIN/ # Windows shortcuts *.lnk -# Gradle +### Gradle ### .gradle -build/ - -# Gradle Patch **/build/ +!src/**/build/ -# Common working directory -run/ +# Ignore Gradle GUI config +gradle-app.setting # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) !gradle-wrapper.jar + +# Avoid ignore Gradle wrappper properties +!gradle-wrapper.properties + +# Cache of project +.gradletasknamecache + +# Eclipse Gradle plugin generated files +# Eclipse Core +.project +# JDT-specific (Eclipse Java Development Tools) +.classpath + +### Gradle Patch ### +# Java heap dump +*.hprof diff --git a/HEADER.txt b/HEADER.txt index 99a4f73c..21d1055b 100644 --- a/HEADER.txt +++ b/HEADER.txt @@ -1,14 +1,17 @@ -Copyright (C) 2021 - 2025 Elytrium +/* + * Copyright (C) $YEAR Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . diff --git a/HEADER_MCPROTOCOLLIB.txt b/HEADER_MCPROTOCOLLIB.txt index 47e87ed8..b021bc57 100644 --- a/HEADER_MCPROTOCOLLIB.txt +++ b/HEADER_MCPROTOCOLLIB.txt @@ -1,21 +1,13 @@ -This file is part of MCProtocolLib, licensed under the MIT License (MIT). +/* + * This file is part of MCProtocolLib, licensed under the MIT License. + * + * Copyright (C) 2013-2021 Steveice10 + * Copyright (C) 2021-2024 GeyserMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ -Copyright (C) 2013-2021 Steveice10 - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE -OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/HEADER_MIXED.txt b/HEADER_MIXED.txt index 74b3acbb..29b3c73a 100644 --- a/HEADER_MIXED.txt +++ b/HEADER_MIXED.txt @@ -1,31 +1,34 @@ -Copyright (C) 2021 - 2025 Elytrium +/* + * Copyright (C) $YEAR Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * This file contains some parts of Velocity, licensed under the GPLv3 License. + * + * Copyright (C) $YEAR Velocity Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . - -This file contains some parts of Velocity, licensed under the AGPLv3 License (AGPLv3). - -Copyright (C) 2018 Velocity Contributors - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . \ No newline at end of file diff --git a/api/HEADER.txt b/api/HEADER.txt index e54013e9..2f42957a 100644 --- a/api/HEADER.txt +++ b/api/HEADER.txt @@ -1,4 +1,7 @@ -Copyright (C) 2021 - 2025 Elytrium +/* + * Copyright (C) $YEAR Elytrium + * + * The LimboAPI (excluding the LimboAPI plugin) is licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in the api top-level directory. + */ -The LimboAPI (excluding the LimboAPI plugin) is licensed under the terms of the MIT License. For more details, -reference the LICENSE file in the api top-level directory. \ No newline at end of file diff --git a/api/build.gradle b/api/build.gradle index a6e298c4..120bfe35 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -1,89 +1,67 @@ -//file:noinspection GroovyAssignabilityCheck +//file:noinspection VulnerableLibrariesLocal -plugins { - id("java-library") +plugins() { id("maven-publish") } -compileJava() { - getOptions().getRelease().set(17) - getOptions().setEncoding("UTF-8") +group = "net.elytrium.limboapi" + +java() { + withSourcesJar() + withJavadocJar() } dependencies() { - compileOnly("com.velocitypowered:velocity-api:$velocityVersion") - api("net.elytrium.commons:config:$elytriumCommonsVersion") - api("net.elytrium.commons:utils:$elytriumCommonsVersion") - api("net.elytrium.commons:velocity:$elytriumCommonsVersion") - api("net.elytrium.commons:kyori:$elytriumCommonsVersion") - api("net.kyori:adventure-nbt:$adventureVersion") + api(libs.velocity.api) + api("net.kyori:adventure-nbt:4.25.0") + api("it.unimi.dsi:fastutil:8.5.18") - compileOnly("com.github.spotbugs:spotbugs-annotations:$spotbugsVersion") -} + compileOnly("net.elytrium.commons:config:1.2.3") + shadowApi("net.elytrium.commons:utils:1.2.3") + shadowApi("net.elytrium.commons:velocity:1.2.3") + shadowApi("net.elytrium.commons:kyori:1.2.3") -license() { - matching(includes: ["**/mcprotocollib/**"]) { - header = rootProject.file("HEADER_MCPROTOCOLLIB.txt") - } - - header = file("HEADER.txt") + compileOnly("com.github.spotbugs:spotbugs-annotations:4.7.3") } +//spotless() { +// //matching(includes: ["**/mcprotocollib/**"]) { +// // header = rootProject.file("HEADER_MCPROTOCOLLIB.txt") +// //} +// +// java() { +// licenseHeaderFile(file("HEADER.txt")) +// } +//} + javadoc() { + def options = options as StandardJavadocDocletOptions options.setEncoding("UTF-8") options.setSource("17") - options.links("https://docs.oracle.com/en/java/javase/11/docs/api/") options.addStringOption("Xdoclint:none", "-quiet") - if (JavaVersion.current() >= JavaVersion.VERSION_1_9 && JavaVersion.current() < JavaVersion.VERSION_12) { - options.addBooleanOption("-no-module-directories", true) - } -} - -tasks.register("sourcesJar", Jar) { - archiveClassifier = "sources" - from(sourceSets.main.getAllSource()) -} - -tasks.register("javadocJar", Jar) { - archiveClassifier = "javadoc" - from(javadoc) + options.links("https://docs.oracle.com/en/java/javase/17/docs/api/", "https://jd.papermc.io/velocity/3.4.0/", "https://jd.advntr.dev/nbt/4.25.0/") + options.tags( + "apiNote:a:API Note:", + "implSpec:a:Implementation Requirements:", + "implNote:a:Implementation Note:", + "sinceMinecraft:a:Since Minecraft:" + ) } publishing() { repositories() { - maven { - credentials { - setUsername(System.getenv("ELYTRIUM_MAVEN_USERNAME")) - setPassword(System.getenv("ELYTRIUM_MAVEN_PASSWORD")) + maven() { + name = "elytrium" + url = "https://maven.elytrium.net/repo/" + credentials() { + username = System.getenv("ELYTRIUM_MAVEN_USERNAME") + password = System.getenv("ELYTRIUM_MAVEN_PASSWORD") } - - setName("elytrium-repo") - setUrl("https://maven.elytrium.net/repo/") } } publications.create("publication", MavenPublication) { + // TODO pom from(components.java) - - artifact(javadocJar) - artifact(sourcesJar) } } - -artifacts() { - archives(javadocJar) - archives(sourcesJar) -} - -sourceSets.main.java.srcDir( - getTasks().register("generateTemplates", Copy) { - task -> { - String version = getVersion().contains("-") ? "${getVersion()} (git-${getCurrentShortRevision()})" : getVersion() - task.getInputs().properties("version": version) - task.from(file("src/main/templates")).into(getLayout().getBuildDirectory().dir("generated/sources/templates")) - task.expand("version": version) - } - }.map { - it.getOutputs() - } -) diff --git a/api/src/main/java/net/elytrium/limboapi/api/Limbo.java b/api/src/main/java/net/elytrium/limboapi/api/Limbo.java index 5f9d4cbb..9540b2a8 100644 --- a/api/src/main/java/net/elytrium/limboapi/api/Limbo.java +++ b/api/src/main/java/net/elytrium/limboapi/api/Limbo.java @@ -11,10 +11,9 @@ import com.velocitypowered.api.command.CommandMeta; import com.velocitypowered.api.proxy.Player; import java.util.function.Supplier; -import net.elytrium.limboapi.api.command.LimboCommandMeta; -import net.elytrium.limboapi.api.player.GameMode; +import net.elytrium.limboapi.api.world.player.GameMode; import net.elytrium.limboapi.api.protocol.PacketDirection; -import net.elytrium.limboapi.api.protocol.packets.PacketMapping; +import net.elytrium.limboapi.api.protocol.PacketMapping; public interface Limbo { @@ -46,7 +45,15 @@ public interface Limbo { Limbo setMaxSuppressPacketLength(int maxSuppressPacketLength); - Limbo registerCommand(LimboCommandMeta commandMeta); + /** + * @deprecated Use {@link #registerCommand(CommandMeta)} in conjunction with {@link com.velocitypowered.api.command.CommandManager#metaBuilder(String)} + */ + @Deprecated(forRemoval = true) + default Limbo registerCommand(net.elytrium.limboapi.api.command.LimboCommandMeta commandMeta) { + return this.registerCommand((CommandMeta) commandMeta); + } + + Limbo registerCommand(CommandMeta commandMeta); Limbo registerCommand(CommandMeta commandMeta, Command command); diff --git a/api/src/main/java/net/elytrium/limboapi/api/LimboFactory.java b/api/src/main/java/net/elytrium/limboapi/api/LimboFactory.java index 2d2e9373..dd764402 100644 --- a/api/src/main/java/net/elytrium/limboapi/api/LimboFactory.java +++ b/api/src/main/java/net/elytrium/limboapi/api/LimboFactory.java @@ -13,242 +13,250 @@ import java.io.InputStream; import java.nio.file.Path; import java.util.Map; -import net.elytrium.limboapi.api.chunk.BuiltInBiome; -import net.elytrium.limboapi.api.chunk.Dimension; -import net.elytrium.limboapi.api.chunk.VirtualBiome; -import net.elytrium.limboapi.api.chunk.VirtualBlock; -import net.elytrium.limboapi.api.chunk.VirtualBlockEntity; -import net.elytrium.limboapi.api.chunk.VirtualChunk; -import net.elytrium.limboapi.api.chunk.VirtualWorld; -import net.elytrium.limboapi.api.file.BuiltInWorldFileType; -import net.elytrium.limboapi.api.file.WorldFile; -import net.elytrium.limboapi.api.material.Block; -import net.elytrium.limboapi.api.material.Item; -import net.elytrium.limboapi.api.material.VirtualItem; +import net.elytrium.limboapi.api.world.WorldVersion; +import net.elytrium.limboapi.api.world.chunk.biome.BuiltInBiome; +import net.elytrium.limboapi.api.world.chunk.Dimension; +import net.elytrium.limboapi.api.world.chunk.biome.VirtualBiome; +import net.elytrium.limboapi.api.world.chunk.block.VirtualBlock; +import net.elytrium.limboapi.api.world.chunk.blockentity.VirtualBlockEntity; +import net.elytrium.limboapi.api.world.chunk.VirtualChunk; +import net.elytrium.limboapi.api.world.VirtualWorld; +import net.elytrium.limboapi.api.world.BuiltInWorldFileType; +import net.elytrium.limboapi.api.world.WorldFile; +import net.elytrium.limboapi.api.world.chunk.block.Block; +import net.elytrium.limboapi.api.world.item.Item; +import net.elytrium.limboapi.api.world.item.VirtualItem; import net.elytrium.limboapi.api.protocol.PreparedPacket; -import net.elytrium.limboapi.api.protocol.item.ItemComponentMap; -import net.elytrium.limboapi.api.protocol.packets.PacketFactory; +import net.elytrium.limboapi.api.world.item.datacomponent.DataComponentMap; +import net.elytrium.limboapi.api.protocol.PacketFactory; import net.kyori.adventure.nbt.CompoundBinaryTag; +import org.checkerframework.checker.nullness.qual.Nullable; public interface LimboFactory { /** - * Creates new virtual block from Block enum. + * Creates new virtual block from Block enum * - * @param block Block from Block enum. + * @param block Block from Block enum * - * @return new virtual block. + * @return new virtual block */ VirtualBlock createSimpleBlock(Block block); /** - * Creates new virtual block from id and data. + * Creates new virtual block from id and data * - * @param legacyID Legacy block id. (1.12.2 and lower) + * @param legacyId Legacy block id (1.12.2 and lower) * - * @return new virtual block. + * @return new virtual block */ - VirtualBlock createSimpleBlock(short legacyID); + VirtualBlock createSimpleBlock(short legacyId); /** - * Creates new virtual block from id and data. + * Creates new virtual block from id and data * - * @param modernID Modern block id. + * @param modernId Modern block id * - * @return new virtual block. + * @return new virtual block */ - VirtualBlock createSimpleBlock(String modernID); + VirtualBlock createSimpleBlock(String modernId); /** - * Creates new virtual block from id and data. + * Creates new virtual block from id and data * - * @param modernID Modern block id. - * @param properties Modern properties like {"waterlogged": "true"}. + * @param modernId Modern block id + * @param properties Modern properties like {"waterlogged": "true"} * - * @return new virtual block. + * @return new virtual block */ - VirtualBlock createSimpleBlock(String modernID, Map properties); + VirtualBlock createSimpleBlock(String modernId, @Nullable Map properties); /** - * Creates new virtual block from id and data. + * Creates new virtual block from id and data * - * @param legacyID Block id. - * @param modern Use the latest supported version ids or 1.12.2 and lower. + * @param id Block id + * @param modern Use the latest supported version ids or 1.12.2 and lower * - * @return new virtual block. + * @return new virtual block */ - VirtualBlock createSimpleBlock(short legacyID, boolean modern); + VirtualBlock createSimpleBlock(short id, boolean modern); + + @Deprecated(forRemoval = true) + default VirtualBlock createSimpleBlock(boolean solid, boolean air, boolean motionBlocking, short id) { + return this.createSimpleBlock(id, air, solid, motionBlocking); + } /** - * Creates new virtual customizable block. + * Creates new virtual customizable block * - * @param solid Defines if the block is solid or not. - * @param air Defines if the block is the air. - * @param motionBlocking Defines if the block blocks motions. (1.14+) - * @param id Block protocol id. + * @param blockStateId Block protocol id + * @param air Defines if the block is the air + * @param solid Defines if the block is solid + * @param motionBlocking Defines if the block blocks motions (1.14+) * - * @return new virtual block. + * @return new virtual block */ - VirtualBlock createSimpleBlock(boolean solid, boolean air, boolean motionBlocking, short id); + VirtualBlock createSimpleBlock(short blockStateId, boolean air, boolean solid, boolean motionBlocking); + + @Deprecated(forRemoval = true) + default VirtualBlock createSimpleBlock(boolean solid, boolean air, boolean motionBlocking, String modernId, Map properties) { + return this.createSimpleBlock(modernId, properties, air, solid, motionBlocking); + } /** - * Creates new virtual customizable block. + * Creates new virtual customizable block * - * @param solid Defines if the block is solid or not. - * @param air Defines if the block is the air. - * @param motionBlocking Defines if the block blocks motions. (1.14+) - * @param modernID Block id. - * @param properties Modern properties like {"waterlogged": "true"}. + * @param modernId Block id + * @param properties Modern properties like {"waterlogged": "true"} + * @param air Defines if the block is the air + * @param solid Defines if the block is solid + * @param motionBlocking Defines if the block blocks motions (1.14+) * - * @return new virtual block. + * @return new virtual block */ - VirtualBlock createSimpleBlock(boolean solid, boolean air, boolean motionBlocking, String modernID, Map properties); + VirtualBlock createSimpleBlock(String modernId, Map properties, boolean air, boolean solid, boolean motionBlocking); /** - * Creates new virtual world. + * Creates new virtual world * * @param dimension World dimension. - * @param posX Spawn location. (X) - * @param posY Spawn location. (Y) - * @param posZ Spawn location. (Z) - * @param yaw Spawn rotation. (Yaw) - * @param pitch Spawn rotation. (Pitch) + * @param posX Spawn location x + * @param posY Spawn location y + * @param posZ Spawn location z + * @param yaw Spawn rotation yaw + * @param pitch Spawn rotation pitch * - * @return new virtual world. + * @return new virtual world */ VirtualWorld createVirtualWorld(Dimension dimension, double posX, double posY, double posZ, float yaw, float pitch); /** - * Creates new virtual chunk with plain biomes set as default. - * You need to provide the chunk location, you can get it using {@code blockCoordinate >> 4}. + * Creates new virtual chunk with plain biomes set as default + * You need to provide the chunk location, you can get it using {@code blockCoordinate >> 4} * - * @param posX Chunk location. (X) - * @param posZ Chunk location. (Z) + * @param posX Chunk position by X + * @param posZ Chunk position by Z * - * @return new virtual chunk. + * @return new virtual chunk */ @Deprecated VirtualChunk createVirtualChunk(int posX, int posZ); /** - * Creates new virtual chunk. - * You need to provide the chunk location, you can get it using {@code blockCoordinate >> 4}. + * Creates new virtual chunk + * You need to provide the chunk location, you can get it using {@code blockCoordinate >> 4} * - * @param posX Chunk location. (X) - * @param posZ Chunk location. (Z) - * @param defaultBiome Default biome to fill it. + * @param posX Chunk position by X + * @param posZ Chunk position by Z + * @param defaultBiome Default biome to fill it * * @return new virtual chunk. */ VirtualChunk createVirtualChunk(int posX, int posZ, VirtualBiome defaultBiome); /** - * Creates new virtual chunk. - * You need to provide the chunk location, you can get it using ({@code block_coordinate >> 4}) + * Creates new virtual chunk + * You need to provide the chunk location, you can get it using {@code blockCoordinate >> 4} * - * @param posX Chunk location. (X) - * @param posZ Chunk location. (Z) - * @param defaultBiome Default biome to fill it. + * @param posX Chunk position by X + * @param posZ Chunk position by Z + * @param defaultBiome Default biome to fill it * - * @return new virtual chunk. + * @return new virtual chunk */ VirtualChunk createVirtualChunk(int posX, int posZ, BuiltInBiome defaultBiome); /** - * Creates new virtual server. + * Creates new virtual server * - * @param world Virtual world. + * @param world Virtual world * - * @return new virtual server. + * @return new virtual server */ Limbo createLimbo(VirtualWorld world); - /** - * Releases a thread after PreparedPacket#build executions. - * Used to free compression libraries. + * Releases the thread after {@link PreparedPacket#build()} executions. + *

+ * Used to free compression libraries */ void releasePreparedPacketThread(Thread thread); /** - * Creates new prepared packet builder. + * Creates new prepared packet builder * - * @return new prepared packet. + * @return new prepared packet */ PreparedPacket createPreparedPacket(); /** - * Creates new prepared packet builder. + * Creates new prepared packet builder * - * @param minVersion Minimum version to prepare. - * @param maxVersion Maximum version to prepare. + * @param minVersion Minimum version to prepare + * @param maxVersion Maximum version to prepare * - * @return new prepared packet. + * @return new prepared packet */ PreparedPacket createPreparedPacket(ProtocolVersion minVersion, ProtocolVersion maxVersion); /** - * Creates new prepared packet builder for the CONFIG state. + * Creates new prepared packet builder for the CONFIG state * - * @return new prepared packet. + * @return new prepared packet */ PreparedPacket createConfigPreparedPacket(); /** - * Creates new prepared packet builder for the CONFIG state. + * Creates new prepared packet builder for the CONFIG state * - * @param minVersion Minimum version to prepare. - * @param maxVersion Maximum version to prepare. + * @param minVersion Minimum version to prepare + * @param maxVersion Maximum version to prepare * - * @return new prepared packet. + * @return new prepared packet */ PreparedPacket createConfigPreparedPacket(ProtocolVersion minVersion, ProtocolVersion maxVersion); /** - * Pass the player to the next Login Limbo, without spawning at current Limbo. + * Pass the player to the next Login Limbo, without spawning at current Limbo * - * @param player Player to pass. + * @param player Player to pass */ void passLoginLimbo(Player player); - /** - * Creates new virtual item from Item enum. - * - * @param item Item from item enum. - * - * @return new virtual item. - */ VirtualItem getItem(Item item); + VirtualItem getItem(String modernId); + + VirtualItem getItem(ProtocolVersion version, short id); + + VirtualItem getItem(WorldVersion version, short id); + + VirtualItem getLegacyItem(short legacyId); + /** - * Creates new virtual item from Item enum. - * - * @param itemID Modern item identifier. - * - * @return new virtual item. + * @return new data component map */ - VirtualItem getItem(String itemID); + DataComponentMap createDataComponentMap(); + + @Deprecated(forRemoval = true) + default VirtualBlockEntity getBlockEntity(String entityId) { + return this.getBlockEntityFromModernId(entityId); + } /** - * Creates new virtual item from Item enum. - * - * @param itemLegacyID Legacy item ID - * - * @return new virtual item. + * @param modernId Should be prefixed with minecraft namespace */ - VirtualItem getLegacyItem(int itemLegacyID); + @Nullable + VirtualBlockEntity getBlockEntityFromModernId(String modernId); /** - * Creates new item component map. - * - * @return new item component map + * @return Block entity using legacy id or null (used in 1.9-1.10, e.g., DLDetector instead of daylight_detector) */ - ItemComponentMap createItemComponentMap(); - - VirtualBlockEntity getBlockEntity(String entityID); + @Nullable + VirtualBlockEntity getBlockEntityFromLegacyId(String legacyId); /** - * A factory to instantiate Minecraft packet objects. + * A factory to instantiate Minecraft packet objects */ PacketFactory getPacketFactory(); @@ -265,7 +273,6 @@ public interface LimboFactory { */ WorldFile openWorldFile(BuiltInWorldFileType apiType, Path file) throws IOException; - /** * Opens world file (a.k.a. schematic file) * @@ -275,7 +282,6 @@ public interface LimboFactory { */ WorldFile openWorldFile(BuiltInWorldFileType apiType, InputStream stream) throws IOException; - /** * Opens world file (a.k.a. schematic file) * diff --git a/api/src/main/java/net/elytrium/limboapi/api/LimboSessionHandler.java b/api/src/main/java/net/elytrium/limboapi/api/LimboSessionHandler.java index ce0212a4..5e4e2b3e 100644 --- a/api/src/main/java/net/elytrium/limboapi/api/LimboSessionHandler.java +++ b/api/src/main/java/net/elytrium/limboapi/api/LimboSessionHandler.java @@ -7,7 +7,7 @@ package net.elytrium.limboapi.api; -import net.elytrium.limboapi.api.player.LimboPlayer; +import net.elytrium.limboapi.api.world.player.LimboPlayer; public interface LimboSessionHandler { @@ -19,11 +19,11 @@ default void onConfig(Limbo server, LimboPlayer player) { } - default void onMove(double posX, double posY, double posZ) { + default void onMove(double posX, double posY, double posZ, float yaw, float pitch) { } - default void onMove(double posX, double posY, double posZ, float yaw, float pitch) { + default void onMove(double posX, double posY, double posZ) { } @@ -35,7 +35,7 @@ default void onGround(boolean onGround) { } - default void onTeleport(int teleportID) { + default void onTeleport(int teleportId) { } @@ -44,7 +44,7 @@ default void onChat(String chat) { } /** - * @param packet Any velocity built-in packet or any packet registered via {@link Limbo#registerPacket}. + * @param packet Any velocity built-in packet or any packet registered via {@link Limbo#registerPacket} */ default void onGeneric(Object packet) { diff --git a/api/src/main/java/net/elytrium/limboapi/api/chunk/BlockEntityVersion.java b/api/src/main/java/net/elytrium/limboapi/api/chunk/BlockEntityVersion.java deleted file mode 100644 index f134e8e4..00000000 --- a/api/src/main/java/net/elytrium/limboapi/api/chunk/BlockEntityVersion.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * The LimboAPI (excluding the LimboAPI plugin) is licensed under the terms of the MIT License. For more details, - * reference the LICENSE file in the api top-level directory. - */ - -package net.elytrium.limboapi.api.chunk; - -import com.velocitypowered.api.network.ProtocolVersion; -import java.util.Arrays; -import java.util.EnumMap; -import java.util.EnumSet; -import java.util.Map; -import java.util.Set; -import net.elytrium.limboapi.api.utils.EnumUniverse; - -public enum BlockEntityVersion { - LEGACY(EnumSet.range(ProtocolVersion.MINECRAFT_1_7_2, ProtocolVersion.MINECRAFT_1_18_2)), - MINECRAFT_1_19(EnumSet.of(ProtocolVersion.MINECRAFT_1_19)), - MINECRAFT_1_19_1(EnumSet.of(ProtocolVersion.MINECRAFT_1_19_1)), - MINECRAFT_1_19_3(EnumSet.of(ProtocolVersion.MINECRAFT_1_19_3)), - MINECRAFT_1_19_4(EnumSet.of(ProtocolVersion.MINECRAFT_1_19_4)), - MINECRAFT_1_20(EnumSet.of(ProtocolVersion.MINECRAFT_1_20)), - MINECRAFT_1_20_2(EnumSet.of(ProtocolVersion.MINECRAFT_1_20_2)), - MINECRAFT_1_20_3(EnumSet.of(ProtocolVersion.MINECRAFT_1_20_3)), - MINECRAFT_1_20_5(EnumSet.of(ProtocolVersion.MINECRAFT_1_20_5)), - MINECRAFT_1_21(EnumSet.of(ProtocolVersion.MINECRAFT_1_21)), - MINECRAFT_1_21_2(EnumSet.of(ProtocolVersion.MINECRAFT_1_21_2)), - MINECRAFT_1_21_4(EnumSet.of(ProtocolVersion.MINECRAFT_1_21_4)), - MINECRAFT_1_21_5(EnumSet.of(ProtocolVersion.MINECRAFT_1_21_5)), - MINECRAFT_1_21_6(EnumSet.of(ProtocolVersion.MINECRAFT_1_21_6)), - MINECRAFT_1_21_7(EnumSet.of(ProtocolVersion.MINECRAFT_1_21_7)); - - private static final EnumMap MC_VERSION_TO_ITEM_VERSIONS = new EnumMap<>(ProtocolVersion.class); - private static final Map KEY_LOOKUP = Map.copyOf(EnumUniverse.createProtocolLookup(values())); - - private final Set versions; - - BlockEntityVersion(ProtocolVersion... versions) { - this.versions = EnumSet.copyOf(Arrays.asList(versions)); - } - - BlockEntityVersion(Set versions) { - this.versions = versions; - } - - public ProtocolVersion getMinSupportedVersion() { - return this.versions.iterator().next(); - } - - public Set getVersions() { - return this.versions; - } - - static { - for (BlockEntityVersion version : BlockEntityVersion.values()) { - for (ProtocolVersion protocolVersion : version.getVersions()) { - MC_VERSION_TO_ITEM_VERSIONS.put(protocolVersion, version); - } - } - } - - public static BlockEntityVersion parse(String from) { - return KEY_LOOKUP.getOrDefault(from, LEGACY); - } - - public static BlockEntityVersion from(ProtocolVersion protocolVersion) { - return MC_VERSION_TO_ITEM_VERSIONS.get(protocolVersion); - } -} diff --git a/api/src/main/java/net/elytrium/limboapi/api/chunk/VirtualBlock.java b/api/src/main/java/net/elytrium/limboapi/api/chunk/VirtualBlock.java deleted file mode 100644 index c2de5c24..00000000 --- a/api/src/main/java/net/elytrium/limboapi/api/chunk/VirtualBlock.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * The LimboAPI (excluding the LimboAPI plugin) is licensed under the terms of the MIT License. For more details, - * reference the LICENSE file in the api top-level directory. - */ - -package net.elytrium.limboapi.api.chunk; - -import com.velocitypowered.api.network.ProtocolVersion; -import net.elytrium.limboapi.api.material.WorldVersion; - -public interface VirtualBlock { - - short getModernID(); - - String getModernStringID(); - - @Deprecated - short getID(ProtocolVersion version); - - short getBlockID(WorldVersion version); - - short getBlockID(ProtocolVersion version); - - boolean isSupportedOn(ProtocolVersion version); - - boolean isSupportedOn(WorldVersion version); - - short getBlockStateID(ProtocolVersion version); - - boolean isSolid(); - - boolean isAir(); - - boolean isMotionBlocking(); -} diff --git a/api/src/main/java/net/elytrium/limboapi/api/chunk/VirtualBlockEntity.java b/api/src/main/java/net/elytrium/limboapi/api/chunk/VirtualBlockEntity.java deleted file mode 100644 index d7f43050..00000000 --- a/api/src/main/java/net/elytrium/limboapi/api/chunk/VirtualBlockEntity.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * The LimboAPI (excluding the LimboAPI plugin) is licensed under the terms of the MIT License. For more details, - * reference the LICENSE file in the api top-level directory. - */ - -package net.elytrium.limboapi.api.chunk; - -import com.velocitypowered.api.network.ProtocolVersion; -import net.kyori.adventure.nbt.CompoundBinaryTag; - -public interface VirtualBlockEntity { - - int getID(ProtocolVersion version); - - int getID(BlockEntityVersion version); - - boolean isSupportedOn(ProtocolVersion version); - - boolean isSupportedOn(BlockEntityVersion version); - - String getModernID(); - - Entry getEntry(int posX, int posY, int posZ, CompoundBinaryTag nbt); - - interface Entry { - - VirtualBlockEntity getBlockEntity(); - - int getPosX(); - - int getPosY(); - - int getPosZ(); - - CompoundBinaryTag getNbt(); - - int getID(ProtocolVersion version); - - int getID(BlockEntityVersion version); - - boolean isSupportedOn(ProtocolVersion version); - - boolean isSupportedOn(BlockEntityVersion version); - } -} diff --git a/api/src/main/java/net/elytrium/limboapi/api/chunk/VirtualChunk.java b/api/src/main/java/net/elytrium/limboapi/api/chunk/VirtualChunk.java deleted file mode 100644 index 3a91bd38..00000000 --- a/api/src/main/java/net/elytrium/limboapi/api/chunk/VirtualChunk.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * The LimboAPI (excluding the LimboAPI plugin) is licensed under the terms of the MIT License. For more details, - * reference the LICENSE file in the api top-level directory. - */ - -package net.elytrium.limboapi.api.chunk; - -import net.elytrium.limboapi.api.chunk.data.ChunkSnapshot; -import net.kyori.adventure.nbt.CompoundBinaryTag; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.common.value.qual.IntRange; - -public interface VirtualChunk { - - void setBlock(int posX, int posY, int posZ, @Nullable VirtualBlock block); - - void setBlockEntity(int posX, int posY, int posZ, @Nullable CompoundBinaryTag nbt, @Nullable VirtualBlockEntity blockEntity); - - void setBlockEntity(VirtualBlockEntity.Entry blockEntityEntry); - - @NonNull - VirtualBlock getBlock(int posX, int posY, int posZ); - - void setBiome2D(int posX, int posZ, @NonNull VirtualBiome biome); - - void setBiome3D(int posX, int posY, int posZ, @NonNull VirtualBiome biome); - - @NonNull - VirtualBiome getBiome(int posX, int posY, int posZ); - - void setBlockLight(int posX, int posY, int posZ, byte light); - - byte getBlockLight(int posX, int posY, int posZ); - - void setSkyLight(int posX, int posY, int posZ, byte light); - - byte getSkyLight(int posX, int posY, int posZ); - - void fillBlockLight(@IntRange(from = 0, to = 15) int level); - - void fillSkyLight(@IntRange(from = 0, to = 15) int level); - - int getPosX(); - - int getPosZ(); - - ChunkSnapshot getFullChunkSnapshot(); - - ChunkSnapshot getPartialChunkSnapshot(long previousUpdate); -} diff --git a/api/src/main/java/net/elytrium/limboapi/api/chunk/VirtualWorld.java b/api/src/main/java/net/elytrium/limboapi/api/chunk/VirtualWorld.java deleted file mode 100644 index ccf1d040..00000000 --- a/api/src/main/java/net/elytrium/limboapi/api/chunk/VirtualWorld.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * The LimboAPI (excluding the LimboAPI plugin) is licensed under the terms of the MIT License. For more details, - * reference the LICENSE file in the api top-level directory. - */ - -package net.elytrium.limboapi.api.chunk; - -import java.util.List; -import net.kyori.adventure.nbt.CompoundBinaryTag; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.common.value.qual.IntRange; - -public interface VirtualWorld { - - void setBlockEntity(int posX, int posY, int posZ, @Nullable CompoundBinaryTag nbt, @Nullable VirtualBlockEntity blockEntity); - - @NonNull - VirtualBlock getBlock(int posX, int posY, int posZ); - - void setBiome2d(int posX, int posZ, @NonNull VirtualBiome biome); - - void setBiome3d(int posX, int posY, int posZ, @NonNull VirtualBiome biome); - - VirtualBiome getBiome(int posX, int posY, int posZ); - - byte getBlockLight(int posX, int posY, int posZ); - - void setBlockLight(int posX, int posY, int posZ, byte light); - - void fillBlockLight(@IntRange(from = 0, to = 15) int level); - - void fillSkyLight(@IntRange(from = 0, to = 15) int level); - - List getChunks(); - - List> getOrderedChunks(); - - @Nullable - VirtualChunk getChunk(int posX, int posZ); - - VirtualChunk getChunkOrNew(int posX, int posZ); - - @NonNull - Dimension getDimension(); - - double getSpawnX(); - - double getSpawnY(); - - double getSpawnZ(); - - float getYaw(); - - float getPitch(); - - void setBlock(int posX, int posY, int posZ, @Nullable VirtualBlock block); -} diff --git a/api/src/main/java/net/elytrium/limboapi/api/chunk/data/BlockSection.java b/api/src/main/java/net/elytrium/limboapi/api/chunk/data/BlockSection.java deleted file mode 100644 index ac273b9f..00000000 --- a/api/src/main/java/net/elytrium/limboapi/api/chunk/data/BlockSection.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * The LimboAPI (excluding the LimboAPI plugin) is licensed under the terms of the MIT License. For more details, - * reference the LICENSE file in the api top-level directory. - */ - -package net.elytrium.limboapi.api.chunk.data; - -import net.elytrium.limboapi.api.chunk.VirtualBlock; -import org.checkerframework.checker.nullness.qual.Nullable; - -public interface BlockSection { - - void setBlockAt(int posX, int posY, int posZ, @Nullable VirtualBlock block); - - VirtualBlock getBlockAt(int posX, int posY, int posZ); - - BlockSection getSnapshot(); - - long getLastUpdate(); -} diff --git a/api/src/main/java/net/elytrium/limboapi/api/chunk/data/BlockStorage.java b/api/src/main/java/net/elytrium/limboapi/api/chunk/data/BlockStorage.java deleted file mode 100644 index e4eaa2db..00000000 --- a/api/src/main/java/net/elytrium/limboapi/api/chunk/data/BlockStorage.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * The LimboAPI (excluding the LimboAPI plugin) is licensed under the terms of the MIT License. For more details, - * reference the LICENSE file in the api top-level directory. - */ - -package net.elytrium.limboapi.api.chunk.data; - -import com.velocitypowered.api.network.ProtocolVersion; -import net.elytrium.limboapi.api.chunk.VirtualBlock; -import org.checkerframework.checker.nullness.qual.NonNull; - -public interface BlockStorage { - - void write(Object byteBufObject, ProtocolVersion version, int pass); - - void set(int posX, int posY, int posZ, @NonNull VirtualBlock block); - - @NonNull - VirtualBlock get(int posX, int posY, int posZ); - - int getDataLength(ProtocolVersion version); - - BlockStorage copy(); - - static int index(int posX, int posY, int posZ) { - return posY << 8 | posZ << 4 | posX; - } -} diff --git a/api/src/main/java/net/elytrium/limboapi/api/chunk/data/ChunkSnapshot.java b/api/src/main/java/net/elytrium/limboapi/api/chunk/data/ChunkSnapshot.java deleted file mode 100644 index 3f0bd274..00000000 --- a/api/src/main/java/net/elytrium/limboapi/api/chunk/data/ChunkSnapshot.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * The LimboAPI (excluding the LimboAPI plugin) is licensed under the terms of the MIT License. For more details, - * reference the LICENSE file in the api top-level directory. - */ - -package net.elytrium.limboapi.api.chunk.data; - -import java.util.List; -import net.elytrium.limboapi.api.chunk.VirtualBiome; -import net.elytrium.limboapi.api.chunk.VirtualBlock; -import net.elytrium.limboapi.api.chunk.VirtualBlockEntity; - -public interface ChunkSnapshot { - - VirtualBlock getBlock(int posX, int posY, int posZ); - - int getPosX(); - - int getPosZ(); - - boolean isFullChunk(); - - BlockSection[] getSections(); - - LightSection[] getLight(); - - VirtualBiome[] getBiomes(); - - List getBlockEntityEntries(); -} diff --git a/api/src/main/java/net/elytrium/limboapi/api/chunk/data/LightSection.java b/api/src/main/java/net/elytrium/limboapi/api/chunk/data/LightSection.java deleted file mode 100644 index 1b70f7ce..00000000 --- a/api/src/main/java/net/elytrium/limboapi/api/chunk/data/LightSection.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * The LimboAPI (excluding the LimboAPI plugin) is licensed under the terms of the MIT License. For more details, - * reference the LICENSE file in the api top-level directory. - */ - -package net.elytrium.limboapi.api.chunk.data; - -import net.elytrium.limboapi.api.mcprotocollib.NibbleArray3D; - -public interface LightSection { - - void setBlockLight(int posX, int posY, int posZ, byte light); - - NibbleArray3D getBlockLight(); - - byte getBlockLight(int posX, int posY, int posZ); - - void setSkyLight(int posX, int posY, int posZ, byte light); - - NibbleArray3D getSkyLight(); - - byte getSkyLight(int posX, int posY, int posZ); - - long getLastUpdate(); - - LightSection copy(); -} diff --git a/api/src/main/java/net/elytrium/limboapi/api/command/LimboCommandMeta.java b/api/src/main/java/net/elytrium/limboapi/api/command/LimboCommandMeta.java index aea94b35..cc2331a4 100644 --- a/api/src/main/java/net/elytrium/limboapi/api/command/LimboCommandMeta.java +++ b/api/src/main/java/net/elytrium/limboapi/api/command/LimboCommandMeta.java @@ -7,22 +7,26 @@ package net.elytrium.limboapi.api.command; -import com.google.common.collect.ImmutableList; import com.mojang.brigadier.tree.CommandNode; import com.velocitypowered.api.command.CommandMeta; import com.velocitypowered.api.command.CommandSource; import java.util.Collection; +import java.util.Collections; import java.util.Objects; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +/** + * @deprecated Use {@link CommandMeta} + */ +@Deprecated(forRemoval = true) public class LimboCommandMeta implements CommandMeta { @NonNull private final Collection aliases; @NonNull private final Collection> hints; - @Nullable // Why?.. + @Nullable private final Object plugin; public LimboCommandMeta(@NonNull Collection aliases) { @@ -35,7 +39,7 @@ public LimboCommandMeta(@NonNull Collection aliases, @Nullable Collectio public LimboCommandMeta(@NonNull Collection aliases, @Nullable Collection> hints, @Nullable Object plugin) { this.aliases = aliases; - this.hints = Objects.requireNonNullElse(hints, ImmutableList.of()); + this.hints = Objects.requireNonNullElse(hints, Collections.emptyList()); this.plugin = plugin; } diff --git a/api/src/main/java/net/elytrium/limboapi/api/event/LoginLimboRegisterEvent.java b/api/src/main/java/net/elytrium/limboapi/api/event/LoginLimboRegisterEvent.java index 9fff7127..979835d4 100644 --- a/api/src/main/java/net/elytrium/limboapi/api/event/LoginLimboRegisterEvent.java +++ b/api/src/main/java/net/elytrium/limboapi/api/event/LoginLimboRegisterEvent.java @@ -7,24 +7,27 @@ package net.elytrium.limboapi.api.event; -import com.google.common.base.Preconditions; +import com.velocitypowered.api.event.annotation.AwaitingEvent; import com.velocitypowered.api.event.player.KickedFromServerEvent; import com.velocitypowered.api.proxy.Player; +import java.util.Objects; import java.util.Queue; import java.util.concurrent.LinkedBlockingQueue; import java.util.function.Function; /** - * This event is fired during login process before the player has been authenticated, e.g. to enable or disable custom authentication. + * This event is fired during a login process before the player has been authenticated, e.g., to enable or disable custom authentication */ +@AwaitingEvent public class LoginLimboRegisterEvent { private final Player player; private final Queue onJoinCallbacks; + private Function onKickCallback; public LoginLimboRegisterEvent(Player player) { - this.player = Preconditions.checkNotNull(player, "player"); + this.player = Objects.requireNonNull(player, "player"); this.onJoinCallbacks = new LinkedBlockingQueue<>(); } @@ -43,7 +46,6 @@ public Queue getOnJoinCallbacks() { return this.onJoinCallbacks; } - public Function getOnKickCallback() { return this.onKickCallback; } @@ -55,7 +57,7 @@ public void addOnJoinCallback(Runnable callback) { /** * @deprecated Use {@link LoginLimboRegisterEvent#addOnJoinCallback(Runnable)} instead */ - @Deprecated + @Deprecated(forRemoval = true) public void addCallback(Runnable callback) { this.onJoinCallbacks.add(callback); } diff --git a/api/src/main/java/net/elytrium/limboapi/api/material/VirtualItem.java b/api/src/main/java/net/elytrium/limboapi/api/material/VirtualItem.java deleted file mode 100644 index 020a6412..00000000 --- a/api/src/main/java/net/elytrium/limboapi/api/material/VirtualItem.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * The LimboAPI (excluding the LimboAPI plugin) is licensed under the terms of the MIT License. For more details, - * reference the LICENSE file in the api top-level directory. - */ - -package net.elytrium.limboapi.api.material; - -import com.velocitypowered.api.network.ProtocolVersion; - -public interface VirtualItem { - - short getID(ProtocolVersion version); - - short getID(WorldVersion version); - - boolean isSupportedOn(ProtocolVersion version); - - boolean isSupportedOn(WorldVersion version); - - String getModernID(); -} diff --git a/api/src/main/java/net/elytrium/limboapi/api/material/WorldVersion.java b/api/src/main/java/net/elytrium/limboapi/api/material/WorldVersion.java deleted file mode 100644 index b87c8947..00000000 --- a/api/src/main/java/net/elytrium/limboapi/api/material/WorldVersion.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * The LimboAPI (excluding the LimboAPI plugin) is licensed under the terms of the MIT License. For more details, - * reference the LICENSE file in the api top-level directory. - */ - -package net.elytrium.limboapi.api.material; - -import com.velocitypowered.api.network.ProtocolVersion; -import java.util.Arrays; -import java.util.EnumMap; -import java.util.EnumSet; -import java.util.Map; -import java.util.Set; -import net.elytrium.limboapi.api.utils.EnumUniverse; - -public enum WorldVersion { - LEGACY(EnumSet.range(ProtocolVersion.MINECRAFT_1_7_2, ProtocolVersion.MINECRAFT_1_12_2)), - MINECRAFT_1_13(ProtocolVersion.MINECRAFT_1_13), - MINECRAFT_1_13_2(ProtocolVersion.MINECRAFT_1_13_1, ProtocolVersion.MINECRAFT_1_13_2), - MINECRAFT_1_14(EnumSet.range(ProtocolVersion.MINECRAFT_1_14, ProtocolVersion.MINECRAFT_1_14_4)), - MINECRAFT_1_15(EnumSet.range(ProtocolVersion.MINECRAFT_1_15, ProtocolVersion.MINECRAFT_1_15_2)), - MINECRAFT_1_16(ProtocolVersion.MINECRAFT_1_16, ProtocolVersion.MINECRAFT_1_16_1), - MINECRAFT_1_16_2(EnumSet.range(ProtocolVersion.MINECRAFT_1_16_2, ProtocolVersion.MINECRAFT_1_16_4)), - MINECRAFT_1_17(EnumSet.range(ProtocolVersion.MINECRAFT_1_17, ProtocolVersion.MINECRAFT_1_18_2)), - MINECRAFT_1_19(EnumSet.range(ProtocolVersion.MINECRAFT_1_19, ProtocolVersion.MINECRAFT_1_19_1)), - MINECRAFT_1_19_3(ProtocolVersion.MINECRAFT_1_19_3), - MINECRAFT_1_19_4(EnumSet.range(ProtocolVersion.MINECRAFT_1_19_4, ProtocolVersion.MINECRAFT_1_20)), - MINECRAFT_1_20(EnumSet.range(ProtocolVersion.MINECRAFT_1_20, ProtocolVersion.MINECRAFT_1_20_2)), - MINECRAFT_1_20_3(ProtocolVersion.MINECRAFT_1_20_3), - MINECRAFT_1_20_5(EnumSet.range(ProtocolVersion.MINECRAFT_1_20_5, ProtocolVersion.MINECRAFT_1_21)), - MINECRAFT_1_21_2(ProtocolVersion.MINECRAFT_1_21_2), - MINECRAFT_1_21_4(ProtocolVersion.MINECRAFT_1_21_4), - MINECRAFT_1_21_5(ProtocolVersion.MINECRAFT_1_21_5), - MINECRAFT_1_21_6(ProtocolVersion.MINECRAFT_1_21_6), - MINECRAFT_1_21_7(EnumSet.range(ProtocolVersion.MINECRAFT_1_21_7, ProtocolVersion.MAXIMUM_VERSION)); - - private static final EnumMap MC_VERSION_TO_ITEM_VERSIONS = new EnumMap<>(ProtocolVersion.class); - private static final Map KEY_LOOKUP = Map.copyOf(EnumUniverse.createProtocolLookup(values())); - - private final Set versions; - - WorldVersion(ProtocolVersion... versions) { - this.versions = EnumSet.copyOf(Arrays.asList(versions)); - } - - WorldVersion(Set versions) { - this.versions = versions; - } - - public ProtocolVersion getMinSupportedVersion() { - return this.versions.iterator().next(); - } - - public Set getVersions() { - return this.versions; - } - - static { - for (WorldVersion version : WorldVersion.values()) { - for (ProtocolVersion protocolVersion : version.getVersions()) { - MC_VERSION_TO_ITEM_VERSIONS.put(protocolVersion, version); - } - } - } - - public static WorldVersion parse(String from) { - return KEY_LOOKUP.getOrDefault(from, LEGACY); - } - - public static WorldVersion from(ProtocolVersion protocolVersion) { - return MC_VERSION_TO_ITEM_VERSIONS.get(protocolVersion); - } -} diff --git a/api/src/main/java/net/elytrium/limboapi/api/player/LimboPlayer.java b/api/src/main/java/net/elytrium/limboapi/api/player/LimboPlayer.java deleted file mode 100644 index 32ab256f..00000000 --- a/api/src/main/java/net/elytrium/limboapi/api/player/LimboPlayer.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * The LimboAPI (excluding the LimboAPI plugin) is licensed under the terms of the MIT License. For more details, - * reference the LICENSE file in the api top-level directory. - */ - -package net.elytrium.limboapi.api.player; - -import com.velocitypowered.api.proxy.Player; -import com.velocitypowered.api.proxy.server.RegisteredServer; -import java.awt.image.BufferedImage; -import java.util.concurrent.ScheduledExecutorService; -import net.elytrium.limboapi.api.Limbo; -import net.elytrium.limboapi.api.material.VirtualItem; -import net.elytrium.limboapi.api.protocol.item.ItemComponentMap; -import net.kyori.adventure.nbt.CompoundBinaryTag; - -public interface LimboPlayer { - - void writePacket(Object packetObj); - - void writePacketAndFlush(Object packetObj); - - void flushPackets(); - - void closeWith(Object packetObj); - - ScheduledExecutorService getScheduledExecutor(); - - void sendImage(BufferedImage image); - - void sendImage(BufferedImage image, boolean sendItem); - - void sendImage(int mapID, BufferedImage image); - - void sendImage(int mapID, BufferedImage image, boolean sendItem); - - void sendImage(int mapID, BufferedImage image, boolean sendItem, boolean resize); - - void setInventory(VirtualItem item, int count); - - void setInventory(VirtualItem item, int slot, int count); - - void setInventory(int slot, VirtualItem item, int count, int data, CompoundBinaryTag nbt); - - void setInventory(int slot, VirtualItem item, int count, int data, ItemComponentMap map); - - void setGameMode(GameMode gameMode); - - void teleport(double posX, double posY, double posZ, float yaw, float pitch); - - void disableFalling(); - - void enableFalling(); - - void disconnect(); - - void disconnect(RegisteredServer server); - - void sendAbilities(); - - void sendAbilities(int abilities, float flySpeed, float walkSpeed); - - void sendAbilities(byte abilities, float flySpeed, float walkSpeed); - - byte getAbilities(); - - GameMode getGameMode(); - - Limbo getServer(); - - Player getProxyPlayer(); - - int getPing(); - - void setWorldTime(long ticks); -} diff --git a/api/src/main/java/net/elytrium/limboapi/api/protocol/PacketFactory.java b/api/src/main/java/net/elytrium/limboapi/api/protocol/PacketFactory.java new file mode 100644 index 00000000..8fb9ea45 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/protocol/PacketFactory.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2021 - 2025 Elytrium + * + * The LimboAPI (excluding the LimboAPI plugin) is licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in the api top-level directory. + */ + +package net.elytrium.limboapi.api.protocol; + +import com.velocitypowered.api.network.ProtocolVersion; +import java.util.Map; +import net.elytrium.limboapi.api.protocol.data.EntityData; +import net.elytrium.limboapi.api.world.chunk.Dimension; +import net.elytrium.limboapi.api.world.chunk.blockentity.VirtualBlockEntity; +import net.elytrium.limboapi.api.world.chunk.ChunkSnapshot; +import net.elytrium.limboapi.api.world.item.VirtualItem; +import net.elytrium.limboapi.api.world.WorldVersion; +import net.elytrium.limboapi.api.world.item.datacomponent.DataComponentMap; +import net.elytrium.limboapi.api.protocol.data.AbilityFlags; +import net.elytrium.limboapi.api.protocol.data.MapData; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.text.Component; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +public interface PacketFactory { + + @Deprecated(forRemoval = true) + default Object createChangeGameStatePacket(int event, float param) { + return this.createGameEventPacket(event, param); + } + + Object createGameEventPacket(int event, float param); + + /** + * @see #prepareCompleteChunkDataPacket(ProtocolVersion, ProtocolVersion, PreparedPacket, ChunkSnapshot, Dimension) + */ + default void prepareCompleteChunkDataPacket(PreparedPacket packet, ChunkSnapshot chunkSnapshot, Dimension dimension) { + this.prepareCompleteChunkDataPacket(ProtocolVersion.MINIMUM_VERSION, ProtocolVersion.MAXIMUM_VERSION, packet, chunkSnapshot, dimension); + } + + /** + * @see #prepareCompleteChunkDataPacket(ProtocolVersion, ProtocolVersion, PreparedPacket, ChunkSnapshot, Dimension) + */ + default void prepareCompleteChunkDataPacket(ProtocolVersion minPrepareVersion, ProtocolVersion maxPrepareVersion, PreparedPacket packet, ChunkSnapshot chunkSnapshot, Dimension dimension) { + this.prepareCompleteChunkDataPacket(minPrepareVersion, maxPrepareVersion, packet, chunkSnapshot, dimension.hasSkyLight(), dimension.getMaxSections()); + } + + /** + * @see #prepareCompleteChunkDataPacket(ProtocolVersion, ProtocolVersion, PreparedPacket, ChunkSnapshot, Dimension) + */ + default void prepareCompleteChunkDataPacket(PreparedPacket packet, ChunkSnapshot chunkSnapshot, boolean hasSkyLight, int maxSections) { + this.prepareCompleteChunkDataPacket(ProtocolVersion.MINIMUM_VERSION, ProtocolVersion.MAXIMUM_VERSION, packet, chunkSnapshot, hasSkyLight, maxSections); + } + + /** + * Prepares a complete chunk that includes various packets depending on the protocol version: + *

    + *
  • {@link #createLightUpdatePacket(ChunkSnapshot, boolean) Light Update Packet} - for versions [1.14-1.17.1]
  • + *
  • {@link #createBlockEntityDataPacket(VirtualBlockEntity.Entry) Block Entity Data Packet} - for versions [1.7.2-1.9.2]
  • + *
  • {@link #createUpdateSignPacket(VirtualBlockEntity.Entry) Update Sign Packet} - for versions [1.7.2-1.9.2], if applicable
  • + *
+ */ + void prepareCompleteChunkDataPacket(ProtocolVersion minPrepareVersion, ProtocolVersion maxPrepareVersion, PreparedPacket packet, ChunkSnapshot chunkSnapshot, boolean hasSkyLight, int maxSections); + + default Object createChunkDataPacket(ChunkSnapshot chunkSnapshot, Dimension dimension) { + return this.createChunkDataPacket(chunkSnapshot, dimension.hasSkyLight(), dimension.getMaxSections()); + } + + Object createChunkDataPacket(ChunkSnapshot chunkSnapshot, boolean hasSkyLight, int maxSections); + + /** + * @sinceMinecraft 1.14 + */ + default Object createLightUpdatePacket(ChunkSnapshot chunkSnapshot, Dimension dimension) { + return this.createLightUpdatePacket(chunkSnapshot, dimension.hasSkyLight()); + } + + /** + * @sinceMinecraft 1.14 + */ + Object createLightUpdatePacket(ChunkSnapshot chunkSnapshot, boolean hasSkyLight); + + Object createBlockEntityDataPacket(VirtualBlockEntity.Entry entry); + + /** + * @param lines length should be exactly 4 + * + * @deprecated Used only in [1.7.2-1.9.2] + */ + @Deprecated + Object createUpdateSignPacket(int posX, int posY, int posZ, @NonNull Component @NonNull [] lines); + + /** + * @deprecated Used only in [1.7.2-1.9.2] + */ + @Deprecated + Object createUpdateSignPacket(VirtualBlockEntity.Entry entry); + + Object createChunkUnloadPacket(int posX, int posZ); + + /** + * @deprecated Use {@link PacketFactory#createDefaultSpawnPositionPacket(String, int, int, int, float, float)} + */ + @Deprecated + Object createDefaultSpawnPositionPacket(int posX, int posY, int posZ, float angle); + + Object createDefaultSpawnPositionPacket(String dimension, int posX, int posY, int posZ, float yaw, float pitch); + + Object createMapDataPacket(int mapId, byte scale, MapData mapData); + + /** + * @param id Entity id + * @param data See {@link EntityData} for all possible data types available + */ + Object createEntityDataPacket(int id, EntityData data); + + /** + * @param abilities See {@link AbilityFlags} (e.g. {@code AbilityFlags.ALLOW_FLYING | AbilityFlags.CREATIVE_MODE}) + */ + Object createPlayerAbilitiesPacket(int abilities, float flyingSpeed, float walkingSpeed); + + /** + * @param abilities See {@link AbilityFlags} (e.g. {@code AbilityFlags.ALLOW_FLYING | AbilityFlags.CREATIVE_MODE}) + */ + Object createPlayerAbilitiesPacket(byte abilities, float flyingSpeed, float walkingSpeed); + + @Deprecated(forRemoval = true) + default Object createPositionRotationPacket(double posX, double posY, double posZ, float yaw, float pitch, boolean onGround, int teleportId, boolean dismountVehicle) { + return this.createPlayerPositionPacket(posX, posY, posZ, yaw, pitch, onGround, teleportId, dismountVehicle); + } + + Object createPlayerPositionPacket(double posX, double posY, double posZ, float yaw, float pitch, boolean onGround, int teleportId, boolean dismountVehicle); + + Object createSetExperiencePacket(float experienceProgress, int experienceLevel, int totalExperience); + + @Deprecated(forRemoval = true) + default Object createSetSlotPacket(int containerId, int slot, VirtualItem item, int count, int data, @Nullable CompoundBinaryTag nbt) { + return this.createSetSlotPacket(containerId, slot, item, count, (short) data, nbt); + } + + @Deprecated(forRemoval = true) + default Object createSetSlotPacket(int containerId, int slot, VirtualItem item, int count, int data, @Nullable DataComponentMap map) { + return this.createSetSlotPacket(containerId, slot, item, count, (short) data, map); + } + + Object createSetSlotPacket(int containerId, int slot, VirtualItem item, int count); + + Object createSetSlotPacket(int containerId, int slot, VirtualItem item, int count, @Nullable CompoundBinaryTag nbt); + + Object createSetSlotPacket(int containerId, int slot, VirtualItem item, int count, @Nullable DataComponentMap map); + + Object createSetSlotPacket(int containerId, int slot, VirtualItem item, int count, short data); + + Object createSetSlotPacket(int containerId, int slot, VirtualItem item, int count, short data, @Nullable CompoundBinaryTag nbt); + + Object createSetSlotPacket(int containerId, int slot, VirtualItem item, int count, short data, @Nullable DataComponentMap map); + + Object createSetSlotPacket(int containerId, int slot, VirtualItem item, int count, short data, @Nullable CompoundBinaryTag nbt, @Nullable DataComponentMap map); + + @Deprecated(forRemoval = true) + default Object createTimeUpdatePacket(long gameTime, long dayTime) { + return this.createSetTimePacket(gameTime, dayTime); + } + + Object createSetTimePacket(long gameTime, long dayTime); + + @Deprecated(forRemoval = true) + default Object createUpdateViewPositionPacket(int posX, int posZ) { + return this.createSetChunkCacheCenter(posX, posZ); + } + + /** + * @sinceMinecraft 1.14 + */ + Object createSetChunkCacheCenter(int posX, int posZ); + + Object createUpdateTagsPacket(WorldVersion version); + + Object createUpdateTagsPacket(ProtocolVersion version); + + /** + * @sinceMinecraft 1.13 + */ + Object createUpdateTagsPacket(Map> tags); +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/protocol/packets/PacketMapping.java b/api/src/main/java/net/elytrium/limboapi/api/protocol/PacketMapping.java similarity index 91% rename from api/src/main/java/net/elytrium/limboapi/api/protocol/packets/PacketMapping.java rename to api/src/main/java/net/elytrium/limboapi/api/protocol/PacketMapping.java index 2f138150..0b74049a 100644 --- a/api/src/main/java/net/elytrium/limboapi/api/protocol/packets/PacketMapping.java +++ b/api/src/main/java/net/elytrium/limboapi/api/protocol/PacketMapping.java @@ -5,7 +5,7 @@ * reference the LICENSE file in the api top-level directory. */ -package net.elytrium.limboapi.api.protocol.packets; +package net.elytrium.limboapi.api.protocol; import com.velocitypowered.api.network.ProtocolVersion; import org.checkerframework.checker.nullness.qual.Nullable; @@ -29,10 +29,15 @@ public PacketMapping(int id, ProtocolVersion protocolVersion, @Nullable Protocol this.encodeOnly = encodeOnly; } + @Deprecated(forRemoval = true) public int getID() { return this.id; } + public int getId() { + return this.id; + } + public ProtocolVersion getProtocolVersion() { return this.protocolVersion; } diff --git a/api/src/main/java/net/elytrium/limboapi/api/protocol/data/AbilityFlags.java b/api/src/main/java/net/elytrium/limboapi/api/protocol/data/AbilityFlags.java new file mode 100644 index 00000000..18977c87 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/protocol/data/AbilityFlags.java @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 - 2025 Elytrium + * + * The LimboAPI (excluding the LimboAPI plugin) is licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in the api top-level directory. + */ + +package net.elytrium.limboapi.api.protocol.data; + +/** + * For PlayerAbilities packet + */ +public class AbilityFlags { + + public static final int INVULNERABLE = 0b0001; + public static final int FLYING = 0b0010; + public static final int ALLOW_FLYING = 0b0100; + public static final int CREATIVE_MODE = 0b1000; +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/protocol/packets/data/BiomeData.java b/api/src/main/java/net/elytrium/limboapi/api/protocol/data/BiomeData.java similarity index 68% rename from api/src/main/java/net/elytrium/limboapi/api/protocol/packets/data/BiomeData.java rename to api/src/main/java/net/elytrium/limboapi/api/protocol/data/BiomeData.java index 73c286d7..c14bcb30 100644 --- a/api/src/main/java/net/elytrium/limboapi/api/protocol/packets/data/BiomeData.java +++ b/api/src/main/java/net/elytrium/limboapi/api/protocol/data/BiomeData.java @@ -5,35 +5,35 @@ * reference the LICENSE file in the api top-level directory. */ -package net.elytrium.limboapi.api.protocol.packets.data; +package net.elytrium.limboapi.api.protocol.data; import java.util.HashMap; import java.util.Map; -import net.elytrium.limboapi.api.chunk.VirtualBiome; -import net.elytrium.limboapi.api.chunk.data.ChunkSnapshot; +import net.elytrium.limboapi.api.world.chunk.biome.VirtualBiome; +import net.elytrium.limboapi.api.world.chunk.ChunkSnapshot; /** - * For ChunkData packet. + * For ChunkData packet */ public class BiomeData { private final int[] post115Biomes = new int[1024]; - private final byte[] pre115Biomes = new byte[256]; + private final int[] pre115Biomes = new int[256]; public BiomeData(ChunkSnapshot chunk) { - VirtualBiome[] biomes = chunk.getBiomes(); + VirtualBiome[] biomes = chunk.biomes(); for (int i = 0; i < biomes.length; ++i) { - this.post115Biomes[i] = biomes[i].getID(); + this.post115Biomes[i] = biomes[i].getId(); } - // Down sample 4x4x4 3D biomes to 2D XZ. + // Down sample 4x4x4 3D biomes to 2D XZ Map samples = new HashMap<>(64); for (int posX = 0; posX < 16; posX += 4) { for (int posZ = 0; posZ < 16; posZ += 4) { samples.clear(); for (int posY = 0; posY < 256; posY += 16) { VirtualBiome biome = biomes[/*SimpleChunk.getBiomeIndex(posX, posY, posZ)*/(posY >> 2 & 63) << 4 | (posZ >> 2 & 3) << 2 | posX >> 2 & 3]; - samples.put(biome.getID(), samples.getOrDefault(biome.getID(), 0) + 1); + samples.put(biome.getId(), samples.getOrDefault(biome.getId(), 0) + 1); } int id = samples.entrySet() .stream() @@ -42,7 +42,7 @@ public BiomeData(ChunkSnapshot chunk) { .getKey(); for (int i = posX; i < posX + 4; ++i) { for (int j = posZ; j < posZ + 4; ++j) { - this.pre115Biomes[(j << 4) + i] = (byte) id; + this.pre115Biomes[(j << 4) + i] = id; } } } @@ -53,7 +53,7 @@ public int[] getPost115Biomes() { return this.post115Biomes; } - public byte[] getPre115Biomes() { + public int[] getPre115Biomes() { return this.pre115Biomes; } } diff --git a/api/src/main/java/net/elytrium/limboapi/api/protocol/data/BlockPos.java b/api/src/main/java/net/elytrium/limboapi/api/protocol/data/BlockPos.java new file mode 100644 index 00000000..270e9345 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/protocol/data/BlockPos.java @@ -0,0 +1,12 @@ +/* + * Copyright (C) 2021 - 2024 Elytrium + * + * The LimboAPI (excluding the LimboAPI plugin) is licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in the api top-level directory. + */ + +package net.elytrium.limboapi.api.protocol.data; + +public record BlockPos(int posX, int posY, int posZ) implements EntityData.Particle.VibrationParticle.PositionSource { + +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/protocol/data/ComponentHolder.java b/api/src/main/java/net/elytrium/limboapi/api/protocol/data/ComponentHolder.java new file mode 100644 index 00000000..80f2e373 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/protocol/data/ComponentHolder.java @@ -0,0 +1,67 @@ +package net.elytrium.limboapi.api.protocol.data; + +import com.velocitypowered.api.network.ProtocolVersion; +import java.util.ServiceLoader; +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.text.Component; + +public class ComponentHolder { + + public static final Codec CODEC = ServiceLoader.load(Codec.class, ComponentHolder.class.getClassLoader()).iterator().next(); + + private final Component component; + private final String json; + private final BinaryTag binaryTag; + + public ComponentHolder(Component component) { + this.component = component; + this.json = null; + this.binaryTag = null; + } + + public ComponentHolder(String json) { + this.component = null; + this.json = json; + this.binaryTag = null; + } + + public ComponentHolder(BinaryTag binaryTag) { + this.component = null; + this.json = null; + this.binaryTag = binaryTag; + } + + public Component getComponent(ProtocolVersion version) { + Component component = this.component; + if (component == null) { + if (this.json != null) { + return ComponentHolder.CODEC.json2Component(version, this.json); + } else if (this.binaryTag != null) { + return ComponentHolder.CODEC.binaryTag2Component(version, this.binaryTag); + } + } + + return component; + } + + public String getJson(ProtocolVersion version) { + String json = this.json; + return json == null ? ComponentHolder.CODEC.component2Json(version, this.getComponent(version)) : json; + } + + public BinaryTag getBinaryTag(ProtocolVersion version) { + BinaryTag binaryTag = this.binaryTag; + return binaryTag == null ? ComponentHolder.CODEC.component2BinaryTag(version, this.getComponent(version)) : binaryTag; + } + + public interface Codec { + + Component json2Component(ProtocolVersion version, String json); + + Component binaryTag2Component(ProtocolVersion version, BinaryTag binaryTag); + + String component2Json(ProtocolVersion version, Component component); + + BinaryTag component2BinaryTag(ProtocolVersion version, Component component); + } +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/protocol/data/EntityData.java b/api/src/main/java/net/elytrium/limboapi/api/protocol/data/EntityData.java new file mode 100644 index 00000000..d1dbbbc6 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/protocol/data/EntityData.java @@ -0,0 +1,698 @@ +/* + * Copyright (C) 2021 - 2024 Elytrium + * + * The LimboAPI (excluding the LimboAPI plugin) is licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in the api top-level directory. + */ + +package net.elytrium.limboapi.api.protocol.data; + +import com.velocitypowered.api.network.ProtocolVersion; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.shorts.Short2ObjectMap; +import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.OptionalInt; +import java.util.UUID; +import net.elytrium.limboapi.api.world.item.datacomponent.type.PaintingVariant; +import net.elytrium.limboapi.api.world.item.datacomponent.type.ResolvableProfile; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.Holder; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.HolderSet; +import net.elytrium.limboapi.api.world.player.LimboPlayer; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.nbt.EndBinaryTag; +import org.checkerframework.checker.index.qual.Positive; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; + +/** + * The value can be one of the following data types (note that some are version-specific): + *
+ * {@link Byte},
+ * {@link Short} (2-byte short <1.9, VarInt >=1.9),
+ * {@link Integer} (4-byte int <=1.8, VarInt >1.8),
+ * {@link Long} (4-byte int <=1.8, VarInt <=1.19.2, VarLong >1.19.2),
+ * {@link Float}, {@link String}, {@link ComponentHolder}, {@link OptionalComponentHolder}, {@link ItemStack},
+ * {@link Boolean} (serialized as Byte data type <=1.8, Boolean >1.8),
+ * {@link Rotations}, {@link BlockPos}, {@link OptionalBlockPos}, {@link Direction},
+ * {@link OptionalUUID} (serialized as String data type <=1.8, OptionalUUID >1.8),
+ * {@link BlockState}, {@link OptionalBlockState},
+ * {@link CompoundBinaryTag} ({@link EndBinaryTag#endBinaryTag()} Should be used instead of null),
+ * {@link Particle}, {@link Particles}, {@link VillagerData},
+ * {@link OptionalInt} (Also known as OptionalUnsignedInt) (4-byte int <=1.8, VarInt <=1.13.2, OptionalInt >1.13.2),
+ * {@link Pose}, {@link CatVariant}, {@link CowVariant}, {@link WolfVariant}, {@link WolfSoundVariant}, {@link FrogVariant}, {@link PigVariant}, {@link ChickenVariant},
+ * {@link OptionalGlobalPos}, {@link PaintingVariantHolder}, {@link SnifferState}, {@link ArmadilloState}, {@link CopperGolemState}, {@link WeatheringCopperState},
+ * {@link ResolvableProfile}, {@link Vector3}, {@link Quaternion}
+ * 
+ * + * @see LimboPlayer#setEntityData(int, EntityData) + */ +@NullMarked +public class EntityData extends Short2ObjectOpenHashMap { + + public EntityData(int expected, float f) { + super(expected, f); + } + + public EntityData(int expected) { + super(expected); + } + + public EntityData() { + + } + + public EntityData(Map m, float f) { + super(m, f); + } + + public EntityData(Map m) { + super(m); + } + + public EntityData(Short2ObjectMap m, float f) { + super(m, f); + } + + public EntityData(Short2ObjectMap m) { + super(m); + } + + public EntityData(short[] k, Object[] v, float f) { + super(k, v, f); + } + + public EntityData(short[] k, Object[] v) { + super(k, v); + } + + public record OptionalComponentHolder(@Nullable ComponentHolder component) { + + public static final OptionalComponentHolder EMPTY = new OptionalComponentHolder(null); + + public ComponentHolder orElseThrow() { + if (this.component == null) { + throw new NoSuchElementException("No value present"); + } + + return this.component; + } + + public static OptionalComponentHolder of(@Nullable ComponentHolder component) { + return component == null ? OptionalComponentHolder.EMPTY : new OptionalComponentHolder(component); + } + } + + public record OptionalBlockPos(@Nullable BlockPos blockPos) { + + public static final OptionalBlockPos EMPTY = new OptionalBlockPos(null); + + public BlockPos orElseThrow() { + if (this.blockPos == null) { + throw new NoSuchElementException("No value present"); + } + + return this.blockPos; + } + + public static OptionalBlockPos of(@Nullable BlockPos blockPos) { + return blockPos == null ? OptionalBlockPos.EMPTY : new OptionalBlockPos(blockPos); + } + } + + /** + * @sinceMinecraft 1.8 + */ + public record Rotations(float posX, float posY, float posZ) { + + public Rotations(float posX, float posY, float posZ) { + this.posX = !Float.isInfinite(posX) && !Float.isNaN(posX) ? posX % 360.0F : 0.0F; + this.posY = !Float.isInfinite(posY) && !Float.isNaN(posY) ? posY % 360.0F : 0.0F; + this.posZ = !Float.isInfinite(posZ) && !Float.isNaN(posZ) ? posZ % 360.0F : 0.0F; + } + } + + public enum Direction { + + DOWN, + UP, + NORTH, + SOUTH, + WEST, + EAST; + + public static final Direction[] VALUES = Direction.values(); + } + + /** + * Also known as OptionalLivingEntityReference as of 1.21.5 + */ + public record OptionalUUID(@Nullable UUID uuid) { + + public static final OptionalUUID EMPTY = new OptionalUUID(null); + + public UUID orElseThrow() { + if (this.uuid == null) { + throw new NoSuchElementException("No value present"); + } + + return this.uuid; + } + + public static OptionalUUID of(@Nullable UUID uuid) { + return uuid == null ? OptionalUUID.EMPTY : new OptionalUUID(uuid); + } + } + + /** + * @sinceMinecraft 1.19.4 + */ + public record BlockState(int blockState/*TODO use existing api*/) implements Particle.ParticleOptions { + + } + + public record OptionalBlockState(@Nullable BlockState blockState) { + + public static final OptionalBlockState EMPTY = new OptionalBlockState(null); + + public BlockState orElseThrow() { + if (this.blockState == null) { + throw new NoSuchElementException("No value present"); + } + + return this.blockState; + } + + public static OptionalBlockState of(@Nullable BlockState blockState) { + return blockState == null ? OptionalBlockState.EMPTY : new OptionalBlockState(blockState); + } + } + + /** + * @sinceMinecraft 1.13 + */ + public record Particle(int type/*TODO expose api*/, @Nullable ParticleOptions data) { + + /** + * @sinceMinecraft 1.21.9 + */ + public record PowerParticleOption(float power) implements ParticleOptions { + + } + + /** + * @param scale Must be within range [0.01, 4.0] + * + * @sinceMinecraft 1.13 + */ + public record DustParticleOptions(int color, float scale) implements ParticleOptions { + + /** + * @param scale Must be within range [0.01, 4.0] + */ + public DustParticleOptions(int color, float scale) { + this.color = color; + this.scale = scale < 0.01F ? 0.01F : Math.min(scale, 4.0F); + } + } + + /** + * @param scale Must be within range [0.01, 4.0] + * + * @sinceMinecraft 1.17 + */ + public record DustColorTransitionOptions(int fromColor, int toColor, float scale) implements ParticleOptions { + + public DustColorTransitionOptions(int fromColor, int toColor, float scale) { + this.fromColor = fromColor; + this.toColor = toColor; + this.scale = scale < 0.01F ? 0.01F : Math.min(scale, 4.0F); + } + } + + /** + * @sinceMinecraft 1.21.9 + */ + public record SpellParticleOption(int color, float power) implements ParticleOptions { + + } + + /** + * @param color ARGB + * + * @sinceMinecraft 1.20.5 + */ + public record ColorParticleOption(int color) implements ParticleOptions { + + public static float redFloat(int color) { + return ColorParticleOption.from8BitChannel(ColorParticleOption.red(color)); + } + + public static int red(int color) { + return (color >> 16) & 0xFF; + } + + public static float greenFloat(int color) { + return ColorParticleOption.from8BitChannel(ColorParticleOption.green(color)); + } + + public static int green(int color) { + return (color >> 8) & 0xFF; + } + + public static float blueFloat(int color) { + return ColorParticleOption.from8BitChannel(ColorParticleOption.blue(color)); + } + + public static int blue(int i) { + return i & 0xFF; + } + + public static float alphaFloat(int color) { + return ColorParticleOption.from8BitChannel(ColorParticleOption.alpha(color)); + } + + public static int alpha(int color) { + return color >>> 24; + } + + public static int colorFromFloat(float red, float green, float blue) { + return ColorParticleOption.colorFromFloat(1.0F, red, green, blue); + } + + public static int colorFromFloat(float alpha, float red, float green, float blue) { + return ColorParticleOption.color(ColorParticleOption.as8BitChannel(alpha), ColorParticleOption.as8BitChannel(red), ColorParticleOption.as8BitChannel(green), ColorParticleOption.as8BitChannel(blue)); + } + + public static int color(int red, int green, int blue) { + return ColorParticleOption.color(0xFF, red, green, blue); + } + + public static int color(int alpha, int red, int green, int blue) { + return (alpha & 0xFF) << 24 | (red & 0xFF) << 16 | (green & 0xFF) << 8 | blue & 0xFF; + } + + private static float from8BitChannel(int channel) { + return (float) channel / 255.0F; + } + + public static int as8BitChannel(float channel) { + return ColorParticleOption.floor(channel * 255.0F); + } + + private static int floor(float value) { + int i = (int) value; + return value < i ? i - 1 : i; + } + } + + /** + * @sinceMinecraft 1.19 + */ + public record SculkChargeParticleOptions(float roll) implements ParticleOptions { + + } + + /** + * @param origin Present only in versions [1.17-1.18.2] + * + * @sinceMinecraft 1.17 + */ + public record VibrationParticle(@ApiStatus.Obsolete @Nullable BlockPos origin, PositionSource destination, int arrivalInTicks) implements ParticleOptions { + + public record EntityPositionSource(int entityId, float yOffset) implements PositionSource { + + } + + public sealed interface PositionSource permits BlockPos, EntityPositionSource { + + } + } + + /** + * @param duration Added in version 1.21.4 + * + * @sinceMinecraft 1.21.2 + */ + public record TrailParticleOption(Vector3 target, int color, @Positive int duration) implements ParticleOptions { + + } + + /** + * @sinceMinecraft 1.19 + */ + public record ShriekParticleOption(int delay) implements ParticleOptions { + + } + + public sealed interface ParticleOptions permits BlockState, + PowerParticleOption, DustParticleOptions, DustColorTransitionOptions, SpellParticleOption, ColorParticleOption, + SculkChargeParticleOptions, ItemStack, VibrationParticle, TrailParticleOption, ShriekParticleOption { + + } + } + + /** + * @sinceMinecraft 1.20.5 + */ + public static class Particles extends ObjectArrayList { + + public Particles(int initialCapacity) { + super(initialCapacity); + } + + public Particles() { + + } + } + + /** + * @param level Must be no less than 1 + * + * @sinceMinecraft 1.14 + */ + public record VillagerData(int type, int profession, int level) { + + public VillagerData(int type, int profession, int level) { + this.type = type; + this.profession = profession; + this.level = Math.max(1, level); + } + } + + /** + * @sinceMinecraft 1.14 + */ + public enum Pose { // TODO registry map + + STANDING, + FALL_FLYING, + SLEEPING, + SWIMMING, + SPIN_ATTACK, + CROUCHING, + /** + * @sinceMinecraft 1.17 + */ + LONG_JUMPING, + DYING, + /** + * @sinceMinecraft 1.19 + */ + CROAKING, + /** + * @sinceMinecraft 1.19 + */ + USING_TONGUE, + /** + * @sinceMinecraft 1.19.3 + */ + SITTING, + /** + * @sinceMinecraft 1.19 + */ + ROARING, + /** + * @sinceMinecraft 1.19 + */ + SNIFFING, + /** + * @sinceMinecraft 1.19 + */ + EMERGING, + /** + * @sinceMinecraft 1.19 + */ + DIGGING, + /** + * @sinceMinecraft 1.20 + */ + SLIDING, + /** + * @sinceMinecraft 1.20 + */ + SHOOTING, + /** + * @sinceMinecraft 1.20 + */ + INHALING; + + public static final Pose[] VALUES = Pose.values(); + + public int getProtocolId(ProtocolVersion version) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20)) { // after 1.19.3 all new poses were added to end + return this.ordinal(); + } else { + return switch (this) { + case STANDING -> 0; + case FALL_FLYING -> 1; + case SLEEPING -> 2; + case SWIMMING -> 3; + case SPIN_ATTACK -> 4; + case CROUCHING -> 5; + default -> version.noLessThan(ProtocolVersion.MINECRAFT_1_17) ? switch (this) { + // >=1.17 + case LONG_JUMPING -> 6; + case DYING -> 7; + default -> version.noLessThan(ProtocolVersion.MINECRAFT_1_19) ? switch (this) { + // >=1.19 + case CROAKING -> 8; + case USING_TONGUE -> 9; + default -> version.noLessThan(ProtocolVersion.MINECRAFT_1_19_3) ? switch (this) { + // >=1.19.3 + case SITTING -> 10; + case ROARING -> 11; + case SNIFFING -> 12; + case EMERGING -> 13; + case DIGGING -> 14; + default -> Pose.fail(this); + } : switch (this) { + // >=1.19 + case ROARING -> 10; + case SNIFFING -> 11; + case EMERGING -> 12; + case DIGGING -> 13; + default -> Pose.fail(this); + }; + } : Pose.fail(this); + } : this == Pose.DYING ? 6 : Pose.fail(this); + }; + } + } + + public static Pose fromProtocolId(int id, ProtocolVersion version) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20)) { + return Pose.VALUES[id]; + } else { + return switch (id) { + case 0 -> Pose.STANDING; + case 1 -> Pose.FALL_FLYING; + case 2 -> Pose.SLEEPING; + case 3 -> Pose.SWIMMING; + case 4 -> Pose.SPIN_ATTACK; + case 5 -> Pose.CROUCHING; + default -> version.noLessThan(ProtocolVersion.MINECRAFT_1_17) ? switch (id) { + // >=1.17 + case 6 -> Pose.LONG_JUMPING; + case 7 -> Pose.DYING; + default -> version.noLessThan(ProtocolVersion.MINECRAFT_1_19) ? switch (id) { + // >=1.19 + case 8 -> Pose.CROAKING; + case 9 -> Pose.USING_TONGUE; + default -> version.noLessThan(ProtocolVersion.MINECRAFT_1_19_3) ? switch (id) { + // >=1.19.3 + case 10 -> Pose.SITTING; + case 11 -> Pose.ROARING; + case 12 -> Pose.SNIFFING; + case 13 -> Pose.EMERGING; + case 14 -> Pose.DIGGING; + default -> Pose.fail(id); + } : switch (id) { + // >=1.19 + case 10 -> Pose.ROARING; + case 11 -> Pose.SNIFFING; + case 12 -> Pose.EMERGING; + case 13 -> Pose.DIGGING; + default -> Pose.fail(id); + }; + } : Pose.fail(id); + } : id == 6 ? Pose.DYING : Pose.fail(id); + }; + } + } + + private static T fail(Object value) { + throw new IllegalStateException("Unexpected value: " + value); + } + } + + /** + * @sinceMinecraft 1.19 + */ + public record CatVariant(int id) { + + } + + /** + * @sinceMinecraft 1.21.5 + */ + public record CowVariant(int id) { + + } + + /** + * @sinceMinecraft 1.20.5 + */ + public sealed interface WolfVariant permits WolfVariant.Direct, WolfVariant.Reference { + + static WolfVariant.Reference of(int id) { + return new WolfVariant.Reference(id); + } + + record Reference(int id) implements WolfVariant { + + } + + /** + * Present only in versions [1.21-1.21.4] + *

+ * For versions outside this range, {@code WolfVariant.Direct} values are replaced with ID {@code 0}. + */ + @ApiStatus.Obsolete + record Direct(String wildTexture, String tameTexture, String angryTexture, HolderSet biomes) implements WolfVariant { + + } + } + + /** + * @sinceMinecraft 1.21.5 + */ + public record WolfSoundVariant(int id) { + + } + + /** + * @sinceMinecraft 1.19 + */ + public record FrogVariant(int id) { + + } + + /** + * @sinceMinecraft 1.21.5 + */ + public record PigVariant(int id) { + + } + + /** + * @sinceMinecraft 1.21.5 + */ + public record ChickenVariant(int id) { + + } + + /** + * @sinceMinecraft 1.19 + */ + public record OptionalGlobalPos(@Nullable GlobalPos globalPos) { + + public static final OptionalGlobalPos EMPTY = new OptionalGlobalPos(null); + + public GlobalPos orElseThrow() { + if (this.globalPos == null) { + throw new NoSuchElementException("No value present"); + } + + return this.globalPos; + } + + public static OptionalGlobalPos of(@Nullable GlobalPos globalPos) { + return globalPos == null ? OptionalGlobalPos.EMPTY : new OptionalGlobalPos(globalPos); + } + } + + /** + * @param painting For versions prior to 1.21, only {@code Holder.Reference} is expected. + * Direct {@link PaintingVariant} values are replaced with an ID {@code 0}. + *
+ * Since version 1.21, either {@code PaintingVariant} or {@code Holder.Reference} can be provided. + * + * @sinceMinecraft 1.19 + */ + public record PaintingVariantHolder(Holder painting) { + + } + + /** + * @sinceMinecraft 1.19.4 + */ + public enum SnifferState { + + IDLING, + FEELING_HAPPY, + SCENTING, + SNIFFING, + SEARCHING, + DIGGING, + RISING; + + public static final SnifferState[] VALUES = SnifferState.values(); + } + + /** + * @sinceMinecraft 1.20.5 + */ + public enum ArmadilloState { // 🤠 + + IDLE, + ROLLING, + SCARED, + UNROLLING; + + public static final ArmadilloState[] VALUES = ArmadilloState.values(); + } + + /** + * @sinceMinecraft 1.21.5 + */ + public enum CopperGolemState { + + IDLE, + GETTING_ITEM, + GETTING_NO_ITEM, + DROPPING_ITEM, + DROPPING_NO_ITEM; + + public static final CopperGolemState[] VALUES = CopperGolemState.values(); + } + + /** + * @sinceMinecraft 1.21.5 + */ + public enum WeatheringCopperState { + + UNAFFECTED, + EXPOSED, + WEATHERED, + OXIDIZED; + + public static final WeatheringCopperState[] VALUES = WeatheringCopperState.values(); + } + + /** + * @sinceMinecraft 1.19.4 + */ + public record Vector3(float x, float y, float z) { + + } + + /** + * @sinceMinecraft 1.19.4 + */ + public record Quaternion(float x, float y, float z, float w) { + + } +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/protocol/item/ItemComponent.java b/api/src/main/java/net/elytrium/limboapi/api/protocol/data/GlobalPos.java similarity index 58% rename from api/src/main/java/net/elytrium/limboapi/api/protocol/item/ItemComponent.java rename to api/src/main/java/net/elytrium/limboapi/api/protocol/data/GlobalPos.java index b5e50ac9..745b53db 100644 --- a/api/src/main/java/net/elytrium/limboapi/api/protocol/item/ItemComponent.java +++ b/api/src/main/java/net/elytrium/limboapi/api/protocol/data/GlobalPos.java @@ -5,13 +5,11 @@ * reference the LICENSE file in the api top-level directory. */ -package net.elytrium.limboapi.api.protocol.item; +package net.elytrium.limboapi.api.protocol.data; -public interface ItemComponent { - - String getName(); - - ItemComponent setValue(T value); +/** + * @sinceMinecraft 1.19 + */ +public record GlobalPos(String dimension, BlockPos blockPos) { - T getValue(); } diff --git a/api/src/main/java/net/elytrium/limboapi/api/protocol/data/ItemStack.java b/api/src/main/java/net/elytrium/limboapi/api/protocol/data/ItemStack.java new file mode 100644 index 00000000..ac15bba0 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/protocol/data/ItemStack.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2021 - 2024 Elytrium + * + * The LimboAPI (excluding the LimboAPI plugin) is licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in the api top-level directory. + */ + +package net.elytrium.limboapi.api.protocol.data; + +import com.velocitypowered.api.network.ProtocolVersion; +import net.elytrium.limboapi.api.world.WorldVersion; +import net.elytrium.limboapi.api.world.item.VirtualItem; +import net.elytrium.limboapi.api.world.item.datacomponent.DataComponentMap; +import net.elytrium.limboapi.api.world.item.datacomponent.DataComponentTypes; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import org.checkerframework.checker.nullness.qual.Nullable; + +public record ItemStack(VirtualItem material, int amount, int data, @Nullable CompoundBinaryTag nbt, @Nullable DataComponentMap map) implements EntityData.Particle.ParticleOptions { + + public static final ItemStack EMPTY = new ItemStack(null, 0, 0, null, null); + + public ItemStack(VirtualItem material, int amount, @Nullable CompoundBinaryTag nbt) { + this(material, amount, nbt == null ? 0 : nbt.getInt("Damage", 0), nbt, null); + } + + public ItemStack(VirtualItem material, int amount, @Nullable DataComponentMap map) { + this(material, amount, map == null ? 0 : map.getDataOrDefault(DataComponentTypes.DAMAGE, 0), map); + } + + public ItemStack(VirtualItem material, int amount, int data) { + this(material, amount, data, null, null); + } + + public ItemStack(VirtualItem material, int amount, int data, @Nullable CompoundBinaryTag nbt) { + this(material, amount, data, nbt, null); + } + + public ItemStack(VirtualItem material, int amount, int data, @Nullable DataComponentMap map) { + this(material, amount, data, null, map); + } + + public boolean isEmpty(ProtocolVersion version, boolean checkDamage) { + return this.isEmpty(WorldVersion.from(version), checkDamage); + } + + public boolean isEmpty(WorldVersion version, boolean checkDamage) { + return this == ItemStack.EMPTY || this.material.itemId(version) <= 0 || this.amount <= 0 || (checkDamage && (this.data < -32768 || this.data > 65535)); + } +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/protocol/packets/data/MapData.java b/api/src/main/java/net/elytrium/limboapi/api/protocol/data/MapData.java similarity index 51% rename from api/src/main/java/net/elytrium/limboapi/api/protocol/packets/data/MapData.java rename to api/src/main/java/net/elytrium/limboapi/api/protocol/data/MapData.java index c2f40976..71a607b7 100644 --- a/api/src/main/java/net/elytrium/limboapi/api/protocol/packets/data/MapData.java +++ b/api/src/main/java/net/elytrium/limboapi/api/protocol/data/MapData.java @@ -5,65 +5,46 @@ * reference the LICENSE file in the api top-level directory. */ -package net.elytrium.limboapi.api.protocol.packets.data; +package net.elytrium.limboapi.api.protocol.data; /** - * For MapData packet. + * For MapData packet */ -public class MapData { +public record MapData(int columns, int rows, int posX, int posY, byte[] data) { public static final int MAP_DIM_SIZE = 128; public static final int MAP_SIZE = MAP_DIM_SIZE * MAP_DIM_SIZE; // 128² == 16384 - private final int columns; - private final int rows; - private final int posX; - private final int posY; - private final byte[] data; - public MapData(byte[] data) { this(0, data); } public MapData(int posX, byte[] data) { - this(MAP_DIM_SIZE, MAP_DIM_SIZE, posX, 0, data); - } - - public MapData(int columns, int rows, int posX, int posY, byte[] data) { - this.columns = columns; - this.rows = rows; - this.posX = posX; - this.posY = posY; - this.data = data; + this(MapData.MAP_DIM_SIZE, MapData.MAP_DIM_SIZE, posX, 0, data); } + @Deprecated(forRemoval = true) public int getColumns() { return this.columns; } + @Deprecated(forRemoval = true) public int getRows() { return this.rows; } + @Deprecated(forRemoval = true) public int getX() { return this.posX; } + @Deprecated(forRemoval = true) public int getY() { return this.posY; } + @Deprecated(forRemoval = true) public byte[] getData() { return this.data; } - - @Override - public String toString() { - return "MapData{" - + "columns=" + this.columns - + ", rows=" + this.rows - + ", posX=" + this.posX - + ", posY=" + this.posY - + "}"; - } } diff --git a/api/src/main/java/net/elytrium/limboapi/api/protocol/packets/data/MapPalette.java b/api/src/main/java/net/elytrium/limboapi/api/protocol/data/MapPalette.java similarity index 65% rename from api/src/main/java/net/elytrium/limboapi/api/protocol/packets/data/MapPalette.java rename to api/src/main/java/net/elytrium/limboapi/api/protocol/data/MapPalette.java index f4ea6264..7dc80b02 100644 --- a/api/src/main/java/net/elytrium/limboapi/api/protocol/packets/data/MapPalette.java +++ b/api/src/main/java/net/elytrium/limboapi/api/protocol/data/MapPalette.java @@ -5,7 +5,7 @@ * reference the LICENSE file in the api top-level directory. */ -package net.elytrium.limboapi.api.protocol.packets.data; +package net.elytrium.limboapi.api.protocol.data; import com.velocitypowered.api.network.ProtocolVersion; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; @@ -23,10 +23,10 @@ public class MapPalette { private static final Map REMAP_BUFFERS = new EnumMap<>(MapVersion.class); - private static final byte[] MAIN_BUFFER = readBuffer("/mapping/colors_main_map"); + private static final byte[] MAIN_BUFFER = MapPalette.readBuffer("/mappings/colors_main_map"); /** - * @deprecated Use {@link java.awt.Color#WHITE} instead. + * @deprecated Use {@link java.awt.Color#WHITE} instead */ @Deprecated public static final byte WHITE = 34; @@ -34,7 +34,7 @@ public class MapPalette { static { for (MapVersion version : MapVersion.values()) { - REMAP_BUFFERS.put(version, readBuffer("/mapping/colors_" + version.toString().toLowerCase(Locale.ROOT) + "_map")); + MapPalette.REMAP_BUFFERS.put(version, readBuffer("/mappings/colors_" + version.toString().toLowerCase(Locale.ROOT) + "_map")); } } @@ -47,24 +47,24 @@ private static byte[] readBuffer(String filename) { } /** - * Convert an Image to a byte[] using the palette. - * Uses reduced set of colors, to support more colors use {@link MapPalette#imageToBytes(BufferedImage, ProtocolVersion)} + * Convert an Image to a byte[] using the palette + * Uses a reduced set of colors, to support more colors use {@link MapPalette#imageToBytes(BufferedImage, ProtocolVersion)} * - * @param image The image to convert. + * @param image The image to convert * - * @return A byte[] containing the pixels of the image. + * @return A byte[] containing the pixels of the image */ public static int[] imageToBytes(BufferedImage image) { return imageToBytes(image, ProtocolVersion.MINIMUM_VERSION); } /** - * Convert an Image to a byte[] using the palette. + * Convert an Image to a byte[] using the palette * - * @param image The image to convert. - * @param version The ProtocolVersion to support more colors. + * @param image The image to convert + * @param version The ProtocolVersion to support more colors * - * @return A byte[] containing the pixels of the image. + * @return A byte[] containing the pixels of the image */ public static int[] imageToBytes(BufferedImage image, ProtocolVersion version) { int[] result = image.getRGB(0, 0, image.getWidth(), image.getHeight(), null, 0, image.getWidth()); @@ -72,25 +72,25 @@ public static int[] imageToBytes(BufferedImage image, ProtocolVersion version) { } /** - * Convert an image to a byte[] using the palette. + * Convert an image to a byte[] using the palette * - * @param image The image to convert. - * @param version The ProtocolVersion to support more colors. + * @param image The image to convert + * @param version The ProtocolVersion to support more colors * - * @return A byte[] containing the pixels of the image. + * @return A byte[] containing the pixels of the image */ public static int[] imageToBytes(int[] image, ProtocolVersion version) { return imageToBytes(image, new int[image.length], version); } /** - * Convert an image to a byte[] using the palette. + * Convert an image to a byte[] using the palette * - * @param from The image to convert. - * @param to Output image. - * @param version The ProtocolVersion to support more colors. + * @param from The image to convert + * @param to Output image + * @param version The ProtocolVersion to support more colors * - * @return A byte[] containing the pixels of the image. + * @return A byte[] containing the pixels of the image */ public static int[] imageToBytes(int[] from, int[] to, ProtocolVersion version) { for (int i = 0; i < from.length; ++i) { @@ -101,13 +101,13 @@ public static int[] imageToBytes(int[] from, int[] to, ProtocolVersion version) } /** - * Convert an image to a byte[] using the palette. + * Convert an image to a byte[] using the palette * - * @param from The image to convert. - * @param to Output image. - * @param version The ProtocolVersion to support more colors. + * @param from The image to convert + * @param to Output image + * @param version The ProtocolVersion to support more colors * - * @return A byte[] containing the pixels of the image. + * @return A byte[] containing the pixels of the image */ public static byte[] imageToBytes(int[] from, byte[] to, ProtocolVersion version) { for (int i = 0; i < from.length; ++i) { @@ -118,23 +118,23 @@ public static byte[] imageToBytes(int[] from, byte[] to, ProtocolVersion version } /** - * Get the index of the closest matching color in the palette to the given - * color. Uses caching and downscaling of color values. + * Get the index of the closest matching color in the palette to the given color + * Uses caching and downscaling of color values * - * @param rgb The Color to match. + * @param rgb The Color to match * - * @return The index in the palette. + * @return The index in the palette */ public static byte tryFastMatchColor(int rgb, ProtocolVersion version) { if (getAlpha(rgb) < 128) { return TRANSPARENT; } else { MapVersion mapVersion = MapVersion.fromProtocolVersion(version); - byte originalColorID = MAIN_BUFFER[rgb & 0xFFFFFF]; + byte originalColorId = MAIN_BUFFER[rgb & 0xFFFFFF]; if (mapVersion == MapVersion.MAXIMUM_VERSION) { - return originalColorID; + return originalColorId; } else { - return remapByte(REMAP_BUFFERS.get(mapVersion), originalColorID); + return remapByte(REMAP_BUFFERS.get(mapVersion), originalColorId); } } } @@ -146,10 +146,10 @@ private static int getAlpha(int rgb) { /** * Convert an image from MapVersion.MAXIMUM_VERSION to the desired version * - * @param image The image to convert. - * @param version The ProtocolVersion to support more colors. + * @param image The image to convert + * @param version The ProtocolVersion to support more colors * - * @return A byte[] containing the pixels of the image. + * @return A byte[] containing the pixels of the image */ public static int[] convertImage(int[] image, MapVersion version) { return convertImage(image, new int[image.length], version); @@ -158,11 +158,11 @@ public static int[] convertImage(int[] image, MapVersion version) { /** * Convert an image from MapVersion.MAXIMUM_VERSION to the desired version * - * @param from The image to convert. - * @param to Output image. - * @param version The ProtocolVersion to support more colors. + * @param from The image to convert + * @param to Output image + * @param version The ProtocolVersion to support more colors * - * @return A byte[] containing the pixels of the image. + * @return A byte[] containing the pixels of the image */ public static int[] convertImage(int[] from, int[] to, MapVersion version) { byte[] remapBuffer = REMAP_BUFFERS.get(version); @@ -176,11 +176,11 @@ public static int[] convertImage(int[] from, int[] to, MapVersion version) { /** * Convert an image from MapVersion.MAXIMUM_VERSION to the desired version * - * @param from The image to convert. - * @param to Output image. - * @param version The ProtocolVersion to support more colors. + * @param from The image to convert + * @param to Output image + * @param version The ProtocolVersion to support more colors * - * @return A byte[] containing the pixels of the image. + * @return A byte[] containing the pixels of the image */ public static byte[] convertImage(byte[] from, byte[] to, MapVersion version) { byte[] remapBuffer = REMAP_BUFFERS.get(version); @@ -194,11 +194,11 @@ public static byte[] convertImage(byte[] from, byte[] to, MapVersion version) { /** * Convert an image from MapVersion.MAXIMUM_VERSION to the desired version * - * @param from The image to convert. - * @param to Output image. - * @param version The ProtocolVersion to support more colors. + * @param from The image to convert + * @param to Output image + * @param version The ProtocolVersion to support more colors * - * @return A byte[] containing the pixels of the image. + * @return A byte[] containing the pixels of the image */ public static byte[] convertImage(int[] from, byte[] to, MapVersion version) { byte[] remapBuffer = REMAP_BUFFERS.get(version); @@ -210,7 +210,7 @@ public static byte[] convertImage(int[] from, byte[] to, MapVersion version) { } private static byte remapByte(byte[] remapBuffer, byte oldByte) { - return remapBuffer[Byte.toUnsignedInt(oldByte)]; + return remapBuffer[oldByte & 0xFF]; } public enum MapVersion { @@ -236,13 +236,13 @@ public EnumSet getVersions() { } static { - for (MapVersion value : MapVersion.values()) { - value.versions.forEach(version -> VERSIONS_MAP.put(version, value)); + for (MapVersion version : MapVersion.values()) { + version.getVersions().forEach(protocolVersion -> VERSIONS_MAP.put(protocolVersion, version)); } } public static MapVersion fromProtocolVersion(ProtocolVersion version) { - return VERSIONS_MAP.get(version); + return Objects.requireNonNull(VERSIONS_MAP.get(version)); } } } diff --git a/api/src/main/java/net/elytrium/limboapi/api/protocol/item/ItemComponentMap.java b/api/src/main/java/net/elytrium/limboapi/api/protocol/item/ItemComponentMap.java deleted file mode 100644 index 31fd468a..00000000 --- a/api/src/main/java/net/elytrium/limboapi/api/protocol/item/ItemComponentMap.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * The LimboAPI (excluding the LimboAPI plugin) is licensed under the terms of the MIT License. For more details, - * reference the LICENSE file in the api top-level directory. - */ - -package net.elytrium.limboapi.api.protocol.item; - -import com.velocitypowered.api.network.ProtocolVersion; -import java.util.List; - -public interface ItemComponentMap { - - ItemComponentMap add(ProtocolVersion version, String name, T value); - - ItemComponentMap remove(ProtocolVersion version, String name); - - List getAdded(); - - List getRemoved(); - - void read(ProtocolVersion version, Object buffer); - - void write(ProtocolVersion version, Object buffer); -} diff --git a/api/src/main/java/net/elytrium/limboapi/api/protocol/map/MapPalette.java b/api/src/main/java/net/elytrium/limboapi/api/protocol/map/MapPalette.java deleted file mode 100644 index 629d8949..00000000 --- a/api/src/main/java/net/elytrium/limboapi/api/protocol/map/MapPalette.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * The LimboAPI (excluding the LimboAPI plugin) is licensed under the terms of the MIT License. For more details, - * reference the LICENSE file in the api top-level directory. - */ - -package net.elytrium.limboapi.api.protocol.map; - -import com.velocitypowered.api.network.ProtocolVersion; -import java.awt.image.BufferedImage; - -@Deprecated(forRemoval = true) -public class MapPalette { - - public static int[] imageToBytes(BufferedImage image) { - return net.elytrium.limboapi.api.protocol.packets.data.MapPalette.imageToBytes(image); - } - - public static int[] imageToBytes(BufferedImage image, ProtocolVersion version) { - return net.elytrium.limboapi.api.protocol.packets.data.MapPalette.imageToBytes(image, version); - } - - public static byte tryFastMatchColor(int rgb, ProtocolVersion version) { - return net.elytrium.limboapi.api.protocol.packets.data.MapPalette.tryFastMatchColor(rgb, version); - } -} diff --git a/api/src/main/java/net/elytrium/limboapi/api/protocol/packets/PacketFactory.java b/api/src/main/java/net/elytrium/limboapi/api/protocol/packets/PacketFactory.java deleted file mode 100644 index bc0529d0..00000000 --- a/api/src/main/java/net/elytrium/limboapi/api/protocol/packets/PacketFactory.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * The LimboAPI (excluding the LimboAPI plugin) is licensed under the terms of the MIT License. For more details, - * reference the LICENSE file in the api top-level directory. - */ - -package net.elytrium.limboapi.api.protocol.packets; - -import com.velocitypowered.api.network.ProtocolVersion; -import java.util.List; -import java.util.Map; -import net.elytrium.limboapi.api.chunk.Dimension; -import net.elytrium.limboapi.api.chunk.data.ChunkSnapshot; -import net.elytrium.limboapi.api.material.VirtualItem; -import net.elytrium.limboapi.api.material.WorldVersion; -import net.elytrium.limboapi.api.protocol.item.ItemComponentMap; -import net.elytrium.limboapi.api.protocol.packets.data.AbilityFlags; -import net.elytrium.limboapi.api.protocol.packets.data.MapData; -import net.kyori.adventure.nbt.CompoundBinaryTag; -import org.checkerframework.checker.nullness.qual.Nullable; - -public interface PacketFactory { - - Object createChangeGameStatePacket(int reason, float value); - - Object createChunkDataPacket(ChunkSnapshot chunkSnapshot, boolean legacySkyLight, int maxSections); - - Object createChunkDataPacket(ChunkSnapshot chunkSnapshot, Dimension dimension); - - Object createChunkUnloadPacket(int posX, int posZ); - - Object createDefaultSpawnPositionPacket(int posX, int posY, int posZ, float angle); - - Object createMapDataPacket(int mapID, byte scale, MapData mapData); - - /** - * @param flags See {@link AbilityFlags}. (e.g. {@code AbilityFlags.ALLOW_FLYING | AbilityFlags.CREATIVE_MODE}) - */ - Object createPlayerAbilitiesPacket(int flags, float flySpeed, float walkSpeed); - - /** - * @param flags See {@link AbilityFlags}. (e.g. {@code AbilityFlags.ALLOW_FLYING | AbilityFlags.CREATIVE_MODE}) - */ - Object createPlayerAbilitiesPacket(byte flags, float flySpeed, float walkSpeed); - - Object createPositionRotationPacket(double posX, double posY, double posZ, float yaw, float pitch, - boolean onGround, int teleportID, boolean dismountVehicle); - - Object createSetExperiencePacket(float expBar, int level, int totalExp); - - Object createSetSlotPacket(int windowID, int slot, VirtualItem item, int count, int data, @Nullable CompoundBinaryTag nbt); - - Object createSetSlotPacket(int windowID, int slot, VirtualItem item, int count, int data, @Nullable ItemComponentMap map); - - Object createTimeUpdatePacket(long worldAge, long timeOfDay); - - Object createUpdateViewPositionPacket(int posX, int posZ); - - Object createUpdateTagsPacket(WorldVersion version); - - Object createUpdateTagsPacket(ProtocolVersion version); - - Object createUpdateTagsPacket(Map>> tags); -} diff --git a/api/src/main/java/net/elytrium/limboapi/api/protocol/packets/data/AbilityFlags.java b/api/src/main/java/net/elytrium/limboapi/api/protocol/packets/data/AbilityFlags.java deleted file mode 100644 index 5253c408..00000000 --- a/api/src/main/java/net/elytrium/limboapi/api/protocol/packets/data/AbilityFlags.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * The LimboAPI (excluding the LimboAPI plugin) is licensed under the terms of the MIT License. For more details, - * reference the LICENSE file in the api top-level directory. - */ - -package net.elytrium.limboapi.api.protocol.packets.data; - -/** - * For PlayerAbilities packet. - */ -public class AbilityFlags { - - public static final int INVULNERABLE = 1; - public static final int FLYING = 2; - public static final int ALLOW_FLYING = 4; - public static final int CREATIVE_MODE = 8; -} diff --git a/api/src/main/java/net/elytrium/limboapi/api/utils/EnumUniverse.java b/api/src/main/java/net/elytrium/limboapi/api/utils/EnumUniverse.java deleted file mode 100644 index 1e8a86ad..00000000 --- a/api/src/main/java/net/elytrium/limboapi/api/utils/EnumUniverse.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * The LimboAPI (excluding the LimboAPI plugin) is licensed under the terms of the MIT License. For more details, - * reference the LICENSE file in the api top-level directory. - */ - -package net.elytrium.limboapi.api.utils; - -import java.util.HashMap; -import java.util.Map; - -public final class EnumUniverse { - - private EnumUniverse() { - - } - - public static > Map createProtocolLookup(T[] values) { - Map lookup = new HashMap<>(); - for (T value : values) { - if (value.name().startsWith("MINECRAFT_")) { - lookup.put(value.name().substring("MINECRAFT_".length()).replace("_", "."), value); - } - } - return lookup; - } -} diff --git a/api/src/main/java/net/elytrium/limboapi/api/utils/OverlayMap.java b/api/src/main/java/net/elytrium/limboapi/api/utils/OverlayMap.java deleted file mode 100644 index 0ef2ca21..00000000 --- a/api/src/main/java/net/elytrium/limboapi/api/utils/OverlayMap.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * The LimboAPI (excluding the LimboAPI plugin) is licensed under the terms of the MIT License. For more details, - * reference the LICENSE file in the api top-level directory. - */ - -package net.elytrium.limboapi.api.utils; - -import java.util.Map; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public abstract class OverlayMap implements Map { - - protected boolean override = false; - protected final Map parent; - protected final Map overlay; - - public OverlayMap(Map parent, Map overlay) { - this.parent = parent; - this.overlay = overlay; - } - - @Override - public int size() { - return this.parent.size() + this.overlay.size(); - } - - @Override - public boolean isEmpty() { - return this.parent.isEmpty() && this.overlay.isEmpty(); - } - - @Override - public boolean containsKey(Object o) { - if (this.override) { - return this.overlay.containsKey(o); - } - - return this.parent.containsKey(o) || this.overlay.containsKey(o); - } - - @Override - public boolean containsValue(Object o) { - if (this.override) { - return this.overlay.containsValue(o); - } - - return this.parent.containsValue(o) || this.overlay.containsValue(o); - } - - @Override - @SuppressWarnings("SuspiciousMethodCalls") - public V get(Object o) { - if (this.overlay.containsKey(o)) { - return this.overlay.get(o); - } - - return this.parent.get(o); - } - - @Nullable - @Override - public V put(K k, V v) { - return this.overlay.put(k, v); - } - - @Override - public V remove(Object o) { - return this.overlay.remove(o); - } - - @Override - public void putAll(@NotNull Map map) { - this.overlay.putAll(map); - } - - @Override - public void clear() { - this.overlay.clear(); - } - - public boolean isOverride() { - return this.override; - } - - public void setOverride(boolean override) { - this.override = override; - } -} diff --git a/api/src/main/java/net/elytrium/limboapi/api/utils/OverlayVanillaMap.java b/api/src/main/java/net/elytrium/limboapi/api/utils/OverlayVanillaMap.java deleted file mode 100644 index 74d07440..00000000 --- a/api/src/main/java/net/elytrium/limboapi/api/utils/OverlayVanillaMap.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * The LimboAPI (excluding the LimboAPI plugin) is licensed under the terms of the MIT License. For more details, - * reference the LICENSE file in the api top-level directory. - */ - -package net.elytrium.limboapi.api.utils; - -import com.google.common.collect.Streams; -import java.util.Collection; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; -import org.jetbrains.annotations.NotNull; - -public class OverlayVanillaMap extends OverlayMap { - - public OverlayVanillaMap(Map parent, Map overlay) { - super(parent, overlay); - } - - @Override - public Set keySet() { - return Streams.concat(this.parent.keySet().stream(), this.overlay.keySet().stream()).collect(Collectors.toSet()); - } - - @NotNull - @Override - public Collection values() { - return Streams.concat(this.parent.values().stream(), this.overlay.values().stream()).collect(Collectors.toList()); - } - - @NotNull - @Override - public Set> entrySet() { - return Streams.concat(this.parent.entrySet().stream(), this.overlay.entrySet().stream()).collect(Collectors.toSet()); - } -} diff --git a/api/src/main/java/net/elytrium/limboapi/api/file/BuiltInWorldFileType.java b/api/src/main/java/net/elytrium/limboapi/api/world/BuiltInWorldFileType.java similarity index 87% rename from api/src/main/java/net/elytrium/limboapi/api/file/BuiltInWorldFileType.java rename to api/src/main/java/net/elytrium/limboapi/api/world/BuiltInWorldFileType.java index 35da4502..3bcce19b 100644 --- a/api/src/main/java/net/elytrium/limboapi/api/file/BuiltInWorldFileType.java +++ b/api/src/main/java/net/elytrium/limboapi/api/world/BuiltInWorldFileType.java @@ -5,9 +5,10 @@ * reference the LICENSE file in the api top-level directory. */ -package net.elytrium.limboapi.api.file; +package net.elytrium.limboapi.api.world; public enum BuiltInWorldFileType { + SCHEMATIC, WORLDEDIT_SCHEM, STRUCTURE diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/VirtualWorld.java b/api/src/main/java/net/elytrium/limboapi/api/world/VirtualWorld.java new file mode 100644 index 00000000..7bdcc9e6 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/VirtualWorld.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2021 - 2025 Elytrium + * + * The LimboAPI (excluding the LimboAPI plugin) is licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in the api top-level directory. + */ + +package net.elytrium.limboapi.api.world; + +import java.util.List; +import net.elytrium.limboapi.api.world.chunk.Dimension; +import net.elytrium.limboapi.api.world.chunk.VirtualChunk; +import net.elytrium.limboapi.api.world.chunk.biome.VirtualBiome; +import net.elytrium.limboapi.api.world.chunk.block.VirtualBlock; +import net.elytrium.limboapi.api.world.chunk.blockentity.VirtualBlockEntity; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.common.value.qual.IntRange; +import org.jetbrains.annotations.NotNull; + +public interface VirtualWorld { + + void setBlockEntity(int posX, int posY, int posZ, @Nullable CompoundBinaryTag nbt, @Nullable VirtualBlockEntity blockEntity); + + void setBlock(int posX, int posY, int posZ, @Nullable VirtualBlock block); + + /** + * @return The block at the specified coordinates, or {@code AIR} if no block exists + */ + @NonNull + VirtualBlock getBlock(int posX, int posY, int posZ); + + @Deprecated(forRemoval = true) + default void setBiome2d(int posX, int posZ, @NonNull VirtualBiome biome) { + this.setBiome2D(posX, posZ, biome); + } + + void setBiome2D(int posX, int posZ, @NonNull VirtualBiome biome); + + @Deprecated(forRemoval = true) + default void setBiome3d(int posX, int posY, int posZ, @NonNull VirtualBiome biome) { + this.setBiome3D(posX, posY, posZ, biome); + } + + void setBiome3D(int posX, int posY, int posZ, @NonNull VirtualBiome biome); + + /** + * @return The biome at the specified coordinates, or {@code PLAINS} if no biome exists + */ + @NotNull + VirtualBiome getBiome(int posX, int posY, int posZ); + + void setBlockLight(int posX, int posY, int posZ, byte light); + + byte getBlockLight(int posX, int posY, int posZ); + + @Deprecated(forRemoval = true) + default void fillBlockLight(@IntRange(from = 0, to = 15) int level) { + this.fillBlockLight((byte) level); + } + + void fillBlockLight(@IntRange(from = 0, to = 15) byte level); + + @Deprecated(forRemoval = true) + default void fillSkyLight(@IntRange(from = 0, to = 15) int level) { + this.fillSkyLight((byte) level); + } + + void fillSkyLight(@IntRange(from = 0, to = 15) byte level); + + List getChunks(); + + List> getOrderedChunks(); + + @Nullable + VirtualChunk getChunk(int posX, int posZ); + + @NonNull + VirtualChunk getChunkOrNew(int posX, int posZ); + + @NonNull + Dimension getDimension(); + + double getSpawnX(); + + double getSpawnY(); + + double getSpawnZ(); + + float getYaw(); + + float getPitch(); +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/file/WorldFile.java b/api/src/main/java/net/elytrium/limboapi/api/world/WorldFile.java similarity index 53% rename from api/src/main/java/net/elytrium/limboapi/api/file/WorldFile.java rename to api/src/main/java/net/elytrium/limboapi/api/world/WorldFile.java index ee6a5ac2..78545925 100644 --- a/api/src/main/java/net/elytrium/limboapi/api/file/WorldFile.java +++ b/api/src/main/java/net/elytrium/limboapi/api/world/WorldFile.java @@ -5,17 +5,21 @@ * reference the LICENSE file in the api top-level directory. */ -package net.elytrium.limboapi.api.file; +package net.elytrium.limboapi.api.world; import net.elytrium.limboapi.api.LimboFactory; -import net.elytrium.limboapi.api.chunk.VirtualWorld; import org.checkerframework.common.value.qual.IntRange; public interface WorldFile { default void toWorld(LimboFactory factory, VirtualWorld world, int offsetX, int offsetY, int offsetZ) { - this.toWorld(factory, world, offsetX, offsetY, offsetZ, 15); + this.toWorld(factory, world, offsetX, offsetY, offsetZ, (byte) 15); } - void toWorld(LimboFactory factory, VirtualWorld world, int offsetX, int offsetY, int offsetZ, @IntRange(from = 0, to = 15) int lightLevel); + @Deprecated(forRemoval = true) + default void toWorld(LimboFactory factory, VirtualWorld world, int offsetX, int offsetY, int offsetZ, @IntRange(from = 0, to = 15) int lightLevel) { + this.toWorld(factory, world, offsetX, offsetY, offsetZ, (byte) lightLevel); + } + + void toWorld(LimboFactory factory, VirtualWorld world, int offsetX, int offsetY, int offsetZ, @IntRange(from = 0, to = 15) byte lightLevel); } diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/WorldVersion.java b/api/src/main/java/net/elytrium/limboapi/api/world/WorldVersion.java new file mode 100644 index 00000000..b1a7b582 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/WorldVersion.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2021 - 2025 Elytrium + * + * The LimboAPI (excluding the LimboAPI plugin) is licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in the api top-level directory. + */ + +package net.elytrium.limboapi.api.world; + +import com.velocitypowered.api.network.ProtocolVersion; +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.Objects; + +public enum WorldVersion { + + LEGACY(EnumSet.range(ProtocolVersion.MINECRAFT_1_7_2, ProtocolVersion.MINECRAFT_1_12_2)), // TODO better legacy support + MINECRAFT_1_13(EnumSet.of(ProtocolVersion.MINECRAFT_1_13)), + MINECRAFT_1_13_2(EnumSet.of(ProtocolVersion.MINECRAFT_1_13_1, ProtocolVersion.MINECRAFT_1_13_2)), + MINECRAFT_1_14(EnumSet.range(ProtocolVersion.MINECRAFT_1_14, ProtocolVersion.MINECRAFT_1_14_4)), + MINECRAFT_1_15(EnumSet.range(ProtocolVersion.MINECRAFT_1_15, ProtocolVersion.MINECRAFT_1_15_2)), + MINECRAFT_1_16(EnumSet.of(ProtocolVersion.MINECRAFT_1_16, ProtocolVersion.MINECRAFT_1_16_1)), + MINECRAFT_1_16_2(EnumSet.range(ProtocolVersion.MINECRAFT_1_16_2, ProtocolVersion.MINECRAFT_1_16_4)), + MINECRAFT_1_17(EnumSet.range(ProtocolVersion.MINECRAFT_1_17, ProtocolVersion.MINECRAFT_1_18_2)), + MINECRAFT_1_19(EnumSet.range(ProtocolVersion.MINECRAFT_1_19, ProtocolVersion.MINECRAFT_1_19_1)), + MINECRAFT_1_19_3(EnumSet.of(ProtocolVersion.MINECRAFT_1_19_3)), + MINECRAFT_1_19_4(EnumSet.range(ProtocolVersion.MINECRAFT_1_19_4, ProtocolVersion.MINECRAFT_1_20)), + MINECRAFT_1_20(EnumSet.range(ProtocolVersion.MINECRAFT_1_20, ProtocolVersion.MINECRAFT_1_20_2)), + MINECRAFT_1_20_3(EnumSet.of(ProtocolVersion.MINECRAFT_1_20_3)), + MINECRAFT_1_20_5(EnumSet.of(ProtocolVersion.MINECRAFT_1_20_5)), + MINECRAFT_1_21(EnumSet.of(ProtocolVersion.MINECRAFT_1_21)), + MINECRAFT_1_21_2(EnumSet.of(ProtocolVersion.MINECRAFT_1_21_2)), + MINECRAFT_1_21_4(EnumSet.of(ProtocolVersion.MINECRAFT_1_21_4)), + MINECRAFT_1_21_5(EnumSet.of(ProtocolVersion.MINECRAFT_1_21_5)), + MINECRAFT_1_21_6(EnumSet.of(ProtocolVersion.MINECRAFT_1_21_6)), + MINECRAFT_1_21_7(EnumSet.of(ProtocolVersion.MINECRAFT_1_21_7)), + MINECRAFT_1_21_9(EnumSet.of(ProtocolVersion.MINECRAFT_1_21_9)); + + private static final EnumMap VERSIONS_MAP = new EnumMap<>(ProtocolVersion.class); + + private final EnumSet versions; + + WorldVersion(EnumSet versions) { + this.versions = versions; + } + + public EnumSet getVersions() { + return this.versions; + } + + static { + for (WorldVersion version : WorldVersion.values()) { + version.getVersions().forEach(protocolVersion -> WorldVersion.VERSIONS_MAP.put(protocolVersion, version)); + } + } + + public static WorldVersion from(ProtocolVersion protocolVersion) { + return Objects.requireNonNull(WorldVersion.VERSIONS_MAP.get(protocolVersion)); + } +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/chunk/ChunkSnapshot.java b/api/src/main/java/net/elytrium/limboapi/api/world/chunk/ChunkSnapshot.java new file mode 100644 index 00000000..229da5ac --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/chunk/ChunkSnapshot.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2021 - 2025 Elytrium + * + * The LimboAPI (excluding the LimboAPI plugin) is licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in the api top-level directory. + */ + +package net.elytrium.limboapi.api.world.chunk; + +import com.velocitypowered.api.network.ProtocolVersion; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; +import net.elytrium.limboapi.api.world.chunk.biome.VirtualBiome; +import net.elytrium.limboapi.api.world.chunk.block.VirtualBlock; +import net.elytrium.limboapi.api.world.chunk.blockentity.VirtualBlockEntity; +import net.elytrium.limboapi.api.world.chunk.data.BlockSection; +import net.elytrium.limboapi.api.world.chunk.data.LightSection; + +public interface ChunkSnapshot { + + VirtualBlock getBlock(int posX, int posY, int posZ); + + @Deprecated(forRemoval = true) + default int getPosX() { + return this.posX(); + } + + int posX(); + + @Deprecated(forRemoval = true) + default int getPosZ() { + return this.posZ(); + } + + int posZ(); + + @Deprecated(forRemoval = true) + default boolean isFullChunk() { + return this.fullChunk(); + } + + boolean fullChunk(); + + @Deprecated(forRemoval = true) + default BlockSection[] getSections() { + return this.sections(); + } + + BlockSection[] sections(); + + @Deprecated(forRemoval = true) + default LightSection[] getLight() { + return this.light(); + } + + LightSection[] light(); + + @Deprecated(forRemoval = true) + default VirtualBiome[] getBiomes() { + return this.biomes(); + } + + VirtualBiome[] biomes(); + + @Deprecated(forRemoval = true) + default List getBlockEntityEntries() { + return List.of(this.blockEntityEntries()); + } + + default VirtualBlockEntity.Entry[] blockEntityEntries(ProtocolVersion version) { + return version.lessThan(ProtocolVersion.MINECRAFT_1_17) + ? this.blockEntityEntriesStream(version).toArray(VirtualBlockEntity.Entry[]::new) + : this.blockEntityEntries(); + } + + default Stream blockEntityEntriesStream(ProtocolVersion version) { + var stream = Arrays.stream(this.blockEntityEntries()); + return version.lessThan(ProtocolVersion.MINECRAFT_1_17) + ? stream.filter(entry -> entry.getPosY() >= 0 && entry.getPosY() <= 255) + : stream; + } + + VirtualBlockEntity.Entry[] blockEntityEntries(); +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/chunk/Dimension.java b/api/src/main/java/net/elytrium/limboapi/api/world/chunk/Dimension.java similarity index 56% rename from api/src/main/java/net/elytrium/limboapi/api/chunk/Dimension.java rename to api/src/main/java/net/elytrium/limboapi/api/world/chunk/Dimension.java index faf85fb8..ada5e314 100644 --- a/api/src/main/java/net/elytrium/limboapi/api/chunk/Dimension.java +++ b/api/src/main/java/net/elytrium/limboapi/api/world/chunk/Dimension.java @@ -5,7 +5,9 @@ * reference the LICENSE file in the api top-level directory. */ -package net.elytrium.limboapi.api.chunk; +package net.elytrium.limboapi.api.world.chunk; + +import net.elytrium.limboapi.api.world.chunk.biome.BuiltInBiome; public enum Dimension { @@ -14,18 +16,18 @@ public enum Dimension { THE_END("minecraft:the_end", 1, 2, 16, false, BuiltInBiome.THE_END); // 256 / 16 private final String key; - private final int legacyID; - private final int modernID; + private final int legacyId; + private final int modernId; private final int maxSections; - private final boolean hasLegacySkyLight; + private final boolean hasSkyLight; private final BuiltInBiome defaultBiome; - Dimension(String key, int legacyID, int modernID, int maxSections, boolean hasLegacySkyLight, BuiltInBiome defaultBiome) { + Dimension(String key, int legacyId, int modernId, int maxSections, boolean hasSkyLight, BuiltInBiome defaultBiome) { this.key = key; - this.legacyID = legacyID; - this.modernID = modernID; + this.legacyId = legacyId; + this.modernId = modernId; this.maxSections = maxSections; - this.hasLegacySkyLight = hasLegacySkyLight; + this.hasSkyLight = hasSkyLight; this.defaultBiome = defaultBiome; } @@ -33,20 +35,35 @@ public String getKey() { return this.key; } + @Deprecated(forRemoval = true) public int getLegacyID() { - return this.legacyID; + return this.legacyId; + } + + public int getLegacyId() { + return this.legacyId; } + @Deprecated(forRemoval = true) public int getModernID() { - return this.modernID; + return this.modernId; + } + + public int getModernId() { + return this.modernId; } public int getMaxSections() { return this.maxSections; } + @Deprecated(forRemoval = true) public boolean hasLegacySkyLight() { - return this.hasLegacySkyLight; + return this.hasSkyLight; + } + + public boolean hasSkyLight() { + return this.hasSkyLight; } public BuiltInBiome getDefaultBiome() { diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/chunk/VirtualChunk.java b/api/src/main/java/net/elytrium/limboapi/api/world/chunk/VirtualChunk.java new file mode 100644 index 00000000..d6194823 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/chunk/VirtualChunk.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2021 - 2025 Elytrium + * + * The LimboAPI (excluding the LimboAPI plugin) is licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in the api top-level directory. + */ + +package net.elytrium.limboapi.api.world.chunk; + +import net.elytrium.limboapi.api.world.chunk.biome.VirtualBiome; +import net.elytrium.limboapi.api.world.chunk.block.VirtualBlock; +import net.elytrium.limboapi.api.world.chunk.blockentity.VirtualBlockEntity; +import net.elytrium.limboapi.api.world.chunk.data.BlockSection; +import net.elytrium.limboapi.api.world.chunk.data.LightSection; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.common.value.qual.IntRange; + +public interface VirtualChunk { + + void setBlock(@IntRange(from = 0, to = 15) int posX, int posY, @IntRange(from = 0, to = 15) int posZ, @Nullable VirtualBlock block); + + void setBlockEntity(int posX, int posY, int posZ, @Nullable CompoundBinaryTag nbt, @Nullable VirtualBlockEntity blockEntity); + + void setBlockEntity(VirtualBlockEntity.Entry blockEntityEntry); + + @NonNull + VirtualBlock getBlock(@IntRange(from = 0, to = 15) int posX, int posY, @IntRange(from = 0, to = 15) int posZ); + + void setBiome2D(@IntRange(from = 0, to = 15) int posX, @IntRange(from = 0, to = 15) int posZ, @NonNull VirtualBiome biome); + + void setBiome3D(@IntRange(from = 0, to = 15) int posX, int posY, @IntRange(from = 0, to = 15) int posZ, @NonNull VirtualBiome biome); + + @NonNull + VirtualBiome getBiome(@IntRange(from = 0, to = 15) int posX, int posY, @IntRange(from = 0, to = 15) int posZ); + + void setBlockLight(@IntRange(from = 0, to = 15) int posX, int posY, @IntRange(from = 0, to = 15) int posZ, @IntRange(from = 0, to = 15) byte light); + + byte getBlockLight(@IntRange(from = 0, to = 15) int posX, int posY, @IntRange(from = 0, to = 15) int posZ); + + void setSkyLight(@IntRange(from = 0, to = 15) int posX, int posY, @IntRange(from = 0, to = 15) int posZ, @IntRange(from = 0, to = 15) byte light); + + byte getSkyLight(@IntRange(from = 0, to = 15) int posX, int posY, @IntRange(from = 0, to = 15) int posZ); + + @Deprecated(forRemoval = true) + default void fillBlockLight(@IntRange(from = 0, to = 15) int level) { + this.fillBlockLight((byte) level); + } + + void fillBlockLight(@IntRange(from = 0, to = 15) byte level); + + @Deprecated(forRemoval = true) + default void fillSkyLight(@IntRange(from = 0, to = 15) int level) { + this.fillSkyLight((byte) level); + } + + void fillSkyLight(@IntRange(from = 0, to = 15) byte level); + + int getPosX(); + + int getPosZ(); + + @Deprecated(forRemoval = true) + default ChunkSnapshot getFullChunkSnapshot() { + return this.createSnapshot(true); + } + + @Deprecated(forRemoval = true) + default ChunkSnapshot getPartialChunkSnapshot(long previousUpdate) { + return this.createSnapshot(false); + } + + ChunkSnapshot createSnapshot(boolean fullChunk); + + BlockSection[] createBlockSectionSnapshot(); + + LightSection[] createLightSnapshot(); +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/chunk/BuiltInBiome.java b/api/src/main/java/net/elytrium/limboapi/api/world/chunk/biome/BuiltInBiome.java similarity index 85% rename from api/src/main/java/net/elytrium/limboapi/api/chunk/BuiltInBiome.java rename to api/src/main/java/net/elytrium/limboapi/api/world/chunk/biome/BuiltInBiome.java index 75b14451..fdbbf1fb 100644 --- a/api/src/main/java/net/elytrium/limboapi/api/chunk/BuiltInBiome.java +++ b/api/src/main/java/net/elytrium/limboapi/api/world/chunk/biome/BuiltInBiome.java @@ -5,7 +5,7 @@ * reference the LICENSE file in the api top-level directory. */ -package net.elytrium.limboapi.api.chunk; +package net.elytrium.limboapi.api.world.chunk.biome; public enum BuiltInBiome { diff --git a/api/src/main/java/net/elytrium/limboapi/api/chunk/VirtualBiome.java b/api/src/main/java/net/elytrium/limboapi/api/world/chunk/biome/VirtualBiome.java similarity index 64% rename from api/src/main/java/net/elytrium/limboapi/api/chunk/VirtualBiome.java rename to api/src/main/java/net/elytrium/limboapi/api/world/chunk/biome/VirtualBiome.java index 67179aa0..2e6a4cc7 100644 --- a/api/src/main/java/net/elytrium/limboapi/api/chunk/VirtualBiome.java +++ b/api/src/main/java/net/elytrium/limboapi/api/world/chunk/biome/VirtualBiome.java @@ -5,11 +5,16 @@ * reference the LICENSE file in the api top-level directory. */ -package net.elytrium.limboapi.api.chunk; +package net.elytrium.limboapi.api.world.chunk.biome; public interface VirtualBiome { String getName(); - int getID(); + @Deprecated(forRemoval = true) + default int getID() { + return this.getId(); + } + + int getId(); } diff --git a/api/src/main/java/net/elytrium/limboapi/api/material/Block.java b/api/src/main/java/net/elytrium/limboapi/api/world/chunk/block/Block.java similarity index 97% rename from api/src/main/java/net/elytrium/limboapi/api/material/Block.java rename to api/src/main/java/net/elytrium/limboapi/api/world/chunk/block/Block.java index 1c37d8eb..17383ed5 100644 --- a/api/src/main/java/net/elytrium/limboapi/api/material/Block.java +++ b/api/src/main/java/net/elytrium/limboapi/api/world/chunk/block/Block.java @@ -5,7 +5,7 @@ * reference the LICENSE file in the api top-level directory. */ -package net.elytrium.limboapi.api.material; +package net.elytrium.limboapi.api.world.chunk.block; public enum Block { @@ -270,7 +270,12 @@ public enum Block { this.id = id; } + @Deprecated(forRemoval = true) public int getID() { + return this.getId(); + } + + public int getId() { return this.id; } } diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/chunk/block/VirtualBlock.java b/api/src/main/java/net/elytrium/limboapi/api/world/chunk/block/VirtualBlock.java new file mode 100644 index 00000000..5dfb45e4 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/chunk/block/VirtualBlock.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2021 - 2025 Elytrium + * + * The LimboAPI (excluding the LimboAPI plugin) is licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in the api top-level directory. + */ + +package net.elytrium.limboapi.api.world.chunk.block; + +import com.velocitypowered.api.network.ProtocolVersion; +import net.elytrium.limboapi.api.world.WorldVersion; + +public interface VirtualBlock { + + @Deprecated(forRemoval = true) + default String getModernStringID() { + return this.modernId(); + } + + String modernId(); + + @Deprecated(forRemoval = true) + default short getModernID() { + return this.blockStateId(); + } + + /** + * @return Latest supported version block state id + */ + short blockStateId(); + + @Deprecated(forRemoval = true) + default short getID(ProtocolVersion version) { + return this.getBlockStateID(version); + } + + @Deprecated(forRemoval = true) + default short getBlockStateID(ProtocolVersion version) { + return this.blockStateId(version); + } + + short blockStateId(ProtocolVersion version); + + /** + * @return Latest supported version block id + */ + short blockId(); + + @Deprecated(forRemoval = true) + default short getBlockID(WorldVersion version) { + return this.blockId(version); + } + + short blockId(WorldVersion version); + + @Deprecated(forRemoval = true) + default short getBlockID(ProtocolVersion version) { + return this.blockId(version); + } + + short blockId(ProtocolVersion version); + + @Deprecated(forRemoval = true) + default boolean isSolid() { + return this.solid(); + } + + boolean solid(); + + @Deprecated(forRemoval = true) + default boolean isAir() { + return this.air(); + } + + boolean air(); + + @Deprecated(forRemoval = true) + default boolean isMotionBlocking() { + return this.motionBlocking(); + } + + boolean motionBlocking(); + + boolean isSupportedOn(ProtocolVersion version); + + boolean isSupportedOn(WorldVersion version); +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/chunk/blockentity/BlockEntityVersion.java b/api/src/main/java/net/elytrium/limboapi/api/world/chunk/blockentity/BlockEntityVersion.java new file mode 100644 index 00000000..e4ada4c7 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/chunk/blockentity/BlockEntityVersion.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2021 - 2025 Elytrium + * + * The LimboAPI (excluding the LimboAPI plugin) is licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in the api top-level directory. + */ + +package net.elytrium.limboapi.api.world.chunk.blockentity; + +import com.velocitypowered.api.network.ProtocolVersion; +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.Objects; + +public enum BlockEntityVersion { + + LEGACY(EnumSet.range(ProtocolVersion.MINECRAFT_1_7_2, ProtocolVersion.MINECRAFT_1_9_2)), + MINECRAFT_1_9(EnumSet.range(ProtocolVersion.MINECRAFT_1_9_4, ProtocolVersion.MINECRAFT_1_10)), + MINECRAFT_1_11(EnumSet.of(ProtocolVersion.MINECRAFT_1_11, ProtocolVersion.MINECRAFT_1_11_1)), + MINECRAFT_1_12(EnumSet.range(ProtocolVersion.MINECRAFT_1_12, ProtocolVersion.MINECRAFT_1_12_2)), + MINECRAFT_1_13(EnumSet.range(ProtocolVersion.MINECRAFT_1_13, ProtocolVersion.MINECRAFT_1_13_2)), + MINECRAFT_1_14(EnumSet.range(ProtocolVersion.MINECRAFT_1_14, ProtocolVersion.MINECRAFT_1_14_4)), + MINECRAFT_1_15(EnumSet.range(ProtocolVersion.MINECRAFT_1_15, ProtocolVersion.MINECRAFT_1_16_4)), + MINECRAFT_1_17(EnumSet.range(ProtocolVersion.MINECRAFT_1_17, ProtocolVersion.MINECRAFT_1_18_2)), + MINECRAFT_1_19(EnumSet.range(ProtocolVersion.MINECRAFT_1_19, ProtocolVersion.MINECRAFT_1_19_1)), + MINECRAFT_1_19_3(EnumSet.of(ProtocolVersion.MINECRAFT_1_19_3)), + MINECRAFT_1_19_4(EnumSet.of(ProtocolVersion.MINECRAFT_1_19_4)), + MINECRAFT_1_20(EnumSet.range(ProtocolVersion.MINECRAFT_1_20, ProtocolVersion.MINECRAFT_1_20_2)), + MINECRAFT_1_20_3(EnumSet.of(ProtocolVersion.MINECRAFT_1_20_3)), + MINECRAFT_1_20_5(EnumSet.range(ProtocolVersion.MINECRAFT_1_20_5, ProtocolVersion.MINECRAFT_1_21)), + MINECRAFT_1_21_2(EnumSet.range(ProtocolVersion.MINECRAFT_1_21_2, ProtocolVersion.MINECRAFT_1_21_4)), + MINECRAFT_1_21_5(EnumSet.range(ProtocolVersion.MINECRAFT_1_21_5, ProtocolVersion.MINECRAFT_1_21_7)), + MINECRAFT_1_21_9(EnumSet.range(ProtocolVersion.MINECRAFT_1_21_9, ProtocolVersion.MAXIMUM_VERSION)); + + private static final EnumMap VERSIONS_MAP = new EnumMap<>(ProtocolVersion.class); + + private final EnumSet versions; + + BlockEntityVersion(EnumSet versions) { + this.versions = versions; + } + + public EnumSet getVersions() { + return this.versions; + } + + static { + for (BlockEntityVersion version : BlockEntityVersion.values()) { + version.getVersions().forEach(protocolVersion -> BlockEntityVersion.VERSIONS_MAP.put(protocolVersion, version)); + } + } + + public static BlockEntityVersion from(ProtocolVersion protocolVersion) { + return Objects.requireNonNull(BlockEntityVersion.VERSIONS_MAP.get(protocolVersion)); + } +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/chunk/blockentity/VirtualBlockEntity.java b/api/src/main/java/net/elytrium/limboapi/api/world/chunk/blockentity/VirtualBlockEntity.java new file mode 100644 index 00000000..1a010898 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/chunk/blockentity/VirtualBlockEntity.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2021 - 2025 Elytrium + * + * The LimboAPI (excluding the LimboAPI plugin) is licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in the api top-level directory. + */ + +package net.elytrium.limboapi.api.world.chunk.blockentity; + +import com.velocitypowered.api.network.ProtocolVersion; +import net.elytrium.limboapi.api.world.chunk.VirtualChunk; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import org.checkerframework.checker.nullness.qual.Nullable; + +public interface VirtualBlockEntity { + + @Deprecated(forRemoval = true) + default String getModernID() { + return this.getModernId(); + } + + String getModernId(); + + /** + * @return Legacy id if present, modern otherwise (used in 1.9-1.10, e.g., DLDetector instead of daylight_detector) + */ + String getLegacyId(); + + @Deprecated(forRemoval = true) + default int getID(ProtocolVersion version) { + return this.getId(version); + } + + /** + * @return Protocol id, or {@link Integer#MIN_VALUE} if not exists + */ + int getId(ProtocolVersion version); + + @Deprecated(forRemoval = true) + default int getID(BlockEntityVersion version) { + return this.getId(version); + } + + /** + * @return Protocol id, or {@link Integer#MIN_VALUE} if not exists + */ + int getId(BlockEntityVersion version); + + boolean isSupportedOn(ProtocolVersion version); + + boolean isSupportedOn(BlockEntityVersion version); + + @Deprecated(forRemoval = true) + default Entry getEntry(int posX, int posY, int posZ, CompoundBinaryTag nbt) { + return this.createEntry(null, posX, posY, posZ, nbt); + } + + /** + * @param chunk Used only to get blockId for correct transformation some of <=1.12.2 block entities + */ + Entry createEntry(@Nullable VirtualChunk chunk, int posX, int posY, int posZ, @Nullable CompoundBinaryTag nbt); + + interface Entry { + + VirtualBlockEntity getBlockEntity(); + + @Deprecated(forRemoval = true) + default int getID(ProtocolVersion version) { + return this.getBlockEntity().getId(version); + } + + @Deprecated(forRemoval = true) + default int getID(BlockEntityVersion version) { + return this.getBlockEntity().getId(version); + } + + @Deprecated(forRemoval = true) + default boolean isSupportedOn(ProtocolVersion version) { + return this.getBlockEntity().isSupportedOn(version); + } + + @Deprecated(forRemoval = true) + default boolean isSupportedOn(BlockEntityVersion version) { + return this.getBlockEntity().isSupportedOn(version); + } + + int getPosX(); + + int getPosY(); + + int getPosZ(); + + @Nullable + CompoundBinaryTag getNbt(ProtocolVersion version); + } +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/chunk/data/BlockSection.java b/api/src/main/java/net/elytrium/limboapi/api/world/chunk/data/BlockSection.java new file mode 100644 index 00000000..6018280a --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/chunk/data/BlockSection.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2021 - 2025 Elytrium + * + * The LimboAPI (excluding the LimboAPI plugin) is licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in the api top-level directory. + */ + +package net.elytrium.limboapi.api.world.chunk.data; + +import net.elytrium.limboapi.api.world.chunk.block.VirtualBlock; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.common.value.qual.IntRange; + +public interface BlockSection { + + void setBlockAt(@IntRange(from = 0, to = 15) int posX, @IntRange(from = 0, to = 15) int posY, @IntRange(from = 0, to = 15) int posZ, @Nullable VirtualBlock block); + + VirtualBlock getBlockAt(@IntRange(from = 0, to = 15) int posX, @IntRange(from = 0, to = 15) int posY, @IntRange(from = 0, to = 15) int posZ); + + @Deprecated(forRemoval = true) + default BlockSection getSnapshot() { + return this.copy(); + } + + BlockSection copy(); + + @Deprecated(forRemoval = true) + default long getLastUpdate() { + return 0; + } +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/chunk/data/BlockStorage.java b/api/src/main/java/net/elytrium/limboapi/api/world/chunk/data/BlockStorage.java new file mode 100644 index 00000000..35bdc358 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/chunk/data/BlockStorage.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2021 - 2025 Elytrium + * + * The LimboAPI (excluding the LimboAPI plugin) is licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in the api top-level directory. + */ + +package net.elytrium.limboapi.api.world.chunk.data; + +import com.velocitypowered.api.network.ProtocolVersion; +import net.elytrium.limboapi.api.world.chunk.block.VirtualBlock; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.common.value.qual.IntRange; + +public interface BlockStorage { + + /** + * @param pass Used only in 1.7-1.8 storage + */ + void write(Object buf, ProtocolVersion version, int pass); + + void set(@IntRange(from = 0, to = 15) int posX, @IntRange(from = 0, to = 15) int posY, @IntRange(from = 0, to = 15) int posZ, @NonNull VirtualBlock block); + + @NonNull + VirtualBlock get(@IntRange(from = 0, to = 15) int posX, @IntRange(from = 0, to = 15) int posY, @IntRange(from = 0, to = 15) int posZ); + + int getDataLength(ProtocolVersion version); + + BlockStorage copy(); + + static int index(int posX, int posY, int posZ) { + return posY << 8 | posZ << 4 | posX; + } +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/chunk/data/LightSection.java b/api/src/main/java/net/elytrium/limboapi/api/world/chunk/data/LightSection.java new file mode 100644 index 00000000..de5545f0 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/chunk/data/LightSection.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2021 - 2025 Elytrium + * + * The LimboAPI (excluding the LimboAPI plugin) is licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in the api top-level directory. + */ + +package net.elytrium.limboapi.api.world.chunk.data; + +import net.elytrium.limboapi.api.world.chunk.util.NibbleArray3D; +import org.checkerframework.common.value.qual.IntRange; + +public interface LightSection { + + void setBlockLight(@IntRange(from = 0, to = 15) int posX, @IntRange(from = 0, to = 15) int posY, @IntRange(from = 0, to = 15) int posZ, @IntRange(from = 0, to = 15) byte light); + + NibbleArray3D getBlockLight(); + + byte getBlockLight(@IntRange(from = 0, to = 15) int posX, @IntRange(from = 0, to = 15) int posY, @IntRange(from = 0, to = 15) int posZ); + + void setSkyLight(@IntRange(from = 0, to = 15) int posX, @IntRange(from = 0, to = 15) int posY, @IntRange(from = 0, to = 15) int posZ, @IntRange(from = 0, to = 15) byte light); + + NibbleArray3D getSkyLight(); + + byte getSkyLight(@IntRange(from = 0, to = 15) int posX, @IntRange(from = 0, to = 15) int posY, @IntRange(from = 0, to = 15) int posZ); + + @Deprecated(forRemoval = true) + default long getLastUpdate() { + return 0; + } + + LightSection copy(); +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/chunk/util/CompactStorage.java b/api/src/main/java/net/elytrium/limboapi/api/world/chunk/util/CompactStorage.java similarity index 85% rename from api/src/main/java/net/elytrium/limboapi/api/chunk/util/CompactStorage.java rename to api/src/main/java/net/elytrium/limboapi/api/world/chunk/util/CompactStorage.java index 2d8d1b58..65b9456e 100644 --- a/api/src/main/java/net/elytrium/limboapi/api/chunk/util/CompactStorage.java +++ b/api/src/main/java/net/elytrium/limboapi/api/world/chunk/util/CompactStorage.java @@ -5,7 +5,7 @@ * reference the LICENSE file in the api top-level directory. */ -package net.elytrium.limboapi.api.chunk.util; +package net.elytrium.limboapi.api.world.chunk.util; import com.velocitypowered.api.network.ProtocolVersion; @@ -15,7 +15,7 @@ public interface CompactStorage { int get(int index); - void write(Object byteBufObject, ProtocolVersion version); + void write(Object buf, ProtocolVersion version); int getBitsPerEntry(); diff --git a/api/src/main/java/net/elytrium/limboapi/api/mcprotocollib/NibbleArray3D.java b/api/src/main/java/net/elytrium/limboapi/api/world/chunk/util/NibbleArray3D.java similarity index 67% rename from api/src/main/java/net/elytrium/limboapi/api/mcprotocollib/NibbleArray3D.java rename to api/src/main/java/net/elytrium/limboapi/api/world/chunk/util/NibbleArray3D.java index a804c4b2..2cbeb1e9 100644 --- a/api/src/main/java/net/elytrium/limboapi/api/mcprotocollib/NibbleArray3D.java +++ b/api/src/main/java/net/elytrium/limboapi/api/world/chunk/util/NibbleArray3D.java @@ -22,10 +22,11 @@ * OR OTHER DEALINGS IN THE SOFTWARE. */ -package net.elytrium.limboapi.api.mcprotocollib; +package net.elytrium.limboapi.api.world.chunk.util; import java.util.Arrays; -import net.elytrium.limboapi.api.chunk.data.BlockStorage; +import net.elytrium.limboapi.api.world.chunk.data.BlockStorage; +import org.checkerframework.common.value.qual.IntRange; public class NibbleArray3D { @@ -35,7 +36,7 @@ public NibbleArray3D(int size) { this.data = new byte[size >> 1]; } - public NibbleArray3D(int size, int defaultValue) { + public NibbleArray3D(int size, @IntRange(from = 0, to = 15) byte defaultValue) { this.data = new byte[size >> 1]; this.fill(defaultValue); } @@ -48,26 +49,25 @@ public byte[] getData() { return this.data; } - public int get(int posX, int posY, int posZ) { + public int get(@IntRange(from = 0, to = 15) int posX, @IntRange(from = 0, to = 15) int posY, @IntRange(from = 0, to = 15) int posZ) { int key = BlockStorage.index(posX, posY, posZ); int index = key >> 1; - return (key & 1) == 0 ? this.data[index] & 15 : this.data[index] >> 4 & 15; + return (key & 1) == 0 ? this.data[index] & 0x0F : this.data[index] >> 4 & 0x0F; } - public void set(int posX, int posY, int posZ, int value) { + public void set(@IntRange(from = 0, to = 15) int posX, @IntRange(from = 0, to = 15) int posY, @IntRange(from = 0, to = 15) int posZ, @IntRange(from = 0, to = 15) byte value) { this.set(BlockStorage.index(posX, posY, posZ), value); } - public void set(int key, int val) { + public void set(int key, @IntRange(from = 0, to = 15) byte value) { int index = key >> 1; - if ((key & 1) == 0) { - this.data[index] = (byte) (this.data[index] & 240 | val & 15); - } else { - this.data[index] = (byte) (this.data[index] & 15 | (val & 15) << 4); - } + this.data[index] = (byte) ((key & 1) == 0 + ? this.data[index] & 0xF0 | value & 0x0F + : this.data[index] & 0x0F | (value & 0x0F) << 4 + ); } - public void fill(int value) { + public void fill(@IntRange(from = 0, to = 15) byte value) { for (int index = 0; index < this.data.length << 1; ++index) { this.set(index, value); } diff --git a/api/src/main/java/net/elytrium/limboapi/api/material/Item.java b/api/src/main/java/net/elytrium/limboapi/api/world/item/Item.java similarity index 96% rename from api/src/main/java/net/elytrium/limboapi/api/material/Item.java rename to api/src/main/java/net/elytrium/limboapi/api/world/item/Item.java index 7521d782..2a98eb47 100644 --- a/api/src/main/java/net/elytrium/limboapi/api/material/Item.java +++ b/api/src/main/java/net/elytrium/limboapi/api/world/item/Item.java @@ -5,7 +5,7 @@ * reference the LICENSE file in the api top-level directory. */ -package net.elytrium.limboapi.api.material; +package net.elytrium.limboapi.api.world.item; public enum Item { @@ -346,18 +346,23 @@ public enum Item { RECORD_11(2266), RECORD_WAIT(2267); - private final int id; + private final short id; Item(int id) { - this.id = id; + this.id = (short) id; } - @Deprecated + @Deprecated(forRemoval = true) public int getID() { return this.id; } + @Deprecated(forRemoval = true) public int getLegacyID() { return this.id; } + + public short getLegacyId() { + return this.id; + } } diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/item/VirtualItem.java b/api/src/main/java/net/elytrium/limboapi/api/world/item/VirtualItem.java new file mode 100644 index 00000000..ac0ff7db --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/item/VirtualItem.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2021 - 2025 Elytrium + * + * The LimboAPI (excluding the LimboAPI plugin) is licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in the api top-level directory. + */ + +package net.elytrium.limboapi.api.world.item; + +import com.velocitypowered.api.network.ProtocolVersion; +import net.elytrium.limboapi.api.world.WorldVersion; + +public interface VirtualItem { + + @Deprecated(forRemoval = true) + default String getModernID() { + return this.modernId(); + } + + String modernId(); + + @Deprecated(forRemoval = true) + default short getID(WorldVersion version) { + return this.itemId(version); + } + + short itemId(WorldVersion version); + + @Deprecated(forRemoval = true) + default short getID(ProtocolVersion version) { + return this.itemId(version); + } + + short itemId(ProtocolVersion version); + + boolean isSupportedOn(WorldVersion version); + + boolean isSupportedOn(ProtocolVersion version); +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/DataComponentMap.java b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/DataComponentMap.java new file mode 100644 index 00000000..354b4490 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/DataComponentMap.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2021 - 2024 Elytrium + * + * The LimboAPI (excluding the LimboAPI plugin) is licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in the api top-level directory. + */ + +package net.elytrium.limboapi.api.world.item.datacomponent; + +import com.velocitypowered.api.network.ProtocolVersion; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +/** + * @sinceMinecraft 1.20.5 + */ +@ApiStatus.NonExtendable +public interface DataComponentMap { + + void setData(DataComponentType.NonValued type); + + void setData(DataComponentType.Valued type, @NonNull T value); + + /** + * Marks this data component as removed for this map + */ + void unsetData(DataComponentType type); + + /** + * Resets the value of this component to be the default value for the item type + */ + void resetData(DataComponentType type); + + /** + * @return the value for the data component type, or {@code null} if not set or marked as removed + */ + @Nullable + T getData(DataComponentType.Valued type); + + default T getDataOrDefault(DataComponentType.Valued type, T fallback) { + T value = this.getData(type); + return value == null ? fallback : value; + } + + boolean hasData(DataComponentType type); + + DataComponentMap read(Object buf, ProtocolVersion version); + + DataComponentMap write(Object buf, ProtocolVersion version); +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/DataComponentType.java b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/DataComponentType.java new file mode 100644 index 00000000..c38a7da0 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/DataComponentType.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2021 - 2024 Elytrium + * + * The LimboAPI (excluding the LimboAPI plugin) is licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in the api top-level directory. + */ + +package net.elytrium.limboapi.api.world.item.datacomponent; + +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; + +/** + * @sinceMinecraft 1.20.5 + */ +@NullMarked +@ApiStatus.Experimental +@ApiStatus.NonExtendable +public interface DataComponentType { + + @SuppressWarnings("unused") + @ApiStatus.NonExtendable + interface Valued extends DataComponentType { + + } + + @ApiStatus.NonExtendable + interface NonValued extends DataComponentType { + + } +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/DataComponentTypes.java b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/DataComponentTypes.java new file mode 100644 index 00000000..b9229dc3 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/DataComponentTypes.java @@ -0,0 +1,321 @@ +package net.elytrium.limboapi.api.world.item.datacomponent; + +import java.util.Collection; +import java.util.Map; +import net.elytrium.limboapi.api.protocol.data.ComponentHolder; +import net.elytrium.limboapi.api.protocol.data.ItemStack; +import net.elytrium.limboapi.api.world.item.datacomponent.type.AdventureModePredicate; +import net.elytrium.limboapi.api.world.item.datacomponent.type.ArmorTrim; +import net.elytrium.limboapi.api.world.item.datacomponent.type.AttributeModifiers; +import net.elytrium.limboapi.api.world.item.datacomponent.type.BannerPatternLayer; +import net.elytrium.limboapi.api.world.item.datacomponent.type.BeehiveOccupant; +import net.elytrium.limboapi.api.world.item.datacomponent.type.BlocksAttacks; +import net.elytrium.limboapi.api.world.item.datacomponent.type.Consumable; +import net.elytrium.limboapi.api.world.item.datacomponent.type.CustomModelData; +import net.elytrium.limboapi.api.world.item.datacomponent.type.DyedColor; +import net.elytrium.limboapi.api.world.item.datacomponent.type.Enchantments; +import net.elytrium.limboapi.api.world.item.datacomponent.type.Equippable; +import net.elytrium.limboapi.api.world.item.datacomponent.type.FireworkExplosion; +import net.elytrium.limboapi.api.world.item.datacomponent.type.Fireworks; +import net.elytrium.limboapi.api.world.item.datacomponent.type.FoodProperties; +import net.elytrium.limboapi.api.world.item.datacomponent.type.Instrument; +import net.elytrium.limboapi.api.world.item.datacomponent.type.JukeboxPlayable; +import net.elytrium.limboapi.api.world.item.datacomponent.type.LodestoneTracker; +import net.elytrium.limboapi.api.world.item.datacomponent.type.PaintingVariant; +import net.elytrium.limboapi.api.world.item.datacomponent.type.PotionContents; +import net.elytrium.limboapi.api.world.item.datacomponent.type.ResolvableProfile; +import net.elytrium.limboapi.api.world.item.datacomponent.type.SuspiciousStewEffect; +import net.elytrium.limboapi.api.world.item.datacomponent.type.Tool; +import net.elytrium.limboapi.api.world.item.datacomponent.type.TooltipDisplay; +import net.elytrium.limboapi.api.world.item.datacomponent.type.Unbreakable; +import net.elytrium.limboapi.api.world.item.datacomponent.type.UseCooldown; +import net.elytrium.limboapi.api.world.item.datacomponent.type.Weapon; +import net.elytrium.limboapi.api.world.item.datacomponent.type.WrittenBookContent; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.ConsumeEffect; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.Either; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.Filterable; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.Holder; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.HolderSet; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.SoundEvent; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.TrimMaterial; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.TypedEntityData; +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.nbt.ListBinaryTag; +import net.kyori.adventure.nbt.StringBinaryTag; +import org.checkerframework.checker.index.qual.NonNegative; +import org.checkerframework.checker.index.qual.Positive; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.common.value.qual.IntRange; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; + +/** + * @sinceMinecraft 1.20.5 + */ +@NullMarked +public final class DataComponentTypes { + + public static final DataComponentType.Valued CUSTOM_DATA = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued<@IntRange(from = 1, to = 99) Integer> MAX_STACK_SIZE = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued<@Positive Integer> MAX_DAMAGE = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued<@NonNegative Integer> DAMAGE = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued UNBREAKABLE = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued CUSTOM_NAME = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued ITEM_NAME = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21.2 + */ + public static final DataComponentType.Valued ITEM_MODEL = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued> LORE = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued RARITY = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued ENCHANTMENTS = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued CAN_PLACE_ON = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued CAN_BREAK = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued ATTRIBUTE_MODIFIERS = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued CUSTOM_MODEL_DATA = DataComponentTypes.uninitialized(); + /** + * Removed in version 1.21.5 + */ + @ApiStatus.Obsolete + public static final DataComponentType.NonValued HIDE_ADDITIONAL_TOOLTIP = DataComponentTypes.uninitialized(); + /** + * Removed in version 1.21.5 + */ + @ApiStatus.Obsolete + public static final DataComponentType.NonValued HIDE_TOOLTIP = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21.5 + */ + public static final DataComponentType.Valued TOOLTIP_DISPLAY = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued<@NonNegative Integer> REPAIR_COST = DataComponentTypes.uninitialized(); + public static final DataComponentType.NonValued CREATIVE_SLOT_LOCK = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued ENCHANTMENT_GLINT_OVERRIDE = DataComponentTypes.uninitialized(); + /** + * @apiNote Does not actually hold any data and should be {@code NonValued}, + * but the client expects an {@link CompoundBinaryTag#empty() empty compound} instead + */ + public static final DataComponentType.Valued INTANGIBLE_PROJECTILE = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued FOOD = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21.2 + */ + public static final DataComponentType.Valued CONSUMABLE = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21.2 + */ + public static final DataComponentType.Valued USE_REMAINDER = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21.2 + */ + public static final DataComponentType.Valued USE_COOLDOWN = DataComponentTypes.uninitialized(); + /** + * Removed in version 1.21.2 + */ + @ApiStatus.Obsolete + public static final DataComponentType.NonValued FIRE_RESISTANT = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21.2 + */ + public static final DataComponentType.Valued DAMAGE_RESISTANT = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued TOOL = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21.5 + */ + public static final DataComponentType.Valued WEAPON = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21.2 + */ + public static final DataComponentType.Valued<@Positive Integer> ENCHANTABLE = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21.2 + */ + public static final DataComponentType.Valued EQUIPPABLE = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21.2 + */ + public static final DataComponentType.Valued REPAIRABLE = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21.2 + */ + public static final DataComponentType.NonValued GLIDER = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21.2 + */ + public static final DataComponentType.Valued TOOLTIP_STYLE = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21.2 + */ + public static final DataComponentType.Valued> DEATH_PROTECTION = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21.5 + */ + public static final DataComponentType.Valued BLOCKS_ATTACKS = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued STORED_ENCHANTMENTS = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued DYED_COLOR = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued MAP_COLOR = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued MAP_ID = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued MAP_DECORATIONS = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued MAP_POST_PROCESSING = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued> CHARGED_PROJECTILES = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued> BUNDLE_CONTENTS = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued POTION_CONTENTS = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21.5 + */ + public static final DataComponentType.Valued POTION_DURATION_SCALE = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued> SUSPICIOUS_STEW_EFFECTS = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued>> WRITABLE_BOOK_CONTENT = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued WRITTEN_BOOK_CONTENT = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued TRIM = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued DEBUG_STICK_STATE = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued ENTITY_DATA = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued BUCKET_ENTITY_DATA = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued BLOCK_ENTITY_DATA = DataComponentTypes.uninitialized(); + /** + * @apiNote For versions prior to 1.21.5, only {@code Holder} is expected. + * The plugin converts known string values to {@code Holder.Reference}, + * while unknown strings are replaced with an ID of {@code 0}. + *
+ * Since version 1.21.5, either {@code Holder} or {@code String} can be provided. + */ + public static final DataComponentType.Valued, String>> INSTRUMENT = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21.5 + */ + public static final DataComponentType.Valued, String>> PROVIDES_TRIM_MATERIAL = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued<@IntRange(from = 0, to = 4) Integer> OMINOUS_BOTTLE_AMPLIFIER = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21 + */ + public static final DataComponentType.Valued JUKEBOX_PLAYABLE = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21.5 + */ + public static final DataComponentType.Valued PROVIDES_BANNER_PATTERNS = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued*/> RECIPES = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued LODESTONE_TRACKER = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued FIREWORK_EXPLOSION = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued FIREWORKS = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued PROFILE = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued NOTE_BLOCK_SOUND = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued> BANNER_PATTERNS = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued BASE_COLOR = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued> POT_DECORATIONS = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued> CONTAINER = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued> BLOCK_STATE = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued> BEES = DataComponentTypes.uninitialized(); + /** + * @apiNote For versions prior to 1.21.2, {@link StringBinaryTag} is expected. + * Since version 1.21.2, {@link CompoundBinaryTag} is used instead + */ + public static final DataComponentType.Valued LOCK = DataComponentTypes.uninitialized(); + public static final DataComponentType.Valued CONTAINER_LOOT = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21.5 + */ + public static final DataComponentType.Valued> BREAK_SOUND = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21.5 + */ + public static final DataComponentType.Valued VILLAGER_VARIANT = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21.5 + */ + public static final DataComponentType.Valued WOLF_VARIANT = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21.5 + */ + public static final DataComponentType.Valued WOLF_SOUND_VARIANT = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21.5 + */ + public static final DataComponentType.Valued WOLF_COLLAR = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21.5 + */ + public static final DataComponentType.Valued FOX_VARIANT = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21.5 + */ + public static final DataComponentType.Valued SALMON_SIZE = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21.5 + */ + public static final DataComponentType.Valued PARROT_VARIANT = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21.5 + */ + public static final DataComponentType.Valued TROPICAL_FISH_PATTERN = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21.5 + */ + public static final DataComponentType.Valued TROPICAL_FISH_BASE_COLOR = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21.5 + */ + public static final DataComponentType.Valued TROPICAL_FISH_PATTERN_COLOR = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21.5 + */ + public static final DataComponentType.Valued MOOSHROOM_VARIANT = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21.5 + */ + public static final DataComponentType.Valued RABBIT_VARIANT = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21.5 + */ + public static final DataComponentType.Valued PIG_VARIANT = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21.5 + */ + public static final DataComponentType.Valued COW_VARIANT = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21.5 + */ + // Either a registry id or a registry key (for no reason) + public static final DataComponentType.Valued> CHICKEN_VARIANT = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21.5 + */ + public static final DataComponentType.Valued FROG_VARIANT = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21.5 + */ + public static final DataComponentType.Valued HORSE_VARIANT = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21.5 + */ + public static final DataComponentType.Valued> PAINTING_VARIANT = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21.5 + */ + public static final DataComponentType.Valued LLAMA_VARIANT = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21.5 + */ + public static final DataComponentType.Valued AXOLOTL_VARIANT = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21.5 + */ + public static final DataComponentType.Valued CAT_VARIANT = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21.5 + */ + public static final DataComponentType.Valued CAT_COLLAR = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21.5 + */ + public static final DataComponentType.Valued SHEEP_COLOR = DataComponentTypes.uninitialized(); + /** + * @sinceMinecraft 1.21.5 + */ + public static final DataComponentType.Valued SHULKER_COLOR = DataComponentTypes.uninitialized(); + + @NonNull + @SuppressWarnings("DataFlowIssue") + private static T uninitialized() { + return null; // The value will be initialized later by LimboAPI plugin using reflection + } +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/AdventureModePredicate.java b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/AdventureModePredicate.java new file mode 100644 index 00000000..b5499780 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/AdventureModePredicate.java @@ -0,0 +1,68 @@ +package net.elytrium.limboapi.api.world.item.datacomponent.type; + +import java.util.Collection; +import java.util.Collections; +import net.elytrium.limboapi.api.world.item.datacomponent.DataComponentType; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.HolderSet; +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.NullMarked; + +/** + * @param showInTooltip Removed in version 1.21.5 + */ +@NullMarked +public record AdventureModePredicate(Collection predicates, boolean showInTooltip) { + + public AdventureModePredicate(Collection predicates) { + this(predicates, true); + } + + /** + * @param components Added in version 1.21.5 + */ + public record BlockPredicate(@Nullable HolderSet blocks, @Nullable Collection properties, @Nullable CompoundBinaryTag nbt, DataComponentMatchers components) { + + public BlockPredicate(@Nullable HolderSet blocks, @Nullable Collection properties, @Nullable CompoundBinaryTag nbt) { + this(blocks, properties, nbt, DataComponentMatchers.ANY); + } + } + + public record PropertyMatcher(String name, ValueMatcher valueMatcher) { + + } + + public sealed interface ValueMatcher permits ValueMatcher.Exact, ValueMatcher.Ranged { + + record Exact(String value) implements ValueMatcher { + + } + + record Ranged(@Nullable String minValue, @Nullable String maxValue) implements ValueMatcher { + + } + } + + public record DataComponentMatchers(Collection exact, Collection partial) { + + public static final DataComponentMatchers ANY = new DataComponentMatchers(Collections.emptyList(), Collections.emptyList()); + } + + public sealed interface DataComponentExactPredicate permits DataComponentExactPredicate.Valued, DataComponentExactPredicate.NonValued { // TODO проверить как сделают в пейпере когда они это реализуют + + DataComponentType type(); + + record Valued(DataComponentType.Valued type, T value) implements DataComponentExactPredicate { + + } + + record NonValued(DataComponentType.NonValued type) implements DataComponentExactPredicate { + + } + } + + public record DataComponentPartialPredicate(int id, BinaryTag predicate) { + + } +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/ArmorTrim.java b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/ArmorTrim.java new file mode 100644 index 00000000..98558f44 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/ArmorTrim.java @@ -0,0 +1,17 @@ +package net.elytrium.limboapi.api.world.item.datacomponent.type; + +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.TrimMaterial; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.TrimPattern; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.Holder; +import org.jspecify.annotations.NullMarked; + +/** + * @param showInTooltip Removed in version 1.21.5 + */ +@NullMarked +public record ArmorTrim(Holder material, Holder pattern, boolean showInTooltip) { + + public ArmorTrim(Holder material, Holder pattern) { + this(material, pattern, true); + } +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/AttributeModifiers.java b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/AttributeModifiers.java new file mode 100644 index 00000000..7c515235 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/AttributeModifiers.java @@ -0,0 +1,51 @@ +package net.elytrium.limboapi.api.world.item.datacomponent.type; + +import java.util.Collection; +import java.util.UUID; +import net.elytrium.limboapi.api.protocol.data.ComponentHolder; +import org.jspecify.annotations.NullMarked; + +/** + * @param showInTooltip Removed in version 1.21.5 + */ +@NullMarked +public record AttributeModifiers(Collection modifiers, boolean showInTooltip) { + + public AttributeModifiers(Collection modifiers) { + this(modifiers, true); + } + + /** + * @param display Added in version 1.21.6 + */ + public record Entry(int attribute, AttributeModifier modifier, int/*enum EquipmentSlotGroup*/ slot, Display display) { + + public Entry(int attribute, AttributeModifier modifier, int slot) { + this(attribute, modifier, slot, Display.Default.INSTANCE); + } + } + + /** + * @param uuid Removed in version 1.21 + */ + public record AttributeModifier(UUID uuid, String name, double amount, int/*enum AttributeModifier.Operation*/ operation) { + + } + + public sealed interface Display permits Display.Default, Display.Hidden, Display.OverrideText { + + record Default() implements Display { + + public static final Default INSTANCE = new Default(); + } + + record Hidden() implements Display { + + public static final Hidden INSTANCE = new Hidden(); + } + + record OverrideText(ComponentHolder component) implements Display { + + } + } +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/BannerPatternLayer.java b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/BannerPatternLayer.java new file mode 100644 index 00000000..f69e4f9f --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/BannerPatternLayer.java @@ -0,0 +1,12 @@ +package net.elytrium.limboapi.api.world.item.datacomponent.type; + +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.Holder; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public record BannerPatternLayer(Holder pattern, int/*enum DyeColor*/ color) { + + public record BannerPattern(String assetId, String translationKey) implements Holder.Direct { + + } +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/BeehiveOccupant.java b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/BeehiveOccupant.java new file mode 100644 index 00000000..5e48ff6b --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/BeehiveOccupant.java @@ -0,0 +1,13 @@ +package net.elytrium.limboapi.api.world.item.datacomponent.type; + +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.TypedEntityData; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public record BeehiveOccupant(TypedEntityData entityData, int ticksInHive, int minTicksInHive) { + + public BeehiveOccupant(CompoundBinaryTag tag, int ticksInHive, int minTicksInHive) { + this(new TypedEntityData(11/*bee entity id as of 1.21.9*/, tag), ticksInHive, minTicksInHive); + } +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/BlocksAttacks.java b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/BlocksAttacks.java new file mode 100644 index 00000000..d0cd880f --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/BlocksAttacks.java @@ -0,0 +1,21 @@ +package net.elytrium.limboapi.api.world.item.datacomponent.type; + +import java.util.Collection; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.Holder; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.HolderSet; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.SoundEvent; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public record BlocksAttacks(float blockDelaySeconds, float disableCooldownScale, Collection damageReductions, BlocksAttacks.ItemDamageFunction itemDamage, + @Nullable String bypassedBy, @Nullable Holder blockSound, @Nullable Holder disableSound) { + + public record DamageReduction(float horizontalBlockingAngle, @Nullable HolderSet type, float base, float factor) { + + } + + public record ItemDamageFunction(float threshold, float base, float factor) { + + } +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/Consumable.java b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/Consumable.java new file mode 100644 index 00000000..8994bdfe --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/Consumable.java @@ -0,0 +1,13 @@ +package net.elytrium.limboapi.api.world.item.datacomponent.type; + +import java.util.Collection; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.ConsumeEffect; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.Holder; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.SoundEvent; +import org.checkerframework.checker.index.qual.NonNegative; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public record Consumable(@NonNegative float consumeSeconds, int/*enum ItemUseAnimation*/ animation, Holder sound, boolean hasConsumeParticles, Collection onConsumeEffects) { + +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/CustomModelData.java b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/CustomModelData.java new file mode 100644 index 00000000..b03915d3 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/CustomModelData.java @@ -0,0 +1,20 @@ +package net.elytrium.limboapi.api.world.item.datacomponent.type; + +import java.util.Collections; +import java.util.List; +import org.jspecify.annotations.NullMarked; + +/** + * @param floats For versions prior to 1.21.4, only a single {@code Integer} is expected. + * Since version 1.21.4, any number of {@code Float} values can be provided + * @param flags Added in version 1.21.4 + * @param strings Added in version 1.21.4 + * @param colors Added in version 1.21.4 + */ +@NullMarked +public record CustomModelData(List floats, List flags, List strings, List colors) { + + public CustomModelData(int value) { + this(Collections.singletonList(value), Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); + } +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/DyedColor.java b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/DyedColor.java new file mode 100644 index 00000000..cc18fb02 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/DyedColor.java @@ -0,0 +1,14 @@ +package net.elytrium.limboapi.api.world.item.datacomponent.type; + +import org.jspecify.annotations.NullMarked; + +/** + * @param showInTooltip Removed in version 1.21.5 + */ +@NullMarked +public record DyedColor(int rgb, boolean showInTooltip) { + + public DyedColor(int rgb) { + this(rgb, true); + } +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/Enchantments.java b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/Enchantments.java new file mode 100644 index 00000000..4f244f15 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/Enchantments.java @@ -0,0 +1,16 @@ +package net.elytrium.limboapi.api.world.item.datacomponent.type; + +import java.util.Map; +import org.checkerframework.common.value.qual.IntRange; +import org.jspecify.annotations.NullMarked; + +/** + * @param showInTooltip Removed in version 1.21.5 + */ +@NullMarked +public record Enchantments(Map enchantments, boolean showInTooltip) { + + public Enchantments(Map enchantments) { + this(enchantments, true); + } +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/Equippable.java b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/Equippable.java new file mode 100644 index 00000000..e89528e4 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/Equippable.java @@ -0,0 +1,27 @@ +package net.elytrium.limboapi.api.world.item.datacomponent.type; + +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.Holder; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.HolderSet; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.SoundEvent; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.NullMarked; + +/** + * @param equipOnInteract Added in version 1.21.5 + * @param canBeSheared Added in version 1.21.6 + * @param shearingSound Added in version 1.21.6 + */ +@NullMarked +public record Equippable(int/*enum EquipmentSlot*/ slot, Holder equipSound, @Nullable String assetId, @Nullable String cameraOverlay, @Nullable HolderSet allowedEntities, + boolean dispensable, boolean swappable, boolean damageOnHurt, boolean equipOnInteract, boolean canBeSheared, Holder shearingSound) { + + public Equippable(int/*enum EquipmentSlot*/ slot, Holder equipSound, @Nullable String assetId, @Nullable String cameraOverlay, @Nullable HolderSet allowedEntities, + boolean dispensable, boolean swappable, boolean damageOnHurt) { + this(slot, equipSound, assetId, cameraOverlay, allowedEntities, dispensable, swappable, damageOnHurt, true); + } + + public Equippable(int/*enum EquipmentSlot*/ slot, Holder equipSound, @Nullable String assetId, @Nullable String cameraOverlay, @Nullable HolderSet allowedEntities, + boolean dispensable, boolean swappable, boolean damageOnHurt, boolean equipOnInteract) { + this(slot, equipSound, assetId, cameraOverlay, allowedEntities, dispensable, swappable, damageOnHurt, equipOnInteract, false, Holder.ref(0)); + } +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/FireworkExplosion.java b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/FireworkExplosion.java new file mode 100644 index 00000000..c47bad83 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/FireworkExplosion.java @@ -0,0 +1,9 @@ +package net.elytrium.limboapi.api.world.item.datacomponent.type; + +import java.util.Collection; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public record FireworkExplosion(int/*enum FireworkExplosion.Shape*/ shape, Collection colors, Collection fadeColors, boolean hasTrail, boolean hasTwinkle) { + +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/Fireworks.java b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/Fireworks.java new file mode 100644 index 00000000..105c8f1c --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/Fireworks.java @@ -0,0 +1,10 @@ +package net.elytrium.limboapi.api.world.item.datacomponent.type; + +import java.util.Collection; +import org.checkerframework.common.value.qual.IntRange; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public record Fireworks(@IntRange(from = 0, to = 255) int flightDuration, Collection explosions) { + +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/FoodProperties.java b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/FoodProperties.java new file mode 100644 index 00000000..73565824 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/FoodProperties.java @@ -0,0 +1,30 @@ +package net.elytrium.limboapi.api.world.item.datacomponent.type; + +import java.util.Collection; +import java.util.Collections; +import net.elytrium.limboapi.api.protocol.data.ItemStack; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.MobEffect; +import org.checkerframework.checker.index.qual.NonNegative; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.NullMarked; + +/** + * @param eatSeconds Removed in version 1.21.2 + * @param usingConvertsTo Added in version 1.21; removed in version 1.21.2 + * @param effects Removed in version 1.21.2 + */ +@NullMarked +public record FoodProperties(@NonNegative int nutrition, float saturation, boolean canAlwaysEat, @NonNegative float eatSeconds, @Nullable ItemStack usingConvertsTo, Collection effects) { + + public FoodProperties(@NonNegative int nutrition, float saturation, boolean canAlwaysEat, @NonNegative float eatSeconds, Collection effects) { + this(nutrition, saturation, canAlwaysEat, eatSeconds, null, effects); + } + + public FoodProperties(@NonNegative int nutrition, float saturation, boolean canAlwaysEat) { + this(nutrition, saturation, canAlwaysEat, 1.6F, null, Collections.emptyList()); + } + + public record PossibleEffect(MobEffect effect, float probability) { + + } +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/Instrument.java b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/Instrument.java new file mode 100644 index 00000000..e5a1be66 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/Instrument.java @@ -0,0 +1,20 @@ +package net.elytrium.limboapi.api.world.item.datacomponent.type; + +import net.elytrium.limboapi.api.protocol.data.ComponentHolder; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.Holder; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.SoundEvent; +import net.kyori.adventure.text.Component; +import org.jspecify.annotations.NullMarked; + +/** + * @param useDuration For versions prior to 1.21.2, an {@code int} value representing ticks is expected. + * Since version 1.21.2, a {@code float} value representing seconds is expected + * @param description Added in version 1.21.2 + */ +@NullMarked +public record Instrument(Holder soundEvent, Number useDuration, float range, ComponentHolder description) implements Holder.Direct { + + public Instrument(Holder soundEvent, int useDuration, float range) { + this(soundEvent, useDuration / 20.0F, range, new ComponentHolder(Component.empty())); + } +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/JukeboxPlayable.java b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/JukeboxPlayable.java new file mode 100644 index 00000000..78d1ec8f --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/JukeboxPlayable.java @@ -0,0 +1,22 @@ +package net.elytrium.limboapi.api.world.item.datacomponent.type; + +import net.elytrium.limboapi.api.protocol.data.ComponentHolder; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.Either; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.Holder; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.SoundEvent; +import org.jspecify.annotations.NullMarked; + +/** + * @param showInTooltip Removed in version 1.21.5 + */ +@NullMarked +public record JukeboxPlayable(Either, String> song, boolean showInTooltip) { + + public JukeboxPlayable(Either, String> song) { + this(song, true); + } + + public record JukeboxSong(Holder soundEvent, ComponentHolder description, float lengthInSeconds, int comparatorOutput) implements Holder.Direct { + + } +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/LodestoneTracker.java b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/LodestoneTracker.java new file mode 100644 index 00000000..01b36e3e --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/LodestoneTracker.java @@ -0,0 +1,10 @@ +package net.elytrium.limboapi.api.world.item.datacomponent.type; + +import net.elytrium.limboapi.api.protocol.data.GlobalPos; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public record LodestoneTracker(@Nullable GlobalPos target, boolean tracked) { + +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/PaintingVariant.java b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/PaintingVariant.java new file mode 100644 index 00000000..8e60db30 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/PaintingVariant.java @@ -0,0 +1,20 @@ +package net.elytrium.limboapi.api.world.item.datacomponent.type; + +import net.elytrium.limboapi.api.protocol.data.ComponentHolder; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.Holder; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.common.value.qual.IntRange; +import org.jspecify.annotations.NullMarked; + +/** + * @param title Added in version 1.21.2 + * @param author Added in version 1.21.2 + */ +@NullMarked +public record PaintingVariant(@IntRange(from = 1, to = 16) int width, @IntRange(from = 1, to = 16) int height, String assetId, + @Nullable ComponentHolder title, @Nullable ComponentHolder author) implements Holder.Direct { + + public PaintingVariant(@IntRange(from = 1, to = 16) int width, @IntRange(from = 1, to = 16) int height, String assetId) { + this(width, height, assetId, null, null); + } +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/PotionContents.java b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/PotionContents.java new file mode 100644 index 00000000..be428963 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/PotionContents.java @@ -0,0 +1,17 @@ +package net.elytrium.limboapi.api.world.item.datacomponent.type; + +import java.util.Collection; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.MobEffect; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.NullMarked; + +/** + * @param customName Added in version 1.21.2 + */ +@NullMarked +public record PotionContents(@Nullable Integer potion, @Nullable Integer customColor, Collection customEffects, @Nullable String customName) { + + public PotionContents(@Nullable Integer potion, @Nullable Integer customColor, Collection customEffects) { + this(potion, customColor, customEffects, null); + } +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/ResolvableProfile.java b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/ResolvableProfile.java new file mode 100644 index 00000000..0fad28cd --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/ResolvableProfile.java @@ -0,0 +1,52 @@ +package net.elytrium.limboapi.api.world.item.datacomponent.type; + +import java.util.Collection; +import java.util.UUID; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.NullMarked; + +/** + * @param skinPatch Added in version 1.21.9 + */ +@NullMarked +public record ResolvableProfile(GameProfile gameProfile, PlayerSkinPatch skinPatch) { + + public ResolvableProfile(GameProfile gameProfile) { + this(gameProfile, PlayerSkinPatch.EMPTY); + } + + public sealed interface GameProfile permits ResolvedGameProfile, PartialGameProfile { + + UUID id(); + + String name(); + + Collection properties(); + } + + /** + * @sinceMinecraft 1.21.9 + */ + public record ResolvedGameProfile(UUID id, String name, Collection properties) implements GameProfile { + + } + + public record PartialGameProfile(@Nullable String name, @Nullable UUID id, Collection properties) implements GameProfile { + + } + + public record Property(String name, String value, @Nullable String signature) { + + public Property(String name, String value) { + this(name, value, null); + } + } + + /** + * @param model {@code true} if the model is slim; {@code false} if the model is wide. + */ + public record PlayerSkinPatch(@Nullable String body, @Nullable String cape, @Nullable String elytra, @Nullable Boolean model) { + + public static final PlayerSkinPatch EMPTY = new PlayerSkinPatch(null, null, null, null); + } +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/SuspiciousStewEffect.java b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/SuspiciousStewEffect.java new file mode 100644 index 00000000..a650191a --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/SuspiciousStewEffect.java @@ -0,0 +1,8 @@ +package net.elytrium.limboapi.api.world.item.datacomponent.type; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public record SuspiciousStewEffect(int/*Holder*/ effect, int duration) { + +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/Tool.java b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/Tool.java new file mode 100644 index 00000000..1e6f408d --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/Tool.java @@ -0,0 +1,22 @@ +package net.elytrium.limboapi.api.world.item.datacomponent.type; + +import java.util.Collection; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.HolderSet; +import org.checkerframework.checker.index.qual.NonNegative; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.NullMarked; + +/** + * @param canDestroyBlocksInCreative Added in version 1.21.5 + */ +@NullMarked +public record Tool(Collection rules, float defaultMiningSpeed, @NonNegative int damagePerBlock, boolean canDestroyBlocksInCreative) { + + public Tool(Collection rules, float defaultMiningSpeed, @NonNegative int damagePerBlock) { + this(rules, defaultMiningSpeed, damagePerBlock, true); + } + + public record Rule(HolderSet blocks, @Nullable Float speed, @Nullable Boolean correctForDrops) { + + } +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/TooltipDisplay.java b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/TooltipDisplay.java new file mode 100644 index 00000000..2597862b --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/TooltipDisplay.java @@ -0,0 +1,10 @@ +package net.elytrium.limboapi.api.world.item.datacomponent.type; + +import java.util.Set; +import net.elytrium.limboapi.api.world.item.datacomponent.DataComponentType; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public record TooltipDisplay(boolean hideTooltip, Set/*TODO SequencedSet*/ hiddenComponents) { + +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/Unbreakable.java b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/Unbreakable.java new file mode 100644 index 00000000..2f631974 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/Unbreakable.java @@ -0,0 +1,17 @@ +package net.elytrium.limboapi.api.world.item.datacomponent.type; + +import org.jspecify.annotations.NullMarked; + +/** + * @param showInTooltip Removed in version 1.21.5 + */ +@NullMarked +public record Unbreakable(boolean showInTooltip) { + + public static final Unbreakable TRUE = new Unbreakable(true); + public static final Unbreakable FALSE = new Unbreakable(false); + + public Unbreakable() { + this(true); + } +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/UseCooldown.java b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/UseCooldown.java new file mode 100644 index 00000000..d0b3fae9 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/UseCooldown.java @@ -0,0 +1,9 @@ +package net.elytrium.limboapi.api.world.item.datacomponent.type; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public record UseCooldown(float seconds, @Nullable String cooldownGroup) { + +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/Weapon.java b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/Weapon.java new file mode 100644 index 00000000..6494191a --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/Weapon.java @@ -0,0 +1,8 @@ +package net.elytrium.limboapi.api.world.item.datacomponent.type; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public record Weapon(int itemDamagePerAttack, float disableBlockingForSeconds) { + +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/WrittenBookContent.java b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/WrittenBookContent.java new file mode 100644 index 00000000..980831f4 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/WrittenBookContent.java @@ -0,0 +1,12 @@ +package net.elytrium.limboapi.api.world.item.datacomponent.type; + +import java.util.Collection; +import net.elytrium.limboapi.api.protocol.data.ComponentHolder; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.Filterable; +import org.checkerframework.common.value.qual.IntRange; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public record WrittenBookContent(Filterable title, String author, @IntRange(from = 0, to = 3) int generation, Collection> pages, boolean resolved) { + +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/data/ConsumeEffect.java b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/data/ConsumeEffect.java new file mode 100644 index 00000000..014fd560 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/data/ConsumeEffect.java @@ -0,0 +1,29 @@ +package net.elytrium.limboapi.api.world.item.datacomponent.type.data; + +import java.util.Collection; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public sealed interface ConsumeEffect permits ConsumeEffect.ApplyStatusEffects, ConsumeEffect.RemoveStatusEffects, ConsumeEffect.ClearAllStatusEffects, ConsumeEffect.TeleportRandomly, ConsumeEffect.PlaySound { + + record ApplyStatusEffects(Collection effects, float probability) implements ConsumeEffect { + + } + + record RemoveStatusEffects(HolderSet effects) implements ConsumeEffect { + + } + + record ClearAllStatusEffects() implements ConsumeEffect { + + public static final ClearAllStatusEffects INSTANCE = new ClearAllStatusEffects(); + } + + record TeleportRandomly(float diameter) implements ConsumeEffect { + + } + + record PlaySound(Holder sound) implements ConsumeEffect { + + } +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/data/Either.java b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/data/Either.java new file mode 100644 index 00000000..be4a518a --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/data/Either.java @@ -0,0 +1,88 @@ +package net.elytrium.limboapi.api.world.item.datacomponent.type.data; + +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; + +public sealed interface Either permits Either.Left, Either.Right { + + T map(Function l, Function r); + + Either ifLeft(Consumer consumer); + + Either ifRight(Consumer consumer); + + Optional left(); + + Optional right(); + + static Either left(L value) { + return new Left<>(value); + } + + static Either right(R value) { + return new Right<>(value); + } + + static U unwrap(Either either) { + return either.map(Function.identity(), Function.identity()); + } + + record Left(L value) implements Either { + + @Override + public T map(Function l, Function r) { + return l.apply(this.value); + } + + @Override + public Either ifLeft(Consumer consumer) { + consumer.accept(this.value); + return this; + } + + @Override + public Either ifRight(Consumer consumer) { + return this; + } + + @Override + public Optional left() { + return Optional.of(this.value); + } + + @Override + public Optional right() { + return Optional.empty(); + } + } + + record Right(R value) implements Either { + + @Override + public T map(Function l, Function r) { + return r.apply(this.value); + } + + @Override + public Either ifLeft(Consumer consumer) { + return this; + } + + @Override + public Either ifRight(Consumer consumer) { + consumer.accept(this.value); + return this; + } + + @Override + public Optional left() { + return Optional.empty(); + } + + @Override + public Optional right() { + return Optional.of(this.value); + } + } +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/data/Filterable.java b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/data/Filterable.java new file mode 100644 index 00000000..e612e285 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/data/Filterable.java @@ -0,0 +1,9 @@ +package net.elytrium.limboapi.api.world.item.datacomponent.type.data; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public record Filterable(T raw, @Nullable T filtered) { + +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/data/Holder.java b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/data/Holder.java new file mode 100644 index 00000000..0d6bb2b5 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/data/Holder.java @@ -0,0 +1,20 @@ +package net.elytrium.limboapi.api.world.item.datacomponent.type.data; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +@SuppressWarnings("unused") +public sealed interface Holder> permits Holder.Direct, Holder.Reference { + + static > Holder.Reference ref(int id) { + return new Reference<>(id); + } + + non-sealed interface Direct> extends Holder { + + } + + record Reference>(int id) implements Holder { + + } +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/data/HolderSet.java b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/data/HolderSet.java new file mode 100644 index 00000000..6d72c291 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/data/HolderSet.java @@ -0,0 +1,15 @@ +package net.elytrium.limboapi.api.world.item.datacomponent.type.data; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public sealed interface HolderSet permits HolderSet.Named, HolderSet.Direct { + + record Named(String key) implements HolderSet { + + } + + record Direct(int[] contents) implements HolderSet { + + } +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/data/MobEffect.java b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/data/MobEffect.java new file mode 100644 index 00000000..268590aa --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/data/MobEffect.java @@ -0,0 +1,12 @@ +package net.elytrium.limboapi.api.world.item.datacomponent.type.data; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public record MobEffect(int effect, Details details) { + + public record Details(int amplifier, int duration, boolean ambient, boolean visible, boolean showIcon, @Nullable Details hiddenEffect) { + + } +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/data/SoundEvent.java b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/data/SoundEvent.java new file mode 100644 index 00000000..38198e56 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/data/SoundEvent.java @@ -0,0 +1,9 @@ +package net.elytrium.limboapi.api.world.item.datacomponent.type.data; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public record SoundEvent(String soundId, @Nullable Float range) implements Holder.Direct { + +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/data/TrimMaterial.java b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/data/TrimMaterial.java new file mode 100644 index 00000000..3efa980b --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/data/TrimMaterial.java @@ -0,0 +1,23 @@ +package net.elytrium.limboapi.api.world.item.datacomponent.type.data; + +import java.util.Map; +import net.elytrium.limboapi.api.protocol.data.ComponentHolder; +import org.jspecify.annotations.NullMarked; + +/** + * @param ingredient Removed in version 1.21.5 + * @param itemModelIndex Removed in version 1.21.4 + * @param overrides For versions prior to 1.21.2, the map key is an {@code Integer}. + * Since version 1.21.2, the map key is a {@code String} + */ +@NullMarked +public record TrimMaterial(String assetName, int/*Holder*/ ingredient, float itemModelIndex, Map overrides, ComponentHolder description) implements Holder.Direct { + + public TrimMaterial(String assetName, int ingredient, Map overrides, ComponentHolder description) { + this(assetName, ingredient, 0.0F, overrides, description); + } + + public TrimMaterial(String assetName, Map overrides, ComponentHolder description) { + this(assetName, 0, 0.0F, overrides, description); + } +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/data/TrimPattern.java b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/data/TrimPattern.java new file mode 100644 index 00000000..e2d296a3 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/data/TrimPattern.java @@ -0,0 +1,15 @@ +package net.elytrium.limboapi.api.world.item.datacomponent.type.data; + +import net.elytrium.limboapi.api.protocol.data.ComponentHolder; +import org.jspecify.annotations.NullMarked; + +/** + * @param templateItem Removed in version 1.21.5 + */ +@NullMarked +public record TrimPattern(String assetId, int/*Holder*/ templateItem, ComponentHolder description, boolean decal) implements Holder.Direct { + + public TrimPattern(String assetId, ComponentHolder description, boolean decal) { + this(assetId, 0, description, decal); + } +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/data/TypedEntityData.java b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/data/TypedEntityData.java new file mode 100644 index 00000000..a58387e7 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/item/datacomponent/type/data/TypedEntityData.java @@ -0,0 +1,15 @@ +package net.elytrium.limboapi.api.world.item.datacomponent.type.data; + +import net.kyori.adventure.nbt.CompoundBinaryTag; +import org.jspecify.annotations.NullMarked; + +/** + * @param type Added in version 1.21.9 + */ +@NullMarked +public record TypedEntityData(int type, CompoundBinaryTag tag) { + + public TypedEntityData(CompoundBinaryTag tag) { + this(0, tag); + } +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/player/GameMode.java b/api/src/main/java/net/elytrium/limboapi/api/world/player/GameMode.java similarity index 55% rename from api/src/main/java/net/elytrium/limboapi/api/player/GameMode.java rename to api/src/main/java/net/elytrium/limboapi/api/world/player/GameMode.java index cb45590d..37e9b491 100644 --- a/api/src/main/java/net/elytrium/limboapi/api/player/GameMode.java +++ b/api/src/main/java/net/elytrium/limboapi/api/world/player/GameMode.java @@ -5,7 +5,7 @@ * reference the LICENSE file in the api top-level directory. */ -package net.elytrium.limboapi.api.player; +package net.elytrium.limboapi.api.world.player; import org.checkerframework.checker.nullness.qual.Nullable; @@ -17,32 +17,43 @@ public enum GameMode { SPECTATOR; /** - * Cached {@link #values()} array to avoid constant array allocation. + * Cached {@link #values()} array to avoid constant array allocation */ - private static final GameMode[] VALUES = values(); + private static final GameMode[] VALUES = GameMode.values(); /** - * Get the ID of this {@link GameMode}. + * Get the id of this {@link GameMode} * - * @return The ID. + * @return The id * - * @see #getByID(int) + * @see #getById(int) */ - public short getID() { + public short getId() { return (short) this.ordinal(); } + @Deprecated(forRemoval = true) + public short getID() { + return this.getId(); + } + /** - * Get a {@link GameMode} by its' ID. + * Get a {@link GameMode} by its' id * - * @param id The ID. + * @param id The id * - * @return The {@link GameMode}, or {@code null} if it does not exist. + * @return The {@link GameMode}, or {@code null} if it does not exist * - * @see #getID() + * @see #getId() */ @Nullable + public static GameMode getById(int id) { + return GameMode.VALUES[id]; + } + + @Nullable + @Deprecated(forRemoval = true) public static GameMode getByID(int id) { - return VALUES[id]; + return GameMode.getById(id); } } diff --git a/api/src/main/java/net/elytrium/limboapi/api/world/player/LimboPlayer.java b/api/src/main/java/net/elytrium/limboapi/api/world/player/LimboPlayer.java new file mode 100644 index 00000000..41283f59 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/world/player/LimboPlayer.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2021 - 2025 Elytrium + * + * The LimboAPI (excluding the LimboAPI plugin) is licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in the api top-level directory. + */ + +package net.elytrium.limboapi.api.world.player; + +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.server.RegisteredServer; +import java.awt.image.BufferedImage; +import java.util.Collection; +import java.util.concurrent.ScheduledExecutorService; +import net.elytrium.limboapi.api.Limbo; +import net.elytrium.limboapi.api.world.item.VirtualItem; +import net.elytrium.limboapi.api.world.item.datacomponent.DataComponentMap; +import net.elytrium.limboapi.api.protocol.data.AbilityFlags; +import net.elytrium.limboapi.api.protocol.data.EntityData; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import org.checkerframework.checker.nullness.qual.Nullable; + +public interface LimboPlayer { + + void writePacket(Object msg); + + void writePacketAndFlush(Object msg); + + void flushPackets(); + + void closeWith(Object msg); + + ScheduledExecutorService getScheduledExecutor(); + + default void sendImage(BufferedImage image) { + this.sendImage(0, image, true, 36, true); + } + + default void sendImage(BufferedImage image, boolean sendItem) { + this.sendImage(0, image, sendItem, 36, true); + } + + default void sendImage(int mapId, BufferedImage image) { + this.sendImage(mapId, image, true, 36, true); + } + + default void sendImage(int mapId, BufferedImage image, boolean sendItem) { + this.sendImage(mapId, image, sendItem, 36, true); + } + + default void sendImage(int mapId, BufferedImage image, boolean sendItem, boolean resize) { + this.sendImage(mapId, image, sendItem, 36, resize); + } + + void sendImage(int mapId, BufferedImage image, boolean sendItem, int itemSlot, boolean resize); + + @Deprecated(forRemoval = true) + default void setInventory(VirtualItem item, int count) { + this.setItemInMainHand(item, count); + } + + @Deprecated(forRemoval = true) + default void setInventory(VirtualItem item, int slot, int count) { + this.setItem(slot, item, count); + } + + @Deprecated(forRemoval = true) + default void setInventory(int slot, VirtualItem item, int count, int data, CompoundBinaryTag nbt) { + this.setItem(slot, item, count, (short) data, nbt, null); + } + + @Deprecated(forRemoval = true) + default void setInventory(int slot, VirtualItem item, int count, int data, DataComponentMap map) { + this.setItem(slot, item, count, (short) data, null, map); + } + + void setItemInMainHand(VirtualItem item, int count); + + void setItemInOffHand(VirtualItem item, int count); + + void setItem(int slot, VirtualItem item, int count); + + void setItem(int slot, VirtualItem item, int count, @Nullable CompoundBinaryTag nbt); + + void setItem(int slot, VirtualItem item, int count, @Nullable DataComponentMap map); + + void setItem(int slot, VirtualItem item, int count, short data); + + void setItem(int slot, VirtualItem item, int count, short data, @Nullable CompoundBinaryTag nbt); + + void setItem(int slot, VirtualItem item, int count, short data, @Nullable DataComponentMap map); + + void setItem(int slot, VirtualItem item, int count, short data, @Nullable CompoundBinaryTag nbt, @Nullable DataComponentMap map); + + void setGameMode(GameMode gameMode); + + void setWorldTime(long ticks); + + void teleport(double posX, double posY, double posZ, float yaw, float pitch); + + void disableFalling(); + + void enableFalling(); + + void disconnect(); + + void disconnect(RegisteredServer server); + + /** + * @param id Entity id + * @param data See {@link EntityData} for all possible data types available + */ + void setEntityData(int id, EntityData data); + + @Deprecated(forRemoval = true) + default void sendAbilities() { + this.sendGameModeSpecificAbilities(); + } + + /** + * @apiNote Also resets flyingSpeed and walkingSpeed + */ + void sendGameModeSpecificAbilities(); + + /** + * @param abilities See {@link AbilityFlags} (e.g. {@code AbilityFlags.ALLOW_FLYING | AbilityFlags.CREATIVE_MODE}) + */ + void sendAbilities(int abilities, float flyingSpeed, float walkingSpeed); + + /** + * @param abilities See {@link AbilityFlags} (e.g. {@code AbilityFlags.ALLOW_FLYING | AbilityFlags.CREATIVE_MODE}) + */ + void sendAbilities(byte abilities, float flyingSpeed, float walkingSpeed); + + @Deprecated(forRemoval = true) + default byte getAbilities() { + return this.getGameModeSpecificAbilities(); + } + + byte getGameModeSpecificAbilities(); + + GameMode getGameMode(); + + Limbo getServer(); + + Player getProxyPlayer(); + + int getPing(); +} diff --git a/api/src/main/templates/net/elytrium/limboapi/BuildConstants.java b/api/src/main/templates/net/elytrium/limboapi/BuildConstants.java deleted file mode 100644 index cd5c3662..00000000 --- a/api/src/main/templates/net/elytrium/limboapi/BuildConstants.java +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * The LimboAPI (excluding the LimboAPI plugin) is licensed under the terms of the MIT License. For more details, - * reference the LICENSE file in the api top-level directory. - */ - -package net.elytrium.limboapi; - -// The constants are replaced before compilation. -public class BuildConstants { - - public static final String LIMBO_VERSION = "${version}"; -} diff --git a/build.gradle b/build.gradle index bf4e042d..96a0e52d 100644 --- a/build.gradle +++ b/build.gradle @@ -1,71 +1,107 @@ -//file:noinspection GroovyAssignabilityCheck - plugins() { id("java") - id("checkstyle") - id("com.github.spotbugs").version("6.0.12").apply(false) - id("org.cadixdev.licenser").version("0.6.1").apply(false) + id("com.diffplug.spotless").version("8.0.0").apply(false) + id("com.github.spotbugs").version("6.4.4").apply(false) + + id("com.gradleup.shadow").version("9.2.2") } allprojects() { + group = "net.elytrium" + version = "2.0.0-SNAPSHOT" +} + +if (this.version.endsWith("-SNAPSHOT")) { + final commitId = "git rev-parse --short HEAD".execute().getText().strip() + if (commitId) { + this.version += "+$commitId" + } +} + +subprojects() { + apply(plugin: "java-library") + /* + + apply(plugin: "com.diffplug.spotless") apply(plugin: "checkstyle") apply(plugin: "com.github.spotbugs") - apply(plugin: "org.cadixdev.licenser") + */ - setGroup("net.elytrium.limboapi") - setVersion("1.1.27-SNAPSHOT") + tasks.withType(JavaCompile).configureEach() { + options.setEncoding("UTF-8") + } - compileJava() { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 + java() { + sourceCompatibility = targetCompatibility = JavaVersion.VERSION_17 + } + + configurations() { + api.extendsFrom(shadowApi) } repositories() { mavenCentral() maven() { - setName("elytrium-repo") - setUrl("https://maven.elytrium.net/repo/") + name = "elytrium" + url = "https://maven.elytrium.net/repo/" } + maven() { - setName("papermc-repo") - setUrl("https://repo.papermc.io/repository/maven-public/") + name = "papermc" + url = "https://repo.papermc.io/repository/maven-public/" + } + } + /* + + spotless() { + java() { + licenseHeaderFile(rootProject.file("HEADER.txt")) } } checkstyle() { - toolVersion = "10.12.1" - configFile = file("$rootDir/config/checkstyle/checkstyle.xml") - configProperties = ["configDirectory": "$rootDir/config/checkstyle"] + toolVersion = "12.1.1" + + configFile = rootProject.file(".config/checkstyle/checkstyle.xml") maxErrors = 0 maxWarnings = 0 } - spotbugs() { - excludeFilter = file("${this.getRootDir()}/config/spotbugs/suppressions.xml") - - if (this.project != rootProject) { - reports.register("html") { + spotbugsMain() { + excludeFilter.set(rootProject.file(".config/spotbugs/suppressions.xml")) + reports() { + html() { required = true - outputLocation.value(layout.buildDirectory.file("reports/spotbugs/main/spotbugs.html")) + outputLocation.value(layout.buildDirectory.file("reports/spotbugs/spotbugs.html")) stylesheet = "fancy-hist.xsl" } } } + */ } -String getCurrentShortRevision() { - OutputStream outputStream = new ByteArrayOutputStream() - exec { - if (System.getProperty("os.name").toLowerCase().contains("win")) { - commandLine("cmd", "/c", "git rev-parse --short HEAD") - } else { - commandLine("bash", "-c", "git rev-parse --short HEAD") - } +gradle.projectsEvaluated() { + shadowJar() { + archiveClassifier = null + + //enableRelocation = true + // TODO check this.name or something + //relocationPrefix = "${this.group}.${this.rootProject.getName()}.3rdparty" + // TODO remove + relocate("org.bstats", "net.elytrium.limboapi.thirdparty.org.bstats") + relocate("net.elytrium.fastprepare", "net.elytrium.limboapi.thirdparty.fastprepare") + relocate("net.elytrium.commons.velocity", "net.elytrium.limboapi.thirdparty.commons.velocity") + relocate("net.elytrium.commons.kyori", "net.elytrium.limboapi.thirdparty.commons.kyori") + relocate("net.elytrium.commons.config", "net.elytrium.limboapi.thirdparty.commons.config") - setStandardOutput(outputStream) + configurations = [] + this.subprojects.forEach(project -> { + configurations.add(project.configurations.shadowApi) + from(project.sourceSets.main.output) + }) } - return outputStream.toString().trim() + assemble.dependsOn(shadowJar) } diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml deleted file mode 100644 index a78b3719..00000000 --- a/config/checkstyle/checkstyle.xml +++ /dev/null @@ -1,368 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml deleted file mode 100644 index c8b6730a..00000000 --- a/config/checkstyle/suppressions.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/config/spotbugs/suppressions.xml b/config/spotbugs/suppressions.xml deleted file mode 100644 index 531b7ce4..00000000 --- a/config/spotbugs/suppressions.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/gradle.properties b/gradle.properties index 0fd62712..0cd7651b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,11 +1,15 @@ -org.gradle.jvmargs=-Xmx4096m -fastPrepareVersion=1.0.13 -velocityVersion=3.4.0-SNAPSHOT -nettyVersion=4.1.86.Final -fastutilVersion=8.5.11 -bstatsVersion=3.0.0 -spotbugsVersion=4.7.3 -elytriumCommonsVersion=1.2.3 -adventureVersion=4.15.0 +# Breaks maven-publish +org.gradle.configuration-cache=false +org.gradle.configuration-cache.problems=warn + +org.gradle.parallel=true +org.gradle.caching=true + +org.gradle.jvmargs=-Xmx4G + manifestUrl=https://launchermeta.mojang.com/mc/game/version_manifest.json -cacheValidMillis=6000000 \ No newline at end of file +# 1 week +cacheValidMillis=604800000 + +# Well, yeah... +gameVersions=1.21.10,1.21.9,1.21.8,1.21.7,1.21.6,1.21.5,1.21.4,1.21.3,1.21.2,1.21.1,1.21,1.20.6,1.20.5,1.20.4,1.20.3,1.20.2,1.20.1,1.20,1.19.4,1.19.3,1.19.2,1.19.1,1.19,1.18.2,1.18.1,1.18,1.17.1,1.17,1.16.5,1.16.4,1.16.3,1.16.2,1.16.1,1.16,1.15.2,1.15.1,1.15,1.14.4,1.14.3,1.14.2,1.14.1,1.14,1.13.2,1.13.1,1.13,1.12.2,1.12.1,1.12,1.11.2,1.11.1,1.11,1.10.2,1.10.1,1.10,1.9.4,1.9.3,1.9.2,1.9.1,1.9,1.8.9,1.8.8,1.8.7,1.8.6,1.8.5,1.8.4,1.8.3,1.8.2,1.8.1,1.8,1.7.10,1.7.9,1.7.8,1.7.7,1.7.6,1.7.5,1.7.4,1.7.3,1.7.2 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 00000000..c740b1b3 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,19 @@ +[versions] +velocity = "3.4.0-SNAPSHOT" + +# TODO +[libraries] +velocity-api = { module = "com.velocitypowered:velocity-api", version.ref = "velocity" } +velocity-proxy = { module = "com.velocitypowered:velocity-proxy", version.ref = "velocity" } # from Elytrium repo + +[bundles] +velocity = ["velocity-proxy"] + +[plugins] +grgit = "org.ajoberstar.grgit:5.2.2" +buildconfig = "com.github.gmazzo.buildconfig:5.3.5" # kotlin 💀 + +spotless = "com.diffplug.spotless:6.25.0" +spotbugs = "com.github.spotbugs:6.0.13" + +modrinth = "com:1" diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 41d9927a..f8e1ee31 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2733ed5d..23449a2b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 1b6c7873..adff685a 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,13 +82,11 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -114,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -133,22 +132,29 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -165,7 +171,6 @@ fi # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) @@ -193,18 +198,27 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index 107acd32..c4bdd3ab 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,8 +13,10 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +27,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,13 +43,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -56,32 +59,33 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/plugin/build.gradle b/plugin/build.gradle index fad45c8c..4fbe18f9 100644 --- a/plugin/build.gradle +++ b/plugin/build.gradle @@ -1,707 +1,39 @@ -//file:noinspection GroovyAssignabilityCheck - -buildscript() { - dependencies() { - classpath("commons-io:commons-io:2.6") - classpath("com.google.guava:guava:28.0-jre") - } -} +//file:noinspection VulnerableLibrariesLocal plugins() { - id("java") - id("com.gradleup.shadow").version("8.3.6") + id("com.modrinth.minotaur").version("2.8.10") } -compileJava() { - getOptions().getRelease().set(17) - getOptions().setEncoding("UTF-8") -} +apply(from: "mappings_generator.gradle") dependencies() { - implementation(project(":api")) - implementation("net.elytrium.commons:config:$elytriumCommonsVersion") - implementation("net.elytrium.commons:utils:$elytriumCommonsVersion") - implementation("net.elytrium.commons:velocity:$elytriumCommonsVersion") - implementation("net.elytrium.commons:kyori:$elytriumCommonsVersion") - - implementation("net.elytrium:fastprepare:$fastPrepareVersion") - compileOnly("com.velocitypowered:velocity-api:$velocityVersion") - annotationProcessor("com.velocitypowered:velocity-api:$velocityVersion") - compileOnly("com.velocitypowered:velocity-proxy:$velocityVersion") // From Elytrium Repo. - compileOnly("com.velocitypowered:velocity-native:$velocityVersion") - - // Needs for some velocity methods. - compileOnly("io.netty:netty-codec:$nettyVersion") - compileOnly("io.netty:netty-handler:$nettyVersion") - compileOnly("it.unimi.dsi:fastutil-core:$fastutilVersion") - - implementation("org.bstats:bstats-velocity:$bstatsVersion") - - compileOnly("com.github.spotbugs:spotbugs-annotations:$spotbugsVersion") -} - -shadowJar() { - getArchiveClassifier().set("") - setArchiveFileName("limboapi-${project.version}.jar") - - exclude("META-INF/versions/**") - exclude("net/kyori/**") - - relocate("org.bstats", "net.elytrium.limboapi.thirdparty.org.bstats") - relocate("net.elytrium.fastprepare", "net.elytrium.limboapi.thirdparty.fastprepare") - relocate("net.elytrium.commons.velocity", "net.elytrium.limboapi.thirdparty.commons.velocity") - relocate("net.elytrium.commons.kyori", "net.elytrium.limboapi.thirdparty.commons.kyori") - relocate("net.elytrium.commons.config", "net.elytrium.limboapi.thirdparty.commons.config") -} - -license() { - matching(includes: ["**/mcprotocollib/**"]) { - setHeader(getRootProject().file("HEADER_MCPROTOCOLLIB.txt")) - } - matching(includes: ["**/LoginListener.java", "**/KickListener.java", "**/LoginTasksQueue.java", "**/MinecraftLimitedCompressDecoder.java"]) { - setHeader(getRootProject().file("HEADER_MIXED.txt")) - } - - setHeader(getRootProject().file("HEADER.txt")) -} - -tasks.register("finalize") { - doLast { - file("build/libs/${project.name}-${project.version}.jar").delete() - } -} - -assemble.dependsOn(shadowJar) -build.finalizedBy(finalize) - -import groovy.io.FileType -import groovy.json.JsonOutput -import groovy.json.JsonSlurper -import org.apache.commons.io.FilenameUtils -import org.apache.commons.io.FileUtils -import com.google.common.hash.Hashing -import com.google.common.io.Files - -import java.nio.file.Path -import java.util.function.Function -import java.util.stream.Collectors - -enum MinecraftVersion { - MINECRAFT_1_7_2(4), - MINECRAFT_1_7_6(5), - MINECRAFT_1_8(47), - MINECRAFT_1_9(107), - MINECRAFT_1_9_1(108), - MINECRAFT_1_9_2(109), - MINECRAFT_1_9_4(110), - MINECRAFT_1_10(210), - MINECRAFT_1_11(315), - MINECRAFT_1_11_1(316), - MINECRAFT_1_12(335), - MINECRAFT_1_12_1(338), - MINECRAFT_1_12_2(340), - MINECRAFT_1_13(393), - MINECRAFT_1_13_1(401), - MINECRAFT_1_13_2(404), - MINECRAFT_1_14(477), - MINECRAFT_1_14_1(480), - MINECRAFT_1_14_2(485), - MINECRAFT_1_14_3(490), - MINECRAFT_1_14_4(498), - MINECRAFT_1_15(573), - MINECRAFT_1_15_1(575), - MINECRAFT_1_15_2(578), - MINECRAFT_1_16(735), - MINECRAFT_1_16_1(736), - MINECRAFT_1_16_2(751), - MINECRAFT_1_16_3(753), - MINECRAFT_1_16_4(754), - MINECRAFT_1_17(755), - MINECRAFT_1_17_1(756), - MINECRAFT_1_18(757), - MINECRAFT_1_18_2(758), - MINECRAFT_1_19(759), - MINECRAFT_1_19_1(760), - MINECRAFT_1_19_3(761), - MINECRAFT_1_19_4(762), - MINECRAFT_1_20(763), - MINECRAFT_1_20_2(764), - MINECRAFT_1_20_3(765), - MINECRAFT_1_20_5(766), - MINECRAFT_1_21(767), - MINECRAFT_1_21_2(768), - MINECRAFT_1_21_4(769), - MINECRAFT_1_21_5(770), - MINECRAFT_1_21_6(771), - MINECRAFT_1_21_7(772) - - public static final List WORLD_VERSIONS = List.of( - MINECRAFT_1_13, - MINECRAFT_1_13_2, - MINECRAFT_1_14, - MINECRAFT_1_15, - MINECRAFT_1_16, - MINECRAFT_1_16_2, - MINECRAFT_1_17, - MINECRAFT_1_19, - MINECRAFT_1_19_3, - MINECRAFT_1_19_4, - MINECRAFT_1_20, - MINECRAFT_1_20_3, - MINECRAFT_1_20_5, - MINECRAFT_1_21_2, - MINECRAFT_1_21_4, - MINECRAFT_1_21_5, - MINECRAFT_1_21_6, - MINECRAFT_1_21_7 - ) - - public static final MinecraftVersion MINIMUM_VERSION = MINECRAFT_1_7_2 - public static final MinecraftVersion MAXIMUM_VERSION = values()[values().length - 1] - - static MinecraftVersion fromVersionName(String name) { - return valueOf("MINECRAFT_" + name.replace('.', '_')) - } - - // Cache version name to reduce memory usage in general - final String versionName = this.toString().substring(10).replace('_', '.') - final int protocolVersion - - MinecraftVersion(int protocolVersion) { - this.protocolVersion = protocolVersion - } - - int getProtocolVersion() { - return this.protocolVersion - } - - String getVersionName() { - return this.versionName - } -} - -project.ext.dataDirectory = new File(this.getLayout().getBuildDirectory().get().getAsFile(), "minecraft") -project.ext.generatedDir = new File(this.getLayout().getBuildDirectory().get().getAsFile(), "generated/minecraft") -project.ext.versionManifestFile = new File(dataDirectory, "manifest.json") - -sourceSets { - main { - resources { - srcDirs += generatedDir - } - } -} - -tasks.register("downloadManifest") { - this.println("> Downloading version manifest...") - versionManifestFile.getParentFile().mkdirs() - if (checkIsCacheValid(versionManifestFile)) { - FileUtils.copyURLToFile(new URL(manifestUrl), versionManifestFile) - } -} - -boolean checkIsCacheValid(File file) { - if (file.exists() && System.currentTimeMillis() - file.lastModified() < Long.parseLong(cacheValidMillis)) { - println("> Found cached " + file.getName()) - return false - } - - return true -} -File downloadVersionManifest(String version) { - this.println("> Downloading ${version} manifest...") - - Object manifest = new JsonSlurper().parse(versionManifestFile) - def optional = manifest.versions.stream().filter({ it.id == version }).findFirst() - if (optional.empty()) { - throw new RuntimeException("Couldn't find version: ${version}") - } - - File output = new File(dataDirectory, "${version}/manifest.json") - output.getParentFile().mkdirs() - FileUtils.copyURLToFile(new URL(optional.get().url), output) - return output -} - -@SuppressWarnings('GrMethodMayBeStatic') -File getGeneratedCache(MinecraftVersion version) { - File generated = new File(dataDirectory, "${version.getVersionName()}/generated") - return new File(generated, "reports/blocks.json").exists() - && new File(generated, "reports/${version >= MinecraftVersion.MINECRAFT_1_14 ? "registries" : "items"}.json").exists() - && new File(generated, "data/minecraft/tags").exists() - ? generated : null -} - -static boolean validateServer(File file, String expected) { - if (file == null || !file.exists()) { - return false - } - - def hash = Files.asByteSource(file).hash(Hashing.sha1()) - StringBuilder hashBuilder = new StringBuilder() - hash.asBytes().each({hashBuilder.append(Integer.toString((it & 0xFF) + 0x100, 16).substring(1))}) - return hashBuilder.toString() == expected -} - -File getServerJar(String version) { - File manifestFile = this.downloadVersionManifest(version) - Object manifest = new JsonSlurper().parse(manifestFile) - - File jarFile = new File(dataDirectory, "${version}/server.jar") - if (!validateServer(jarFile, manifest.downloads.server.sha1)) { - this.println("> Downloading ${version} server...") - jarFile.getParentFile().mkdirs() - FileUtils.copyURLToFile(new URL(manifest.downloads.server.url), jarFile) - } - - return jarFile -} - -File generateData(MinecraftVersion version) { - File cache = getGeneratedCache(version) - if (cache != null) { - return cache - } - - File jarFile = this.getServerJar(version.getVersionName()) - File targetDir = new File(jarFile.getParentFile(), "generated") - - try { - FileUtils.deleteDirectory(targetDir) - } catch (IOException ignored) { - // Ignored. - } - - String command - if (version >= MinecraftVersion.MINECRAFT_1_18) { - command = "\"%s\" -DbundlerMainClass=net.minecraft.data.Main -jar \"${jarFile.getAbsolutePath()}\" --reports --server" - } else { - command = "\"%s\" -cp \"${jarFile.getAbsolutePath()}\" net.minecraft.data.Main --reports --server" - } - - exec { - if (System.getProperty("os.name").toLowerCase().contains("win")) { - File java = new File(System.getProperty("java.home"), "bin/java.exe") - commandLine("cmd", "/c", String.format(command, java)) - } else { - File java = new File(System.getProperty("java.home"), "bin/java") - commandLine("bash", "-c", String.format(command, java)) - } - - workingDir(jarFile.getParentFile()) - } - - return targetDir -} - -static Map> getDefaultProperties(Object data) { - Map> defaultProperties = new HashMap<>() - - data.forEach({ key, block -> - if (!block.containsKey("properties")) { - return - } - - for (Object blockState : block.states) { - if (!blockState.containsKey("default") || !blockState.default) { - continue - } - - Map properties = blockState["properties"] - defaultProperties.put(key, properties) - break - } - }) - - return defaultProperties -} - -static Map> loadFallbackMapping(File file) { - Object map = new JsonSlurper().parse(file) - return MinecraftVersion.values().collectEntries({ version -> - [version, map.getOrDefault(version.toString(), Collections.emptyMap())] - }) -} - -static Map> loadLegacyMapping(File file) { - return new JsonSlurper().parse(file).collectEntries({ version, mapping -> - [MinecraftVersion.valueOf(version), mapping.collectEntries({ block, id -> - [block, Integer.parseInt(id)] - })] - }) -} - -static int getBlockID(String block, - Map> mappings, - Map>> properties, - Map> fallback, - MinecraftVersion version) { - Map> defaultProperties - if (version >= MinecraftVersion.MINECRAFT_1_13) { - defaultProperties = properties[version] - } else { - defaultProperties = properties[MinecraftVersion.MINECRAFT_1_18_2] - } - - String[] split = block.split("\\[") - String noArgBlock = split[0] - - MinecraftVersion fallbackVersion = MinecraftVersion.MAXIMUM_VERSION - while (fallbackVersion != version) { - --fallbackVersion - noArgBlock = fallback[fallbackVersion].getOrDefault(noArgBlock, noArgBlock) - } - - Map blockProperties = defaultProperties[noArgBlock] - String targetBlockID - if (blockProperties == null) { - targetBlockID = noArgBlock - } else { - Map currentProperties = new TreeMap<>(blockProperties) - if (split.length > 1) { - String[] args = split[1].split(",") - Map input = Arrays.stream(args) - .map(arg -> arg.replace("]", "").split("=")) - .collect(Collectors.toMap(parts -> parts[0], parts -> parts[1])) - - input.forEach({ key, value -> - if (currentProperties.containsKey(key)) { - currentProperties.put(key, value) - } - }) - } - - targetBlockID = noArgBlock + Arrays.toString( - currentProperties.collect({ k, v -> k + "=" + v }).toArray() - ).replace(" ", "") - } - - Integer id = mappings[version][targetBlockID] - if (id == null && blockProperties != null) { - targetBlockID = noArgBlock + Arrays.toString( - new TreeMap<>(blockProperties).collect({ k, v -> k + "=" + v }).toArray() - ).replace(" ", "") - id = mappings[version][targetBlockID] - } - - if (id == null) { - System.err.println("No ${version.getVersionName()} fallback data for ${noArgBlock}, replacing with minecraft:stone") - id = 1 - } - - return id -} - -static Map getBlockMappings(Object data, Map> defaultPropertiesMap) { - Map mapping = new HashMap<>() - - data.forEach({ blockID, blockData -> - for (Object blockState : blockData.states) { - int protocolID = blockState.id - - if (blockState.containsKey("properties")) { - Map stateProperties = blockState["properties"] - Map properties = new TreeMap<>( - defaultPropertiesMap.getOrDefault(blockID, Collections.emptyMap())) - - properties.putAll(stateProperties) - - String stateID = blockID + Arrays.toString( - properties.collect({ k, v -> k + "=" + v }).toArray() - ).replace(" ", "") - - mapping.put(stateID, protocolID) - } else { - mapping.put(blockID, protocolID) - } - } - }) - - return mapping -} - -void generateBlockMappings(File targetDir, Map blockReports) { - File defaultBlockPropertiesFile = new File(targetDir, "defaultblockproperties.json") - File blockStatesFile = new File(targetDir, "blockstates.json") - File blockStatesMappingFile = new File(targetDir, "blockstates_mapping.json") - File legacyBlocksFile = new File(targetDir, "legacyblocks.json") - - if (checkIsCacheValid(defaultBlockPropertiesFile) || checkIsCacheValid(blockStatesFile) - || checkIsCacheValid(blockStatesMappingFile) || checkIsCacheValid(legacyBlocksFile)) { - this.println("> Generating default block properties...") - - Map>> defaultProperties = - blockReports.collectEntries({ version, report -> - [version, getDefaultProperties(report)] - }) - - defaultBlockPropertiesFile.write(JsonOutput.prettyPrint( - JsonOutput.toJson(defaultProperties[MinecraftVersion.MAXIMUM_VERSION].sort())), "UTF-8") - - this.println("> Generating blockstates...") - - Map> mappings = loadLegacyMapping( - new File(this.getProjectDir(), "mapping/legacyblockmapping.json")) - - blockReports.forEach({ version, report -> - mappings.put(version, getBlockMappings(report, defaultProperties[version])) - }) - - Map blocks = mappings[MinecraftVersion.MAXIMUM_VERSION] - - blockStatesFile.write( - JsonOutput.prettyPrint(JsonOutput.toJson( - blocks.sort(Map.Entry::getValue) - .collectEntries({ k, v -> [k, String.valueOf(v)] }) - )), "UTF-8") - - - - this.println("> Generating blockstates mapping...") - - Map> fallbackMapping = loadFallbackMapping( - new File(this.getProjectDir(), "mapping/fallbackdata.json")) - - Map> blockstateMapping = new LinkedHashMap<>() - blocks.sort(Map.Entry::getValue) - .forEach({ block, modernID -> - Map blockMapping = new LinkedHashMap<>() - - int lastID = -1 - for (MinecraftVersion version : MinecraftVersion.values()) { - int id = getBlockID(block, mappings, defaultProperties, fallbackMapping, version) - if (lastID != id) { - blockMapping.put(version.getVersionName(), String.valueOf(lastID = id)) - } - } - - blockstateMapping.put(String.valueOf(modernID), blockMapping) - }) - - blockStatesMappingFile.write( - JsonOutput.prettyPrint(JsonOutput.toJson(blockstateMapping)), "UTF-8") - - this.println("> Generating legacy blocks...") - - Map legacyData = new JsonSlurper().parse( - new File(this.getProjectDir(), "mapping/legacyblocks.json")) - - legacyData = legacyData.collectEntries({ legacy, modern -> - [legacy, String.valueOf(getBlockID(modern, mappings, defaultProperties, fallbackMapping, MinecraftVersion.MAXIMUM_VERSION))] - }) - - legacyBlocksFile.write( - JsonOutput.prettyPrint(JsonOutput.toJson(legacyData)), "UTF-8") - } -} - -static Map> sortRegistryMapping(Map> mapping) { - return mapping.collectEntries({ modernID, map -> - [modernID, map.sort({ - if (it.getKey().contains(".")) { - return MinecraftVersion.fromVersionName(it.getKey()) - } else { - return MinecraftVersion.MINIMUM_VERSION - } - })] - }).sort() -} - -void generateRegistryMapping(String target, File targetDir, Map registriesReports) { - File targetFile = new File(targetDir, "${target}s.json"); - File targetMappingFile = new File(targetDir, "${target}s_mapping.json"); - if (checkIsCacheValid(targetFile) || checkIsCacheValid(targetMappingFile)) { - this.println("> Generating ${target}s...") - - Map> idMap = - registriesReports.collectEntries({ version, registry -> - Object entries = registry["minecraft:${target}"].entries - return [version, entries.collectEntries({ name, id -> [name, String.valueOf(id["protocol_id"])] })] - }) - - Map modernIDs = Collections.max(idMap.entrySet(), Map.Entry.comparingByKey()).getValue() - - targetFile.write(JsonOutput.prettyPrint( - JsonOutput.toJson(modernIDs.sort({Integer.parseInt(it.getValue()) }))), "UTF-8") - - this.println("> Generating ${target}s mapping...") - - Map> mapping = new JsonSlurper() - .parse(new File(this.getProjectDir(), "mapping/legacy_${target}s_mapping.json")) - .collectEntries({ key, value -> { - if (modernIDs[key] == null) { - throw new IllegalStateException("No modern id found for $key") - } - - return [modernIDs[key], value] - } }) - - idMap.forEach({ version, ids -> - ids.forEach({ key, id -> - if (!modernIDs.containsKey(key)) { - return - } - - mapping.computeIfAbsent(modernIDs[key], _ -> new LinkedHashMap<>()).put(version.getVersionName(), id) - }) - }) - - mapping = sortRegistryMapping(mapping) - targetMappingFile.write(JsonOutput.prettyPrint( - JsonOutput.toJson(mapping.sort({ Integer.parseInt(it.getKey()) }))), "UTF-8") - } -} - -void generateRegistryMappings(File targetDir, Map registriesReports) { - this.generateRegistryMapping("item", targetDir, registriesReports - .findAll({ e -> MinecraftVersion.WORLD_VERSIONS.contains(e.getKey()) })) - this.generateRegistryMapping("block", targetDir, registriesReports) - this.generateRegistryMapping("data_component_type", targetDir, registriesReports - .findAll({ e -> e.getKey() >= MinecraftVersion.MINECRAFT_1_20_5 })) - - File blockEntitiesMappingFile = new File(targetDir, "blockentities_mapping.json"); - - if (checkIsCacheValid(blockEntitiesMappingFile)) { - this.println("> Generating blockentities mapping...") - - Map> blockentities = new JsonSlurper() - .parse(new File(this.getProjectDir(), "mapping/legacy_blockentities_mapping.json")) - - registriesReports.forEach({ version, registries -> - if (version < MinecraftVersion.MINECRAFT_1_19) { - return - } - - registries["minecraft:block_entity_type"].entries.forEach({ key, value -> - int id = value.protocol_id - blockentities.computeIfAbsent(key, _ -> new LinkedHashMap<>()) - .put(version.getVersionName(), String.valueOf(id)) - }) - }) - - blockentities = sortRegistryMapping(blockentities) - blockEntitiesMappingFile.write( - JsonOutput.prettyPrint(JsonOutput.toJson(blockentities)), "UTF-8") - } -} - -static Map>> getTags(File tagDir, Map tagTypes) { - Map>> tags = new LinkedHashMap<>() - - tagTypes.forEach({ directory, key -> - File directoryFile = new File(tagDir, directory) - if (!directoryFile.exists()) { - return - } - - Map> typeTags = new HashMap<>() - Map> tempTags = new HashMap<>() - - directoryFile.eachFileRecurse(FileType.FILES, { file -> - List values = new JsonSlurper().parse(file).values - Path relativePath = directoryFile.toPath().relativize(file.toPath()) - String name = FilenameUtils.removeExtension(relativePath.toString()).replace(File.separatorChar, '/' as char) - typeTags.put("minecraft:" + name, values) - }) - - boolean flatten = false - while (!flatten) { - flatten = true - - typeTags.forEach({ name, currentTags -> - List newTags = new ArrayList<>() - currentTags.forEach({ currentTag -> - if (currentTag.startsWith("#")) { - newTags.addAll(typeTags.get(currentTag.substring(1))) - flatten = false - } else { - newTags.add(currentTag) - } - }) - - tempTags.put(name, newTags) - }) - - typeTags = tempTags - tempTags = new HashMap<>() - } - - tags.put(key, typeTags) - }) - - return tags -} - -void generateTags(File targetDir, Map tagDirs) { - File tagsFile = new File(targetDir, "tags.json"); - if (checkIsCacheValid(tagsFile)) { - this.println("> Generating tags...") - - Map tagTypes = new JsonSlurper().parse(new File(getProjectDir(), "mapping/tag_types.json")) - - Map>>> allTags = - tagDirs.collectEntries({ version, dir -> - [version, getTags(dir, tagTypes.tag_types)] - }) - - Map>> mergedTags = new LinkedHashMap<>() - - allTags.forEach({ version, tags -> - tags.forEach({ type, typeTags -> { - Map> mergedTypeTags = mergedTags.computeIfAbsent(type, _ -> new HashMap<>()) - typeTags.forEach({ name, values -> - Set mergedValues = mergedTypeTags.computeIfAbsent(name, _ -> new HashSet<>()) - if (!tagTypes.supported_tag_types.contains(type)) { - return - } - - mergedValues.addAll(values) - }) - }}) - }) - - mergedTags = mergedTags.collectEntries({ type, typeTags -> - [type, typeTags.collectEntries({ name, values -> - [name, values.sort()] - }).sort()] - }) - - tagsFile.write(JsonOutput.prettyPrint(JsonOutput.toJson(mergedTags)), "UTF-8") - } -} - -tasks.register("generateMappings") { - dependsOn(downloadManifest) - - File targetDir = new File(generatedDir, "mapping") - targetDir.mkdirs() - - this.println("> Generating Minecraft data...") - - Map generated = Arrays.stream(MinecraftVersion.values()) - .dropWhile({ it < MinecraftVersion.MINECRAFT_1_13 }) - .collect(Collectors.toMap(Function.identity(), this::generateData)) - - Map blockReports = generated.collectEntries({ version, directory -> - [version, new JsonSlurper().parse(new File(directory, "reports/blocks.json"))] - }) + annotationProcessor(libs.velocity.api) + compileOnly("com.velocitypowered:velocity-proxy:3.4.0-SNAPSHOT") // From Elytrium repo + compileOnly("com.velocitypowered:velocity-native:3.4.0-SNAPSHOT") - this.generateBlockMappings(targetDir, blockReports) + compileOnly("io.netty:netty-codec:4.2.7.Final") + compileOnly("io.netty:netty-handler:4.2.7.Final") + compileOnly("it.unimi.dsi:fastutil:8.5.18") - Map registriesReports = generated - .findAll({ it.getKey() >= MinecraftVersion.MINECRAFT_1_14 }) - .collectEntries({ version, directory -> - [version, new JsonSlurper().parse(new File(directory, "reports/registries.json"))] - }) + compileOnly(project(":api")) + // TODO remove + shadowApi("net.elytrium.commons:config:1.2.3") + shadowApi("net.elytrium.commons:kyori:1.2.3") - this.generateRegistryMappings(targetDir, registriesReports) + shadowApi("net.elytrium:fastprepare:1.0.13") - Map tags = generated - .collectEntries({ version, directory -> - [version, new File(directory, "data/minecraft/tags")] - }) + shadowApi("org.bstats:bstats-velocity:3.0.0") - this.generateTags(targetDir, tags) + compileOnly("com.github.spotbugs:spotbugs-annotations:4.7.3") } -processResources.dependsOn(generateMappings) \ No newline at end of file +//license() { +// matching(includes: ["**/mcprotocollib/**"]) { +// header = rootProject.file("HEADER_MCPROTOCOLLIB.txt") +// } +// matching(includes: ["**/LoginListener.java", "**/LoginTasksQueue.java", "**/MinecraftLimitedCompressDecoder.java"]) { +// header = rootProject.file("HEADER_MIXED.txt") +// } +// +// header = rootProject.file("HEADER.txt") +//} diff --git a/plugin/mapping/legacy_blockentities_mapping.json b/plugin/mapping/legacy_blockentities_mapping.json deleted file mode 100644 index c5d3ea3f..00000000 --- a/plugin/mapping/legacy_blockentities_mapping.json +++ /dev/null @@ -1,116 +0,0 @@ -{ - "minecraft:banner": { - "legacy": "18" - }, - "minecraft:barrel": { - "legacy": "25" - }, - "minecraft:beacon": { - "legacy": "13" - }, - "minecraft:bed": { - "legacy": "23" - }, - "minecraft:beehive": { - "legacy": "32" - }, - "minecraft:bell": { - "legacy": "29" - }, - "minecraft:blast_furnace": { - "legacy": "27" - }, - "minecraft:brewing_stand": { - "legacy": "10" - }, - "minecraft:campfire": { - "legacy": "31" - }, - "minecraft:chest": { - "legacy": "1" - }, - "minecraft:chiseled_bookshelf": { - "legacy": "36" - }, - "minecraft:command_block": { - "legacy": "21" - }, - "minecraft:comparator": { - "legacy": "17" - }, - "minecraft:conduit": { - "legacy": "24" - }, - "minecraft:daylight_detector": { - "legacy": "15" - }, - "minecraft:dispenser": { - "legacy": "5" - }, - "minecraft:dropper": { - "legacy": "6" - }, - "minecraft:enchanting_table": { - "legacy": "11" - }, - "minecraft:end_gateway": { - "legacy": "20" - }, - "minecraft:end_portal": { - "legacy": "12" - }, - "minecraft:ender_chest": { - "legacy": "3" - }, - "minecraft:furnace": { - "legacy": "0" - }, - "minecraft:hanging_sign": { - "legacy": "7" - }, - "minecraft:hopper": { - "legacy": "16" - }, - "minecraft:jigsaw": { - "legacy": "30" - }, - "minecraft:jukebox": { - "legacy": "4" - }, - "minecraft:lectern": { - "legacy": "28" - }, - "minecraft:mob_spawner": { - "legacy": "8" - }, - "minecraft:piston": { - "legacy": "9" - }, - "minecraft:sculk_catalyst": { - "legacy": "34" - }, - "minecraft:sculk_sensor": { - "legacy": "33" - }, - "minecraft:sculk_shrieker": { - "legacy": "35" - }, - "minecraft:shulker_box": { - "legacy": "22" - }, - "minecraft:sign": { - "legacy": "7" - }, - "minecraft:skull": { - "legacy": "14" - }, - "minecraft:smoker": { - "legacy": "26" - }, - "minecraft:structure_block": { - "legacy": "19" - }, - "minecraft:trapped_chest": { - "legacy": "2" - } -} \ No newline at end of file diff --git a/plugin/mapping/fallbackdata.json b/plugin/mappings/fallback_data.json old mode 100755 new mode 100644 similarity index 88% rename from plugin/mapping/fallbackdata.json rename to plugin/mappings/fallback_data.json index 6748259d..59a7548e --- a/plugin/mapping/fallbackdata.json +++ b/plugin/mappings/fallback_data.json @@ -1,4 +1,68 @@ { + "MINECRAFT_1_21_7": { + "minecraft:acacia_shelf": "minecraft:acacia_planks", + "minecraft:bamboo_shelf": "minecraft:bamboo_planks", + "minecraft:birch_shelf": "minecraft:birch_planks", + "minecraft:cherry_shelf": "minecraft:cherry_planks", + "minecraft:crimson_shelf": "minecraft:crimson_planks", + "minecraft:dark_oak_shelf": "minecraft:dark_oak_planks", + "minecraft:jungle_shelf": "minecraft:jungle_planks", + "minecraft:mangrove_shelf": "minecraft:mangrove_planks", + "minecraft:oak_shelf": "minecraft:oak_planks", + "minecraft:pale_oak_shelf": "minecraft:oak_planks", + "minecraft:spruce_shelf": "minecraft:spruce_planks", + "minecraft:warped_shelf": "minecraft:warped_planks", + "minecraft:copper_chest": "minecraft:chest", + "minecraft:exposed_copper_chest": "minecraft:chest", + "minecraft:weathered_copper_chest": "minecraft:chest", + "minecraft:oxidized_copper_chest": "minecraft:chest", + "minecraft:waxed_copper_chest": "minecraft:chest", + "minecraft:waxed_exposed_copper_chest": "minecraft:chest", + "minecraft:waxed_weathered_copper_chest": "minecraft:chest", + "minecraft:waxed_oxidized_copper_chest": "minecraft:chest", + "minecraft:copper_golem_statue": "minecraft:copper_block", + "minecraft:exposed_copper_golem_statue": "minecraft:copper_block", + "minecraft:weathered_copper_golem_statue": "minecraft:copper_block", + "minecraft:oxidized_copper_golem_statue": "minecraft:copper_block", + "minecraft:waxed_copper_golem_statue": "minecraft:copper_block", + "minecraft:waxed_exposed_copper_golem_statue": "minecraft:copper_block", + "minecraft:waxed_weathered_copper_golem_statue": "minecraft:copper_block", + "minecraft:waxed_oxidized_copper_golem_statue": "minecraft:copper_block", + "minecraft:exposed_lightning_rod": "minecraft:lightning_rod", + "minecraft:weathered_lightning_rod": "minecraft:lightning_rod", + "minecraft:oxidized_lightning_rod": "minecraft:lightning_rod", + "minecraft:waxed_lightning_rod": "minecraft:lightning_rod", + "minecraft:waxed_exposed_lightning_rod": "minecraft:lightning_rod", + "minecraft:waxed_weathered_lightning_rod": "minecraft:lightning_rod", + "minecraft:waxed_oxidized_lightning_rod": "minecraft:lightning_rod", + "minecraft:copper_torch": "minecraft:soul_torch", + "minecraft:copper_wall_torch": "minecraft:soul_wall_torch", + "minecraft:copper_bars": "minecraft:iron_bars", + "minecraft:exposed_copper_bars": "minecraft:iron_bars", + "minecraft:weathered_copper_bars": "minecraft:iron_bars", + "minecraft:oxidized_copper_bars": "minecraft:iron_bars", + "minecraft:waxed_copper_bars": "minecraft:iron_bars", + "minecraft:waxed_exposed_copper_bars": "minecraft:iron_bars", + "minecraft:waxed_weathered_copper_bars": "minecraft:iron_bars", + "minecraft:waxed_oxidized_copper_bars": "minecraft:iron_bars", + "minecraft:copper_chain": "minecraft:chain", + "minecraft:exposed_copper_chain": "minecraft:chain", + "minecraft:weathered_copper_chain": "minecraft:chain", + "minecraft:oxidized_copper_chain": "minecraft:chain", + "minecraft:waxed_copper_chain": "minecraft:chain", + "minecraft:waxed_exposed_copper_chain": "minecraft:chain", + "minecraft:waxed_weathered_copper_chain": "minecraft:chain", + "minecraft:waxed_oxidized_copper_chain": "minecraft:chain", + "minecraft:copper_lantern": "minecraft:soul_lantern", + "minecraft:exposed_copper_lantern": "minecraft:soul_lantern", + "minecraft:weathered_copper_lantern": "minecraft:soul_lantern", + "minecraft:oxidized_copper_lantern": "minecraft:soul_lantern", + "minecraft:waxed_copper_lantern": "minecraft:soul_lantern", + "minecraft:waxed_exposed_copper_lantern": "minecraft:soul_lantern", + "minecraft:waxed_weathered_copper_lantern": "minecraft:soul_lantern", + "minecraft:waxed_oxidized_copper_lantern": "minecraft:soul_lantern", + "minecraft:iron_chain": "minecraft:chain" + }, "MINECRAFT_1_21_5": { "minecraft:dried_ghast": "minecraft:chorus_plant" }, @@ -454,7 +518,7 @@ "minecraft:bee_nest": "minecraft:birch_planks", "minecraft:beehive": "minecraft:birch_planks", "minecraft:honeycomb_block": "minecraft:yellow_glazed_terracotta", - "minecraft:honey_block": "minecraft:yellow_stained_glass" + "minecraft:honey_block": "minecraft:yellow_stained_glass" }, "MINECRAFT_1_13_2": { "minecraft:lily_of_the_valley": "minecraft:white_tulip", diff --git a/plugin/mappings/legacy_block_entity_types_mappings.json b/plugin/mappings/legacy_block_entity_types_mappings.json new file mode 100644 index 00000000..b395147e --- /dev/null +++ b/plugin/mappings/legacy_block_entity_types_mappings.json @@ -0,0 +1,130 @@ +{ + "minecraft:banner": { + "1.11": 19, + "1.12": 19, + "1.13": 18 + }, + "minecraft:beacon": { + "1.11": 13, + "1.12": 13, + "1.13": 13 + }, + "minecraft:bed": { + "1.12": 24, + "1.13": 23 + }, + "minecraft:brewing_stand": { + "1.11": 10, + "1.12": 10, + "1.13": 10 + }, + "minecraft:chest": { + "1.11": 1, + "1.12": 1, + "1.13": 1 + }, + "minecraft:command_block": { + "1.11": 22, + "1.12": 22, + "1.13": 21 + }, + "minecraft:comparator": { + "1.11": 17, + "1.12": 17, + "1.13": 17 + }, + "minecraft:conduit": { + "1.13": 24 + }, + "minecraft:daylight_detector": { + "1.11": 15, + "1.12": 15, + "1.13": 15 + }, + "minecraft:dispenser": { + "1.11": 4, + "1.12": 4, + "1.13": 5 + }, + "minecraft:dropper": { + "1.11": 5, + "1.12": 5, + "1.13": 6 + }, + "minecraft:enchanting_table": { + "1.11": 11, + "1.12": 11, + "1.13": 11 + }, + "minecraft:end_gateway": { + "1.11": 21, + "1.12": 21, + "1.13": 20 + }, + "minecraft:end_portal": { + "1.11": 12, + "1.12": 12, + "1.13": 12 + }, + "minecraft:ender_chest": { + "1.11": 2, + "1.12": 2, + "1.13": 3 + }, + "minecraft:flower_pot": { + "1.11": 18, + "1.12": 18 + }, + "minecraft:furnace": { + "1.11": 0, + "1.12": 0, + "1.13": 0 + }, + "minecraft:hopper": { + "1.11": 16, + "1.12": 16, + "1.13": 16 + }, + "minecraft:jukebox": { + "1.11": 3, + "1.12": 3, + "1.13": 4 + }, + "minecraft:mob_spawner": { + "1.11": 7, + "1.12": 7, + "1.13": 8 + }, + "minecraft:noteblock": { + "1.11": 8, + "1.12": 8 + }, + "minecraft:piston": { + "1.11": 9, + "1.12": 9, + "1.13": 9 + }, + "minecraft:shulker_box": { + "1.11": 23, + "1.12": 23, + "1.13": 22 + }, + "minecraft:sign": { + "1.11": 6, + "1.12": 6, + "1.13": 7 + }, + "minecraft:skull": { + "1.11": 14, + "1.12": 14, + "1.13": 14 + }, + "minecraft:structure_block": { + "1.11": 20, + "1.12": 20, + "1.13": 19 + }, + "minecraft:trapped_chest": { + "1.13": 2 + } +} \ No newline at end of file diff --git a/plugin/mapping/legacyblockmapping.json b/plugin/mappings/legacy_block_mappings.json old mode 100755 new mode 100644 similarity index 100% rename from plugin/mapping/legacyblockmapping.json rename to plugin/mappings/legacy_block_mappings.json diff --git a/plugin/mapping/legacyblocks.json b/plugin/mappings/legacy_blocks.json similarity index 100% rename from plugin/mapping/legacyblocks.json rename to plugin/mappings/legacy_blocks.json diff --git a/plugin/mapping/legacy_blocks_mapping.json b/plugin/mappings/legacy_blocks_mappings.json similarity index 100% rename from plugin/mapping/legacy_blocks_mapping.json rename to plugin/mappings/legacy_blocks_mappings.json diff --git a/plugin/mapping/legacy_data_component_types_mapping.json b/plugin/mappings/legacy_data_component_types_mappings.json similarity index 100% rename from plugin/mapping/legacy_data_component_types_mapping.json rename to plugin/mappings/legacy_data_component_types_mappings.json diff --git a/plugin/mapping/legacy_items_mapping.json b/plugin/mappings/legacy_items_mappings.json similarity index 100% rename from plugin/mapping/legacy_items_mapping.json rename to plugin/mappings/legacy_items_mappings.json diff --git a/plugin/mappings/legacy_particle_types_mappings.json b/plugin/mappings/legacy_particle_types_mappings.json new file mode 100644 index 00000000..ae8f35bf --- /dev/null +++ b/plugin/mappings/legacy_particle_types_mappings.json @@ -0,0 +1,252 @@ +{ + "minecraft:ambient_entity_effect": { + "1.13": "0", + "1.13.1": "0", + "1.13.2": "0" + }, + "minecraft:angry_villager": { + "1.13": "1", + "1.13.1": "1", + "1.13.2": "1" + }, + "minecraft:barrier": { + "1.13": "2", + "1.13.1": "2", + "1.13.2": "2" + }, + "minecraft:block": { + "1.13": "3", + "1.13.1": "3", + "1.13.2": "3" + }, + "minecraft:bubble": { + "1.13": "4", + "1.13.1": "4", + "1.13.2": "4" + }, + "minecraft:cloud": { + "1.13": "5", + "1.13.1": "5", + "1.13.2": "5" + }, + "minecraft:crit": { + "1.13": "6", + "1.13.1": "6", + "1.13.2": "6" + }, + "minecraft:damage_indicator": { + "1.13": "7", + "1.13.1": "7", + "1.13.2": "7" + }, + "minecraft:dragon_breath": { + "1.13": "8", + "1.13.1": "8", + "1.13.2": "8" + }, + "minecraft:dripping_lava": { + "1.13": "9", + "1.13.1": "9", + "1.13.2": "9" + }, + "minecraft:dripping_water": { + "1.13": "10", + "1.13.1": "10", + "1.13.2": "10" + }, + "minecraft:dust": { + "1.13": "11", + "1.13.1": "11", + "1.13.2": "11" + }, + "minecraft:effect": { + "1.13": "12", + "1.13.1": "12", + "1.13.2": "12" + }, + "minecraft:elder_guardian": { + "1.13": "13", + "1.13.1": "13", + "1.13.2": "13" + }, + "minecraft:enchanted_hit": { + "1.13": "14", + "1.13.1": "14", + "1.13.2": "14" + }, + "minecraft:enchant": { + "1.13": "15", + "1.13.1": "15", + "1.13.2": "15" + }, + "minecraft:end_rod": { + "1.13": "16", + "1.13.1": "16", + "1.13.2": "16" + }, + "minecraft:entity_effect": { + "1.13": "17", + "1.13.1": "17", + "1.13.2": "17" + }, + "minecraft:explosion_emitter": { + "1.13": "18", + "1.13.1": "18", + "1.13.2": "18" + }, + "minecraft:explosion": { + "1.13": "19", + "1.13.1": "19", + "1.13.2": "19" + }, + "minecraft:falling_dust": { + "1.13": "20", + "1.13.1": "20", + "1.13.2": "20" + }, + "minecraft:firework": { + "1.13": "21", + "1.13.1": "21", + "1.13.2": "21" + }, + "minecraft:fishing": { + "1.13": "22", + "1.13.1": "22", + "1.13.2": "22" + }, + "minecraft:flame": { + "1.13": "23", + "1.13.1": "23", + "1.13.2": "23" + }, + "minecraft:happy_villager": { + "1.13": "24", + "1.13.1": "24", + "1.13.2": "24" + }, + "minecraft:heart": { + "1.13": "25", + "1.13.1": "25", + "1.13.2": "25" + }, + "minecraft:instant_effect": { + "1.13": "26", + "1.13.1": "26", + "1.13.2": "26" + }, + "minecraft:item": { + "1.13": "27", + "1.13.1": "27", + "1.13.2": "27" + }, + "minecraft:item_slime": { + "1.13": "28", + "1.13.1": "28", + "1.13.2": "28" + }, + "minecraft:item_snowball": { + "1.13": "29", + "1.13.1": "29", + "1.13.2": "29" + }, + "minecraft:large_smoke": { + "1.13": "30", + "1.13.1": "30", + "1.13.2": "30" + }, + "minecraft:lava": { + "1.13": "31", + "1.13.1": "31", + "1.13.2": "31" + }, + "minecraft:mycelium": { + "1.13": "32", + "1.13.1": "32", + "1.13.2": "32" + }, + "minecraft:note": { + "1.13": "33", + "1.13.1": "33", + "1.13.2": "33" + }, + "minecraft:poof": { + "1.13": "34", + "1.13.1": "34", + "1.13.2": "34" + }, + "minecraft:portal": { + "1.13": "35", + "1.13.1": "35", + "1.13.2": "35" + }, + "minecraft:rain": { + "1.13": "36", + "1.13.1": "36", + "1.13.2": "36" + }, + "minecraft:smoke": { + "1.13": "37", + "1.13.1": "37", + "1.13.2": "37" + }, + "minecraft:spit": { + "1.13": "38", + "1.13.1": "38", + "1.13.2": "38" + }, + "minecraft:squid_ink": { + "1.13": "39", + "1.13.1": "39", + "1.13.2": "39" + }, + "minecraft:sweep_attack": { + "1.13": "40", + "1.13.1": "40", + "1.13.2": "40" + }, + "minecraft:totem_of_undying": { + "1.13": "41", + "1.13.1": "41", + "1.13.2": "41" + }, + "minecraft:underwater": { + "1.13": "42", + "1.13.1": "42", + "1.13.2": "42" + }, + "minecraft:splash": { + "1.13": "43", + "1.13.1": "43", + "1.13.2": "43" + }, + "minecraft:witch": { + "1.13": "44", + "1.13.1": "44", + "1.13.2": "44" + }, + "minecraft:bubble_pop": { + "1.13": "45", + "1.13.1": "45", + "1.13.2": "45" + }, + "minecraft:current_down": { + "1.13": "46", + "1.13.1": "46", + "1.13.2": "46" + }, + "minecraft:bubble_column_up": { + "1.13": "47", + "1.13.1": "47", + "1.13.2": "47" + }, + "minecraft:nautilus": { + "1.13": "48", + "1.13.1": "48", + "1.13.2": "48" + }, + "minecraft:dolphin": { + "1.13": "49", + "1.13.1": "49", + "1.13.2": "49" + } +} \ No newline at end of file diff --git a/plugin/mapping/tag_types.json b/plugin/mappings/tag_types.json similarity index 100% rename from plugin/mapping/tag_types.json rename to plugin/mappings/tag_types.json diff --git a/plugin/mappings_generator.gradle b/plugin/mappings_generator.gradle new file mode 100644 index 00000000..4f7b3dbe --- /dev/null +++ b/plugin/mappings_generator.gradle @@ -0,0 +1,471 @@ +import groovy.io.FileType +import groovy.json.JsonOutput +import groovy.json.JsonSlurper +import groovy.transform.Field +import java.security.MessageDigest + +enum MinecraftVersion { + + MINECRAFT_1_7_2(4), + MINECRAFT_1_7_6(5), + MINECRAFT_1_8(47), + MINECRAFT_1_9(107), + MINECRAFT_1_9_1(108), + MINECRAFT_1_9_2(109), + MINECRAFT_1_9_4(110), + MINECRAFT_1_10(210), + MINECRAFT_1_11(315), + MINECRAFT_1_11_1(316), + MINECRAFT_1_12(335), + MINECRAFT_1_12_1(338), + MINECRAFT_1_12_2(340), + MINECRAFT_1_13(393), + MINECRAFT_1_13_1(401), + MINECRAFT_1_13_2(404), + MINECRAFT_1_14(477), + MINECRAFT_1_14_1(480), + MINECRAFT_1_14_2(485), + MINECRAFT_1_14_3(490), + MINECRAFT_1_14_4(498), + MINECRAFT_1_15(573), + MINECRAFT_1_15_1(575), + MINECRAFT_1_15_2(578), + MINECRAFT_1_16(735), + MINECRAFT_1_16_1(736), + MINECRAFT_1_16_2(751), + MINECRAFT_1_16_3(753), + MINECRAFT_1_16_4(754), + MINECRAFT_1_17(755), + MINECRAFT_1_17_1(756), + MINECRAFT_1_18(757), + MINECRAFT_1_18_2(758), + MINECRAFT_1_19(759), + MINECRAFT_1_19_1(760), + MINECRAFT_1_19_3(761), + MINECRAFT_1_19_4(762), + MINECRAFT_1_20(763), + MINECRAFT_1_20_2(764), + MINECRAFT_1_20_3(765), + MINECRAFT_1_20_5(766), + MINECRAFT_1_21(767), + MINECRAFT_1_21_2(768), + MINECRAFT_1_21_4(769), + MINECRAFT_1_21_5(770), + MINECRAFT_1_21_6(771), + MINECRAFT_1_21_7(772), + MINECRAFT_1_21_9(773) + + public static final List BLOCK_ENTITY_VERSIONS = List.of( + MINECRAFT_1_13, + MINECRAFT_1_14, + MINECRAFT_1_15, + MINECRAFT_1_17, + MINECRAFT_1_19, + MINECRAFT_1_19_3, + MINECRAFT_1_19_4, + MINECRAFT_1_20, + MINECRAFT_1_20_3, + MINECRAFT_1_20_5, + MINECRAFT_1_21_2, + MINECRAFT_1_21_5, + MINECRAFT_1_21_9 + ) + + public static final List WORLD_VERSIONS = List.of( + MINECRAFT_1_13, + MINECRAFT_1_13_2, + MINECRAFT_1_14, + MINECRAFT_1_15, + MINECRAFT_1_16, + MINECRAFT_1_16_2, + MINECRAFT_1_17, + MINECRAFT_1_19, + MINECRAFT_1_19_3, + MINECRAFT_1_19_4, + MINECRAFT_1_20, + MINECRAFT_1_20_3, + MINECRAFT_1_20_5, + MINECRAFT_1_21, + MINECRAFT_1_21_2, + MINECRAFT_1_21_4, + MINECRAFT_1_21_5, + MINECRAFT_1_21_6, + MINECRAFT_1_21_7, + MINECRAFT_1_21_9 + ) + + public static final MinecraftVersion MINIMUM_VERSION = MINECRAFT_1_7_2 + public static final MinecraftVersion MAXIMUM_VERSION = values()[values().length - 1] + + static MinecraftVersion fromVersionName(String name) { + return name.equalsIgnoreCase("legacy") ? MINIMUM_VERSION : valueOf("MINECRAFT_" + name.replace('.', '_')) + } + + final int protocolVersion + + MinecraftVersion(int protocolVersion) { + this.protocolVersion = protocolVersion + } + + String getVersionName() { + return this.toString().substring(10).replace('_', '.') + } +} + +ext() { + File buildDirectory = this.layout.getBuildDirectory().get().getAsFile() + dataDirectory = new File(buildDirectory, "minecraft") + generatedDir = new File(buildDirectory, "generated/minecraft") + versionManifestFile = new File(dataDirectory, "manifest.json") +} + +sourceSets.main.resources.srcDirs(generatedDir) + +tasks.register("downloadManifest") { + this.println("> Downloading version manifest...") + versionManifestFile.getParentFile().mkdirs() + if (checkIsCacheValid(versionManifestFile)) { + new URI(manifestUrl).toURL().openStream().transferTo(new FileOutputStream(versionManifestFile)) + } +} + +boolean checkIsCacheValid(File file) { + if (file.exists() && System.currentTimeMillis() - file.lastModified() < Long.parseLong(cacheValidMillis)) { + println("> Found cached " + file.getName()) + return false + } + + return true +} + +@Field static final JsonSlurper SLURPER = new JsonSlurper() + +File downloadVersionManifest(String version) { + this.println("> Downloading ${version} manifest...") + + File output = new File(dataDirectory, "${version}/manifest.json") + output.getParentFile().mkdirs() + for (Map versionInfo : (List>) SLURPER.parse(versionManifestFile, "UTF-8")["versions"]) { + if (versionInfo["id"] == version) { + new URI(versionInfo["url"]).toURL().openStream().transferTo(new FileOutputStream(output)) + return output + } + } + + throw new IllegalArgumentException(version) +} + +@SuppressWarnings("GrMethodMayBeStatic") +File getGeneratedCache(MinecraftVersion version) { + File generated = new File(dataDirectory, "${version.getVersionName()}/generated") + return new File(generated, "reports/blocks.json").exists() + && new File(generated, "reports/${version >= MinecraftVersion.MINECRAFT_1_14 ? "registries" : "items"}.json").exists() + && new File(generated, "data/minecraft/tags").exists() + ? generated : null +} + +static boolean validateServer(File file, String expected) { + if (file == null || !file.exists()) { + return false + } + + MessageDigest messageDigest = MessageDigest.getInstance("SHA1") + file.eachByte(1024 * 1024, (buf, bytesRead) -> messageDigest.update(buf, 0, bytesRead)) + return new BigInteger(1, messageDigest.digest()).toString(16).padLeft(40, '0') == expected +} + +File getServerJar(String version) { + File jarFile = new File(dataDirectory, "${version}/server.jar") + Map server = (Map) SLURPER.parse(this.downloadVersionManifest(version), "UTF-8")["downloads"]["server"] + if (!validateServer(jarFile, server["sha1"])) { + this.println("> Downloading ${version} server...") + jarFile.getParentFile().mkdirs() + new URI(server["url"]).toURL().openStream().transferTo(new FileOutputStream(jarFile)) + } + + return jarFile +} + +interface InjectedExecOps { + + @javax.inject.Inject + ExecOperations getExecOps() +} + +@SuppressWarnings("GrDeprecatedAPIUsage") +File generateData(MinecraftVersion version) { + File cache = this.getGeneratedCache(version) + if (cache != null) { + return cache + } + + File jarFile = this.getServerJar(version.getVersionName()) + File targetDir = new File(jarFile.getParentFile(), "generated") + + targetDir.deleteDir() + + this.project.getObjects().newInstance(InjectedExecOps).getExecOps().javaexec() { // У тебя батя хуесос, сынок. + classpath(jarFile) + if (version >= MinecraftVersion.MINECRAFT_1_18) { + jvmArgs("-DbundlerMainClass=net.minecraft.data.Main") + setMainClass("net.minecraft.bundler.Main") + } else { + setMainClass("net.minecraft.data.Main") + } + + args("--reports", "--server") + workingDir(jarFile.getParentFile()) + } + + return targetDir +} + +static Map> getDefaultProperties(Map> data) { + Map> defaultProperties = new TreeMap<>(Comparator.naturalOrder()) + data.forEach((key, block) -> { + if (!block.containsKey("properties")) { + return + } + + for (Map blockState : (List>) block["states"]) { + if (blockState["default"]) { + defaultProperties[key] = (Map) blockState["properties"] + break + } + } + }) + return defaultProperties +} + +static Map> loadFallbackMappings(File file) { + Map> fallbackMappings = (Map>) SLURPER.parse(file, "UTF-8") + return MinecraftVersion.values().collectEntries(version -> [version, fallbackMappings.getOrDefault(version.toString(), Collections.emptyMap())]) +} + +static Map> loadLegacyMappings(File file) { + Map> legacyMappings = (Map>) SLURPER.parse(file, "UTF-8") + return legacyMappings.collectEntries((version, mappings) -> [MinecraftVersion.valueOf(version), mappings.collectEntries((block, id) -> [block, Integer.parseInt(id)])]) +} + +static int getBlockId(String block, MinecraftVersion version, + Map> mappings, Map>> properties, Map> fallback) { + Map> defaultProperties = version >= MinecraftVersion.MINECRAFT_1_13 ? properties[version] : properties[MinecraftVersion.MINECRAFT_1_18_2] + + String[] split = block.split("\\[") + String noArgBlock = split[0] + + MinecraftVersion fallbackVersion = MinecraftVersion.MAXIMUM_VERSION + while (fallbackVersion != version) { + --fallbackVersion + noArgBlock = fallback[fallbackVersion].getOrDefault(noArgBlock, noArgBlock) + } + + String targetBlockId = noArgBlock + Map blockProperties = defaultProperties[noArgBlock] + if (blockProperties != null) { + Map currentProperties = new TreeMap<>(Comparator.naturalOrder()) + currentProperties.putAll(blockProperties) + if (split.length > 1) { + for (String argument in split[1].split(",")) { + String[] parts = argument.replace("]", "").split("=") + if (currentProperties.containsKey(parts[0])) { + currentProperties[parts[0]] = parts[1] + } + } + } + + targetBlockId += Arrays.toString(currentProperties.collect((key, value) -> key + "=" + value).toArray()).replace(" ", "") + } + + Integer id = mappings[version][targetBlockId] + if (id == null && blockProperties != null) { + id = mappings[version][noArgBlock + Arrays.toString(new TreeMap<>(blockProperties).collect((key, value) -> key + "=" + value).toArray()).replace(" ", "")] + } + + if (id == null) { + System.err.println("No ${version.getVersionName()} fallback data for ${noArgBlock}, replacing with minecraft:stone") + id = 1 + } + + return id +} + +static Map getBlockMappings(Map>>> data, Map> defaultPropertiesMap) { + Map mappings = new TreeMap<>(Comparator.naturalOrder()) + data.forEach((blockId, blockData) -> { + for (Map blockState : blockData.states) { + Map stateProperties = (Map) blockState["properties"] + if (stateProperties) { + Map properties = new TreeMap<>(defaultPropertiesMap.getOrDefault(blockId, Collections.emptyMap())) + properties.putAll(stateProperties) + mappings[blockId + Arrays.toString(properties.collect((key, value) -> key + "=" + value).toArray()).replace(" ", "")] = (Integer) blockState["id"] + } else { + mappings[blockId] = (Integer) blockState["id"] + } + } + }) + return mappings +} + +void generateBlockMappings(File targetDir, Map>>>> blockReports) { + File defaultBlockPropertiesFile = new File(targetDir, "default_block_properties.json") + File blockStatesFile = new File(targetDir, "block_states.json") + File blockStatesMappingsFile = new File(targetDir, "block_states_mappings.json") + File legacyBlocksFile = new File(targetDir, "legacy_blocks.json") + if (checkIsCacheValid(defaultBlockPropertiesFile) || checkIsCacheValid(blockStatesFile) || checkIsCacheValid(blockStatesMappingsFile) || checkIsCacheValid(legacyBlocksFile)) { + this.println("> Generating default block properties...") + Map>> defaultProperties = blockReports.collectEntries((version, report) -> [version, getDefaultProperties(report)]) + defaultBlockPropertiesFile.write(JsonOutput.toJson(defaultProperties[MinecraftVersion.MAXIMUM_VERSION]), "UTF-8") + + this.println("> Generating block_states...") + Map> mappings = loadLegacyMappings(new File(this.getProjectDir(), "mappings/legacy_block_mappings.json")) + blockReports.forEach((version, report) -> mappings.put(version, getBlockMappings(report, defaultProperties[version]))) + Map blocks = mappings[MinecraftVersion.MAXIMUM_VERSION] + blockStatesFile.write(JsonOutput.toJson(blocks), "UTF-8") + + this.println("> Generating block_states mappings...") + Map> fallback = loadFallbackMappings(new File(this.getProjectDir(), "mappings/fallback_data.json")) + Map> blockStateMappings = new TreeMap<>(Integer::compare) + blocks.forEach((block, modernId) -> { + Map blockMappings = new TreeMap<>(Integer::compare) + int lastId = -1 + for (MinecraftVersion version : MinecraftVersion.values()) { + int id = getBlockId(block, version, mappings, defaultProperties, fallback) + if (lastId != id) { + blockMappings[version.protocolVersion] = lastId = id + } + } + + blockStateMappings[modernId] = blockMappings + }) + blockStatesMappingsFile.write(JsonOutput.toJson(blockStateMappings), "UTF-8") + + this.println("> Generating legacy blocks...") + Map legacyData = (Map) SLURPER.parse(new File(this.getProjectDir(), "mappings/legacy_blocks.json"), "UTF-8") + legacyData.remove("0") // Remove AIR + legacyBlocksFile.write(JsonOutput.toJson(legacyData.collectEntries((legacy, modern) -> [legacy, getBlockId(modern, MinecraftVersion.MAXIMUM_VERSION, mappings, defaultProperties, fallback)])), "UTF-8") + } +} + +/** + * @param excludeLegacy Entries that isn't present in the latest version will be excluded (e.g. minecraft:grass_path will be excluded because it exists only on 1.14-1.16.2) + */ +void generateRegistryMappings(String target, File targetDir, Map>>>> registryReports, boolean excludeLegacy) { + File targetMappingsFile = new File(targetDir, "${target}s_mappings.json") + if (checkIsCacheValid(targetMappingsFile)) { + this.println("> Generating ${target}s mappings...") + + Map> mappings = new TreeMap<>(Comparator.naturalOrder()) + + def legacyMappings = (Map>) SLURPER.parse(new File(this.getProjectDir(), "mappings/legacy_${target}s_mappings.json"), "UTF-8") + mappings.putAll(legacyMappings.collectEntries((key, value) -> [key, value.collectEntries((version, id) -> [MinecraftVersion.fromVersionName(version).protocolVersion, id])])) + + registryReports.forEach((version, registry) -> registry["minecraft:${target}"]["entries"].forEach( + (name, value) -> mappings.computeIfAbsent(name, _ -> new TreeMap<>(Integer::compare))[version.protocolVersion] = value["protocol_id"] + )) + + if (excludeLegacy) { + int max = Collections.max(registryReports.keySet()).protocolVersion + mappings.entrySet().removeIf(entry -> !entry.getValue().containsKey(max)) + } + + mappings.forEach((name, map) -> map.replaceAll((version, id) -> id instanceof String ? Integer.parseInt(id) : id)) + targetMappingsFile.write(JsonOutput.toJson(mappings), "UTF-8") + } +} + +void generateRegistryMappings(File targetDir, Map>>>> registryReports) { + this.generateRegistryMappings("particle_type", targetDir, registryReports, false) + + this.generateRegistryMappings("block_entity_type", targetDir, registryReports.findAll(entry -> MinecraftVersion.BLOCK_ENTITY_VERSIONS.contains(entry.getKey())), false) + + def worldVersions = registryReports.findAll(entry -> MinecraftVersion.WORLD_VERSIONS.contains(entry.getKey())) + this.generateRegistryMappings("item", targetDir, worldVersions, false) + this.generateRegistryMappings("block", targetDir, worldVersions, true) + + this.generateRegistryMappings("data_component_type", targetDir, registryReports.findAll(entry -> entry.getKey() >= MinecraftVersion.MINECRAFT_1_20_5), false) +} + +static Map>> getTags(File tagDir, Map tagTypes) { + Map>> tags = new HashMap<>() + tagTypes.forEach((directoryName, key) -> { + File directory = new File(tagDir, directoryName) + if (!directory.exists()) { + return + } + + Map> typeTags = new HashMap<>() + directory.eachFileRecurse(FileType.FILES, file -> { + String tag = directory.toPath().relativize(file.toPath()).toString() + typeTags["minecraft:" + tag.take(tag.lastIndexOf('.' as char as int)).replace(File.separatorChar, '/' as char)] = (List) SLURPER.parse(file, "UTF-8")["values"] + }) + + Map> tempTags = new HashMap<>() + boolean flatten = false + while (!flatten) { + flatten = true + typeTags.forEach((name, currentTags) -> { + List newTags = new ArrayList<>() + currentTags.forEach(currentTag -> { + if (currentTag.charAt(0) == '#' as char) { + newTags.addAll(typeTags[currentTag.substring(1)]) + flatten = false + } else { + newTags.add(currentTag) + } + }) + + tempTags[name] = newTags + }) + + typeTags = tempTags + tempTags = new HashMap<>() + } + + tags[key] = typeTags + }) + return tags +} + +void generateTags(File targetDir, Map tagDirs) { + File tagsFile = new File(targetDir, "tags.json") + if (checkIsCacheValid(tagsFile)) { + this.println("> Generating tags...") + Map> tagTypes = (Map>) SLURPER.parse(new File(this.getProjectDir(), "mappings/tag_types.json"), "UTF-8") + Map>> mergedTags = new TreeMap<>(Comparator.naturalOrder()) + tagDirs.forEach((version, dir) -> { + getTags(dir, tagTypes["tag_types"]).forEach((type, typeTags) -> { + Map> mergedTypeTags = mergedTags.computeIfAbsent(type, _ -> new TreeMap<>(Comparator.naturalOrder())) + typeTags.forEach((name, values) -> { + Set mergedValues = mergedTypeTags.computeIfAbsent(name, _ -> new TreeSet<>(Comparator.naturalOrder())) + if (type in tagTypes["supported_tag_types"]) { + mergedValues.addAll(values) + } + }) + }) + }) + + tagsFile.write(JsonOutput.toJson(mergedTags), "UTF-8") + } +} + +tasks.register("generateMappings") { + dependsOn(downloadManifest) + + File targetDir = new File(this.project.ext.generatedDir, "mappings") + targetDir.mkdirs() + + this.println("> Generating Minecraft data...") + + Map generated = MinecraftVersion.values().findAll(version -> version >= MinecraftVersion.MINECRAFT_1_13).collectEntries(version -> [version, this.generateData(version)]) + this.generateTags(targetDir, generated.collectEntries((version, directory) -> [version, new File(directory, "data/minecraft/tags")])) + this.generateBlockMappings(targetDir, generated.collectEntries((version, directory) -> [version, SLURPER.parse(new File(directory, "reports/blocks.json"), "UTF-8")])) + + // 1.13.x doesn't produce registries.json + generated.remove(MinecraftVersion.MINECRAFT_1_13) + generated.remove(MinecraftVersion.MINECRAFT_1_13_1) + generated.remove(MinecraftVersion.MINECRAFT_1_13_2) + this.generateRegistryMappings(targetDir, generated.collectEntries((version, directory) -> [version, SLURPER.parse(new File(directory, "reports/registries.json"), "UTF-8")])) +} + +processResources.dependsOn(generateMappings) diff --git a/plugin/src/main/java/net/elytrium/limboapi/LimboAPI.java b/plugin/src/main/java/net/elytrium/limboapi/LimboAPI.java index 9df390e9..5b8a7a88 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/LimboAPI.java +++ b/plugin/src/main/java/net/elytrium/limboapi/LimboAPI.java @@ -31,6 +31,7 @@ import com.velocitypowered.natives.compression.VelocityCompressor; import com.velocitypowered.natives.util.Natives; import com.velocitypowered.proxy.VelocityServer; +import com.velocitypowered.proxy.config.VelocityConfiguration; import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; @@ -56,7 +57,6 @@ import java.util.Objects; import java.util.Set; import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import net.elytrium.commons.config.YamlConfig; import net.elytrium.commons.kyori.serialization.Serializer; @@ -67,23 +67,24 @@ import net.elytrium.fastprepare.handler.PreparedPacketEncoder; import net.elytrium.limboapi.api.Limbo; import net.elytrium.limboapi.api.LimboFactory; -import net.elytrium.limboapi.api.chunk.BuiltInBiome; -import net.elytrium.limboapi.api.chunk.Dimension; -import net.elytrium.limboapi.api.chunk.VirtualBiome; -import net.elytrium.limboapi.api.chunk.VirtualBlock; -import net.elytrium.limboapi.api.chunk.VirtualBlockEntity; -import net.elytrium.limboapi.api.chunk.VirtualChunk; -import net.elytrium.limboapi.api.chunk.VirtualWorld; -import net.elytrium.limboapi.api.file.BuiltInWorldFileType; -import net.elytrium.limboapi.api.file.WorldFile; -import net.elytrium.limboapi.api.material.Block; -import net.elytrium.limboapi.api.material.Item; -import net.elytrium.limboapi.api.material.VirtualItem; +import net.elytrium.limboapi.api.world.WorldVersion; +import net.elytrium.limboapi.api.world.chunk.biome.BuiltInBiome; +import net.elytrium.limboapi.api.world.chunk.Dimension; +import net.elytrium.limboapi.api.world.chunk.biome.VirtualBiome; +import net.elytrium.limboapi.api.world.chunk.block.VirtualBlock; +import net.elytrium.limboapi.api.world.chunk.blockentity.VirtualBlockEntity; +import net.elytrium.limboapi.api.world.chunk.VirtualChunk; +import net.elytrium.limboapi.api.world.VirtualWorld; +import net.elytrium.limboapi.api.world.BuiltInWorldFileType; +import net.elytrium.limboapi.api.world.WorldFile; +import net.elytrium.limboapi.api.world.chunk.block.Block; +import net.elytrium.limboapi.api.world.item.Item; +import net.elytrium.limboapi.api.world.item.VirtualItem; import net.elytrium.limboapi.api.protocol.PreparedPacket; -import net.elytrium.limboapi.api.protocol.item.ItemComponentMap; -import net.elytrium.limboapi.api.protocol.packets.PacketFactory; -import net.elytrium.limboapi.file.WorldFileTypeRegistry; -import net.elytrium.limboapi.injection.disconnect.DisconnectListener; +import net.elytrium.limboapi.api.world.item.datacomponent.DataComponentMap; +import net.elytrium.limboapi.api.protocol.PacketFactory; +import net.elytrium.limboapi.file.WorldFileRegistry; +import net.elytrium.limboapi.listener.DisconnectListener; import net.elytrium.limboapi.injection.event.EventManagerHook; import net.elytrium.limboapi.injection.login.LoginListener; import net.elytrium.limboapi.injection.login.LoginTasksQueue; @@ -94,20 +95,20 @@ import net.elytrium.limboapi.injection.packet.PreparedPacketImpl; import net.elytrium.limboapi.injection.packet.RemovePlayerInfoHook; import net.elytrium.limboapi.injection.packet.UpsertPlayerInfoHook; -import net.elytrium.limboapi.material.Biome; +import net.elytrium.limboapi.listener.ReloadListener; +import net.elytrium.limboapi.server.item.DataComponentRegistry; +import net.elytrium.limboapi.server.world.Biome; import net.elytrium.limboapi.protocol.LimboProtocol; import net.elytrium.limboapi.protocol.packets.PacketFactoryImpl; import net.elytrium.limboapi.server.CachedPackets; import net.elytrium.limboapi.server.LimboImpl; -import net.elytrium.limboapi.server.item.SimpleItemComponentManager; -import net.elytrium.limboapi.server.item.SimpleItemComponentMap; +import net.elytrium.limboapi.server.item.SimpleDataComponentMap; import net.elytrium.limboapi.server.world.SimpleBlock; import net.elytrium.limboapi.server.world.SimpleBlockEntity; import net.elytrium.limboapi.server.world.SimpleItem; -import net.elytrium.limboapi.server.world.SimpleTagManager; import net.elytrium.limboapi.server.world.SimpleWorld; import net.elytrium.limboapi.server.world.chunk.SimpleChunk; -import net.elytrium.limboapi.utils.ReloadListener; +import net.elytrium.limboapi.utils.LibLoader; import net.kyori.adventure.nbt.CompoundBinaryTag; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.ComponentSerializer; @@ -118,7 +119,7 @@ @Plugin( id = "limboapi", name = "LimboAPI", - version = BuildConstants.LIMBO_VERSION, + version = "BuildConstants.LIMBO_VERSION", // TODO description = "Velocity plugin for making virtual servers.", url = "https://elytrium.net/", authors = { @@ -126,27 +127,30 @@ } ) @SuppressFBWarnings("MS_EXPOSE_REP") -public class LimboAPI implements LimboFactory { +public class LimboAPI implements LimboFactory { // TODO replace every map with fastutil - private static final int SUPPORTED_MAXIMUM_PROTOCOL_VERSION_NUMBER = 772; + private static final int SUPPORTED_MAXIMUM_PROTOCOL_VERSION_NUMBER = 773; + + // TODO translate + /** + * UUID на backend сервере может отличаться от того что хранится на клиенте, эта карта используется для обмана клиента подставляя ему тот uuid который клиент ожидает + */ + private static final Map CLIENT_UUIDS = new HashMap<>(); @MonotonicNonNull private static Logger LOGGER; @MonotonicNonNull private static Serializer SERIALIZER; - public static final ConcurrentHashMap INITIAL_ID = new ConcurrentHashMap<>(); - private final VelocityServer server; private final Metrics.Factory metricsFactory; private final File configFile; private final Set players; private final CachedPackets packets; private final PacketFactory packetFactory; - private final SimpleItemComponentManager itemComponentManager = new SimpleItemComponentManager(); - private final HashMap loginQueue; - private final HashMap> kickCallback; - private final HashMap nextServer; + private final Map loginQueue; + private final Map> kickCallback; + private final Map nextServer; private PreparedPacketFactory preparedPacketFactory; private PreparedPacketFactory configPreparedPacketFactory; @@ -161,6 +165,7 @@ public class LimboAPI implements LimboFactory { @Inject public LimboAPI(Logger logger, ProxyServer server, Metrics.Factory metricsFactory, @DataDirectory Path dataDirectory) { setLogger(logger); + LibLoader.resolveAndLoad(this, server, new String[] {"it/unimi/dsi/fastutil/8.5.18/fastutil-8.5.18.jar"}); this.server = (VelocityServer) server; this.metricsFactory = metricsFactory; @@ -178,25 +183,23 @@ public LimboAPI(Logger logger, ProxyServer server, Metrics.Factory metricsFactor this.server.shutdown(); return; } else if (maximumProtocolVersionNumber != SUPPORTED_MAXIMUM_PROTOCOL_VERSION_NUMBER) { - LOGGER.warn("Current LimboAPI version doesn't support current Velocity version (protocol version numbers: supported - {}, velocity - {})", - SUPPORTED_MAXIMUM_PROTOCOL_VERSION_NUMBER, maximumProtocolVersionNumber); + LOGGER.warn( + "Current LimboAPI version doesn't support current Velocity version (protocol version numbers: supported - {}, velocity - {})", + SUPPORTED_MAXIMUM_PROTOCOL_VERSION_NUMBER, maximumProtocolVersionNumber + ); LOGGER.warn("Please update LimboAPI (https://github.com/Elytrium/LimboAPI/releases). LimboAPI support: https://ely.su/discord"); } LOGGER.info("Initializing Simple Virtual World system..."); - SimpleBlock.init(); - SimpleBlockEntity.init(); - SimpleItem.init(); - SimpleTagManager.init(); LOGGER.info("Hooking into PlayerList/UpsertPlayerInfo and StateRegistry..."); try { - LegacyPlayerListItemHook.init(this, LimboProtocol.PLAY_CLIENTBOUND_REGISTRY); - UpsertPlayerInfoHook.init(this, LimboProtocol.PLAY_CLIENTBOUND_REGISTRY); - RemovePlayerInfoHook.init(this, LimboProtocol.PLAY_CLIENTBOUND_REGISTRY); - + LegacyPlayerListItemHook.init(LimboProtocol.PLAY_CLIENTBOUND_REGISTRY); + UpsertPlayerInfoHook.init(LimboProtocol.PLAY_CLIENTBOUND_REGISTRY); + RemovePlayerInfoHook.init(LimboProtocol.PLAY_CLIENTBOUND_REGISTRY); LimboProtocol.init(); - } catch (Throwable e) { - throw new ReflectionException(e); + DataComponentRegistry.getType(0, ProtocolVersion.MAXIMUM_VERSION); // Call + } catch (Throwable t) { + throw new ReflectionException(t); } } @@ -207,7 +210,7 @@ public void onProxyInitialization(ProxyInitializeEvent event) { if (Settings.IMP.reload(this.configFile, Settings.IMP.PREFIX) == YamlConfig.LoadResult.CONFIG_NOT_EXISTS) { LOGGER.warn("************* FIRST LAUNCH *************"); LOGGER.warn("Thanks for installing LimboAPI!"); - LOGGER.warn("(C) 2021 - 2024 Elytrium"); + LOGGER.warn("(C) 2021-2025 Elytrium"); LOGGER.warn(""); LOGGER.warn("Check out our plugins here: https://ely.su/github <3"); LOGGER.warn("Discord: https://ely.su/discord"); @@ -262,17 +265,21 @@ public void onProxyInitialization(ProxyInitializeEvent event) { this.metricsFactory.make(this, 12530); if (Settings.IMP.MAIN.CHECK_FOR_UPDATES) { - if (!UpdatesChecker.checkVersionByURL("https://raw.githubusercontent.com/Elytrium/LimboAPI/master/VERSION", Settings.IMP.VERSION)) { - LOGGER.error("****************************************"); - LOGGER.warn("The new LimboAPI update was found, please update."); - LOGGER.error("https://github.com/Elytrium/LimboAPI/releases/"); - LOGGER.error("****************************************"); + try { + if (!UpdatesChecker.checkVersionByURL("https://raw.githubusercontent.com/Elytrium/LimboAPI/master/VERSION", Settings.IMP.VERSION)) { + LOGGER.error("****************************************"); + LOGGER.warn("The new LimboAPI update was found, please update."); + LOGGER.error("https://github.com/Elytrium/LimboAPI/releases/"); + LOGGER.error("****************************************"); + } + } catch (Exception e) { + LimboAPI.LOGGER.warn("Failed to check for updates", e); } } } @Subscribe(order = PostOrder.LAST) - public void postProxyInitialization(ProxyInitializeEvent event) throws IllegalAccessException { + public void onPostProxyInitialize(ProxyInitializeEvent event) throws Throwable { this.eventManagerHook.reloadHandlers(); } @@ -303,19 +310,12 @@ public void reload() { } private void reloadVersion() { - if (Settings.IMP.MAIN.PREPARE_MAX_VERSION.equals("LATEST")) { - this.maxVersion = ProtocolVersion.MAXIMUM_VERSION; - } else { - this.maxVersion = ProtocolVersion.valueOf("MINECRAFT_" + Settings.IMP.MAIN.PREPARE_MAX_VERSION); - } - + this.maxVersion = Settings.IMP.MAIN.PREPARE_MAX_VERSION.equals("LATEST") ? ProtocolVersion.MAXIMUM_VERSION : ProtocolVersion.valueOf("MINECRAFT_" + Settings.IMP.MAIN.PREPARE_MAX_VERSION); this.minVersion = ProtocolVersion.valueOf("MINECRAFT_" + Settings.IMP.MAIN.PREPARE_MIN_VERSION); - - if (ProtocolVersion.MAXIMUM_VERSION.compareTo(this.maxVersion) > 0 || ProtocolVersion.MINIMUM_VERSION.compareTo(this.minVersion) < 0) { + if (ProtocolVersion.MAXIMUM_VERSION.greaterThan(this.maxVersion) || ProtocolVersion.MINIMUM_VERSION.lessThan(this.minVersion)) { LOGGER.warn( - "Currently working only with " - + this.minVersion.getVersionIntroducedIn() + " - " + this.maxVersion.getMostRecentSupportedVersion() - + " versions, modify the plugins/limboapi/config.yml file if you want the plugin to work with other versions." + "Currently working only with {} - {} versions, modify the plugins/limboapi/config.yml file if you want the plugin to work with other versions", + this.minVersion.getVersionIntroducedIn(), this.maxVersion.getMostRecentSupportedVersion() ); } } @@ -324,52 +324,44 @@ public void reloadPreparedPacketFactory() { int level = this.server.getConfiguration().getCompressionLevel(); int threshold = this.server.getConfiguration().getCompressionThreshold(); this.compressionEnabled = threshold != -1; - - this.preparedPacketFactory.updateCompressor(this.compressionEnabled, level, threshold, - Settings.IMP.MAIN.SAVE_UNCOMPRESSED_PACKETS, Settings.IMP.MAIN.COMPATIBILITY_MODE); - this.configPreparedPacketFactory.updateCompressor(this.compressionEnabled, level, threshold, - Settings.IMP.MAIN.SAVE_UNCOMPRESSED_PACKETS, Settings.IMP.MAIN.COMPATIBILITY_MODE); - this.loginPreparedPacketFactory.updateCompressor(this.compressionEnabled, level, threshold, - Settings.IMP.MAIN.SAVE_UNCOMPRESSED_PACKETS, Settings.IMP.MAIN.COMPATIBILITY_MODE); + this.preparedPacketFactory.updateCompressor(this.compressionEnabled, level, threshold, Settings.IMP.MAIN.SAVE_UNCOMPRESSED_PACKETS, Settings.IMP.MAIN.COMPATIBILITY_MODE); + this.configPreparedPacketFactory.updateCompressor(this.compressionEnabled, level, threshold, Settings.IMP.MAIN.SAVE_UNCOMPRESSED_PACKETS, Settings.IMP.MAIN.COMPATIBILITY_MODE); + this.loginPreparedPacketFactory.updateCompressor(this.compressionEnabled, level, threshold, Settings.IMP.MAIN.SAVE_UNCOMPRESSED_PACKETS, Settings.IMP.MAIN.COMPATIBILITY_MODE); } @Override public VirtualBlock createSimpleBlock(Block block) { - return SimpleBlock.fromLegacyID((short) block.getID()); + return SimpleBlock.fromLegacyId((short) block.getId()); } @Override - public VirtualBlock createSimpleBlock(short legacyID) { - return SimpleBlock.fromLegacyID(legacyID); + public VirtualBlock createSimpleBlock(short legacyId) { + return SimpleBlock.fromLegacyId(legacyId); } @Override - public VirtualBlock createSimpleBlock(String modernID) { - return SimpleBlock.fromModernID(modernID); + public VirtualBlock createSimpleBlock(String modernId) { + return SimpleBlock.fromModernId(modernId); } @Override - public VirtualBlock createSimpleBlock(String modernID, Map properties) { - return SimpleBlock.fromModernID(modernID, properties); + public VirtualBlock createSimpleBlock(String modernId, Map properties) { + return SimpleBlock.fromModernId(modernId, properties); } @Override public VirtualBlock createSimpleBlock(short id, boolean modern) { - if (modern) { - return SimpleBlock.solid(id); - } else { - return SimpleBlock.fromLegacyID(id); - } + return modern ? SimpleBlock.solid(id) : SimpleBlock.fromLegacyId(id); } @Override - public VirtualBlock createSimpleBlock(boolean solid, boolean air, boolean motionBlocking, short id) { - return new SimpleBlock(solid, air, motionBlocking, id); + public VirtualBlock createSimpleBlock(short blockStateId, boolean air, boolean solid, boolean motionBlocking) { + return new SimpleBlock(blockStateId, air, solid, motionBlocking); } @Override - public VirtualBlock createSimpleBlock(boolean solid, boolean air, boolean motionBlocking, String modernID, Map properties) { - return new SimpleBlock(solid, air, motionBlocking, modernID, properties); + public VirtualBlock createSimpleBlock(String modernId, Map properties, boolean air, boolean solid, boolean motionBlocking) { + return new SimpleBlock(modernId, properties, air, solid, motionBlocking); } @Override @@ -432,8 +424,7 @@ public ByteBuf encodeSingleLoginUncompressed(MinecraftPacket packet, ProtocolVer public void inject3rdParty(Player player, MinecraftConnection connection, ChannelPipeline pipeline) { StateRegistry state = connection.getState(); - if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) < 0 - || (state != StateRegistry.CONFIG && state != StateRegistry.LOGIN)) { + if (connection.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_20_2) || (state != StateRegistry.CONFIG && state != StateRegistry.LOGIN)) { this.preparedPacketFactory.inject(player, connection, pipeline); } else { this.configPreparedPacketFactory.inject(player, connection, pipeline); @@ -445,32 +436,28 @@ public void setState(MinecraftConnection connection, StateRegistry stateRegistry this.setEncoderState(connection, stateRegistry); } - public void setActiveSessionHandler(MinecraftConnection connection, StateRegistry stateRegistry, - MinecraftSessionHandler sessionHandler) { + public void setActiveSessionHandler(MinecraftConnection connection, StateRegistry stateRegistry, MinecraftSessionHandler sessionHandler) { connection.setActiveSessionHandler(stateRegistry, sessionHandler); this.setEncoderState(connection, stateRegistry); } public void setEncoderState(MinecraftConnection connection, StateRegistry state) { // As CONFIG state was added in 1.20.2, no need to track it for lower versions - if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) < 0) { + if (connection.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_20_2)) { return; } + var pipeline = connection.getChannel().pipeline(); if (Settings.IMP.MAIN.COMPATIBILITY_MODE) { - MinecraftEncoder encoder = connection.getChannel().pipeline().get(MinecraftEncoder.class); + MinecraftEncoder encoder = pipeline.get(MinecraftEncoder.class); if (encoder != null) { encoder.setState(state); } } - PreparedPacketEncoder encoder = connection.getChannel().pipeline().get(PreparedPacketEncoder.class); + PreparedPacketEncoder encoder = pipeline.get(PreparedPacketEncoder.class); if (encoder != null) { - if (state != StateRegistry.CONFIG && state != StateRegistry.LOGIN) { - encoder.setFactory(this.preparedPacketFactory); - } else { - encoder.setFactory(this.configPreparedPacketFactory); - } + encoder.setFactory(state == StateRegistry.CONFIG || state == StateRegistry.LOGIN ? this.configPreparedPacketFactory : this.preparedPacketFactory); } } @@ -479,17 +466,9 @@ public void deject3rdParty(ChannelPipeline pipeline) { } public void fixDecompressor(ChannelPipeline pipeline, int threshold, boolean onLogin) { - ChannelHandler decoder; - if (onLogin && Settings.IMP.MAIN.DISCARD_COMPRESSION_ON_LOGIN) { - decoder = new MinecraftDiscardCompressDecoder(); - } else if (!onLogin && Settings.IMP.MAIN.DISCARD_COMPRESSION_AFTER_LOGIN) { - decoder = new MinecraftDiscardCompressDecoder(); - } else { - int level = this.server.getConfiguration().getCompressionLevel(); - VelocityCompressor compressor = Natives.compress.get().create(level); - decoder = new MinecraftLimitedCompressDecoder(threshold, compressor); - } - + ChannelHandler decoder = (onLogin ? Settings.IMP.MAIN.DISCARD_COMPRESSION_ON_LOGIN : Settings.IMP.MAIN.DISCARD_COMPRESSION_AFTER_LOGIN) + ? new MinecraftDiscardCompressDecoder() + : new MinecraftLimitedCompressDecoder(threshold, Natives.compress.get().create(this.server.getConfiguration().getCompressionLevel())); if (Settings.IMP.MAIN.COMPATIBILITY_MODE && pipeline.context(Connections.COMPRESSION_DECODER) != null) { pipeline.replace(Connections.COMPRESSION_DECODER, Connections.COMPRESSION_DECODER, decoder); } else { @@ -497,35 +476,38 @@ public void fixDecompressor(ChannelPipeline pipeline, int threshold, boolean onL } } - public void fixCompressor(ChannelPipeline pipeline, ProtocolVersion version) { + public void fixCompressor(ChannelPipeline pipeline) { ChannelHandler compressionHandler = pipeline.get(Connections.COMPRESSION_ENCODER); if (compressionHandler == null) { if (!Settings.IMP.MAIN.COMPATIBILITY_MODE) { pipeline.addBefore(Connections.MINECRAFT_DECODER, Connections.FRAME_ENCODER, MinecraftVarintLengthEncoder.INSTANCE); } } else { - int level = this.server.getConfiguration().getCompressionLevel(); - int compressionThreshold = this.server.getConfiguration().getCompressionThreshold(); - VelocityCompressor compressor = Natives.compress.get().create(level); + VelocityConfiguration configuration = this.server.getConfiguration(); + int compressionThreshold = configuration.getCompressionThreshold(); + VelocityCompressor compressor = null; if (!Settings.IMP.MAIN.COMPATIBILITY_MODE) { - MinecraftCompressorAndLengthEncoder encoder = new MinecraftCompressorAndLengthEncoder(compressionThreshold, compressor); pipeline.remove(compressionHandler); - pipeline.addBefore(Connections.MINECRAFT_ENCODER, Connections.COMPRESSION_ENCODER, encoder); + pipeline.addBefore( + Connections.MINECRAFT_ENCODER, Connections.COMPRESSION_ENCODER, + new MinecraftCompressorAndLengthEncoder(compressionThreshold, compressor = Natives.compress.get().create(configuration.getCompressionLevel())) + ); } if (pipeline.get(Connections.COMPRESSION_DECODER) instanceof LimboCompressDecoder) { - MinecraftCompressDecoder decoder = new MinecraftCompressDecoder(compressionThreshold, compressor); - pipeline.replace(Connections.COMPRESSION_DECODER, Connections.COMPRESSION_DECODER, decoder); - } else if (Settings.IMP.MAIN.COMPATIBILITY_MODE) { - compressor.close(); + pipeline.replace( + Connections.COMPRESSION_DECODER, Connections.COMPRESSION_DECODER, + new MinecraftCompressDecoder(compressionThreshold, compressor == null ? Natives.compress.get().create(configuration.getCompressionLevel()) : compressor) + ); } } } @Override public void passLoginLimbo(Player player) { - if (this.loginQueue.containsKey(player)) { - this.loginQueue.get(player).next(); + var queue = this.loginQueue.get(player); + if (queue != null) { + queue.next(); } } @@ -535,23 +517,38 @@ public VirtualItem getItem(Item item) { } @Override - public VirtualItem getItem(String itemID) { - return SimpleItem.fromModernID(itemID); + public VirtualItem getItem(String modernId) { + return SimpleItem.fromModernId(modernId); + } + + @Override + public VirtualItem getItem(ProtocolVersion version, short id) { + return SimpleItem.fromId(version, id); + } + + @Override + public VirtualItem getItem(WorldVersion version, short id) { + return SimpleItem.fromId(version, id); + } + + @Override + public VirtualItem getLegacyItem(short legacyId) { + return SimpleItem.fromLegacyId(legacyId); } @Override - public VirtualItem getLegacyItem(int itemLegacyID) { - return SimpleItem.fromLegacyID(itemLegacyID); + public DataComponentMap createDataComponentMap() { + return new SimpleDataComponentMap(); } @Override - public ItemComponentMap createItemComponentMap() { - return new SimpleItemComponentMap(this.itemComponentManager); + public VirtualBlockEntity getBlockEntityFromModernId(String modernId) { + return SimpleBlockEntity.fromModernId(modernId); } @Override - public VirtualBlockEntity getBlockEntity(String entityID) { - return SimpleBlockEntity.fromModernID(entityID); + public VirtualBlockEntity getBlockEntityFromLegacyId(String legacyId) { + return SimpleBlockEntity.fromLegacyId(legacyId); } @Override @@ -627,18 +624,6 @@ public RegisteredServer getNextServer(Player player) { return this.nextServer.get(player); } - public void setInitialID(Player player, UUID nextServer) { - INITIAL_ID.put(player, nextServer); - } - - public void removeInitialID(Player player) { - INITIAL_ID.remove(player); - } - - public UUID getInitialID(Player player) { - return INITIAL_ID.get(player); - } - public LoginListener getLoginListener() { return this.loginListener; } @@ -665,17 +650,39 @@ public EventManagerHook getEventManagerHook() { @Override public WorldFile openWorldFile(BuiltInWorldFileType apiType, Path file) throws IOException { - return WorldFileTypeRegistry.fromApiType(apiType, file); + return WorldFileRegistry.of(apiType, file); } @Override public WorldFile openWorldFile(BuiltInWorldFileType apiType, InputStream stream) throws IOException { - return WorldFileTypeRegistry.fromApiType(apiType, stream); + return WorldFileRegistry.of(apiType, stream); } @Override public WorldFile openWorldFile(BuiltInWorldFileType apiType, CompoundBinaryTag tag) { - return WorldFileTypeRegistry.fromApiType(apiType, tag); + return WorldFileRegistry.of(apiType, tag); + } + + public static void setClientUniqueId(Player player, UUID clientUniqueId) { + LimboAPI.CLIENT_UUIDS.put(player, clientUniqueId); + } + + public static void removeClientUniqueId(Player player) { + LimboAPI.CLIENT_UUIDS.remove(player); + } + + public static UUID getClientUniqueId(Player player) { + return LimboAPI.CLIENT_UUIDS.get(player); + } + + public static UUID getClientUniqueId(UUID serverSideUniqueId) { + for (var entry : LimboAPI.CLIENT_UUIDS.entrySet()) { + if (entry.getKey().getUniqueId().equals(serverSideUniqueId)) { + return entry.getValue(); + } + } + + return serverSideUniqueId; } private static void setLogger(Logger logger) { diff --git a/plugin/src/main/java/net/elytrium/limboapi/Settings.java b/plugin/src/main/java/net/elytrium/limboapi/Settings.java index e486d881..5d6a53e4 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/Settings.java +++ b/plugin/src/main/java/net/elytrium/limboapi/Settings.java @@ -27,7 +27,7 @@ public class Settings extends YamlConfig { public static final Settings IMP = new Settings(); @Final - public String VERSION = BuildConstants.LIMBO_VERSION; + public String VERSION = "BuildConstants.LIMBO_VERSION"; // TODO @Comment({ "Available serializers:", @@ -87,7 +87,7 @@ public static class MAIN { "1_7_2, 1_7_6, 1_8, 1_9, 1_9_1, 1_9_2, 1_9_4, 1_10, 1_11, 1_11_1, 1_12, 1_12_1, 1_12_2,", "1_13, 1_13_1, 1_13_2, 1_14, 1_14_1, 1_14_2, 1_14_3, 1_14_4, 1_15, 1_15_1, 1_15_2,", "1_16, 1_16_1, 1_16_2, 1_16_3, 1_16_4, 1_17, 1_17_1, 1_18, 1_18_2, 1_19, 1_19_1, 1_19_3,", - "1_20, 1_20_2, 1_20_3, 1_20_5, 1_21, 1_21_2, 1_21_4, 1_21_5, 1_21_6, 1_21_7, LATEST" + "1_20, 1_20_2, 1_20_3, 1_20_5, 1_21, 1_21_2, 1_21_4, 1_21_5, 1_21_6, 1_21_7, 1_21_9, LATEST" }) public String PREPARE_MIN_VERSION = "1_7_2"; public String PREPARE_MAX_VERSION = "LATEST"; diff --git a/plugin/src/main/java/net/elytrium/limboapi/file/MCEditSchematicFile.java b/plugin/src/main/java/net/elytrium/limboapi/file/MCEditSchematicFile.java index 5f7458eb..c4566b13 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/file/MCEditSchematicFile.java +++ b/plugin/src/main/java/net/elytrium/limboapi/file/MCEditSchematicFile.java @@ -18,8 +18,10 @@ package net.elytrium.limboapi.file; import net.elytrium.limboapi.api.LimboFactory; -import net.elytrium.limboapi.api.chunk.VirtualWorld; -import net.elytrium.limboapi.api.file.WorldFile; +import net.elytrium.limboapi.api.world.VirtualWorld; +import net.elytrium.limboapi.api.world.WorldFile; +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.ByteArrayBinaryTag; import net.kyori.adventure.nbt.CompoundBinaryTag; public class MCEditSchematicFile implements WorldFile { @@ -28,31 +30,27 @@ public class MCEditSchematicFile implements WorldFile { private final short height; private final short length; private final byte[] blocks; - private byte[] addBlocks = new byte[0]; + private final byte[] addBlocks; public MCEditSchematicFile(CompoundBinaryTag tag) { this.width = tag.getShort("Width"); this.height = tag.getShort("Height"); this.length = tag.getShort("Length"); this.blocks = tag.getByteArray("Blocks"); - - if (tag.keySet().contains("AddBlocks")) { - this.addBlocks = tag.getByteArray("AddBlocks"); - } + BinaryTag addBlocks = tag.get("AddBlocks"); + this.addBlocks = addBlocks == null ? new byte[0] : ((ByteArrayBinaryTag) addBlocks).value(); } @Override - public void toWorld(LimboFactory factory, VirtualWorld world, int offsetX, int offsetY, int offsetZ, int lightLevel) { - short[] blockIDs = new short[this.blocks.length]; - for (int index = 0; index < blockIDs.length; ++index) { + public void toWorld(LimboFactory factory, VirtualWorld world, int offsetX, int offsetY, int offsetZ, byte lightLevel) { + short[] blockIds = new short[this.blocks.length]; + for (int index = 0; index < blockIds.length; ++index) { if ((index >> 1) >= this.addBlocks.length) { - blockIDs[index] = (short) (this.blocks[index] & 0xFF); + blockIds[index] = (short) (this.blocks[index] & 0xFF); + } else if ((index & 1) == 0) { + blockIds[index] = (short) (((this.addBlocks[index >> 1] & 0x0F) << 8) + (this.addBlocks[index] & 0xFF)); } else { - if ((index & 1) == 0) { - blockIDs[index] = (short) (((this.addBlocks[index >> 1] & 0x0F) << 8) + (this.addBlocks[index] & 0xFF)); - } else { - blockIDs[index] = (short) (((this.addBlocks[index >> 1] & 0xF0) << 4) + (this.addBlocks[index] & 0xFF)); - } + blockIds[index] = (short) (((this.addBlocks[index >> 1] & 0xF0) << 4) + (this.addBlocks[index] & 0xFF)); } } @@ -60,7 +58,7 @@ public void toWorld(LimboFactory factory, VirtualWorld world, int offsetX, int o for (int posY = 0; posY < this.height; ++posY) { for (int posZ = 0; posZ < this.length; ++posZ) { int index = (posY * this.length + posZ) * this.width + posX; - world.setBlock(posX + offsetX, posY + offsetY, posZ + offsetZ, factory.createSimpleBlock(blockIDs[index])); + world.setBlock(posX + offsetX, posY + offsetY, posZ + offsetZ, factory.createSimpleBlock(blockIds[index])); } } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/file/StructureNbtFile.java b/plugin/src/main/java/net/elytrium/limboapi/file/StructureNbtFile.java index 8cb73f94..295e0b97 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/file/StructureNbtFile.java +++ b/plugin/src/main/java/net/elytrium/limboapi/file/StructureNbtFile.java @@ -20,12 +20,13 @@ import java.util.HashMap; import java.util.Map; import net.elytrium.limboapi.api.LimboFactory; -import net.elytrium.limboapi.api.chunk.VirtualBlock; -import net.elytrium.limboapi.api.chunk.VirtualWorld; -import net.elytrium.limboapi.api.file.WorldFile; +import net.elytrium.limboapi.api.world.chunk.block.VirtualBlock; +import net.elytrium.limboapi.api.world.VirtualWorld; +import net.elytrium.limboapi.api.world.WorldFile; import net.kyori.adventure.nbt.BinaryTag; import net.kyori.adventure.nbt.CompoundBinaryTag; import net.kyori.adventure.nbt.ListBinaryTag; +import net.kyori.adventure.nbt.StringBinaryTag; public class StructureNbtFile implements WorldFile { @@ -38,35 +39,32 @@ public StructureNbtFile(CompoundBinaryTag tag) { } @Override - public void toWorld(LimboFactory factory, VirtualWorld world, int offsetX, int offsetY, int offsetZ, int lightLevel) { + public void toWorld(LimboFactory factory, VirtualWorld world, int offsetX, int offsetY, int offsetZ, byte lightLevel) { VirtualBlock[] palettedBlocks = new VirtualBlock[this.palette.size()]; for (int i = 0; i < this.palette.size(); ++i) { CompoundBinaryTag map = this.palette.getCompound(i); - - Map propertiesMap = null; - if (map.keySet().contains("Properties")) { - propertiesMap = new HashMap<>(); - CompoundBinaryTag properties = map.getCompound("Properties"); - for (String entry : properties.keySet()) { - propertiesMap.put(entry, properties.getString(entry)); - } + BinaryTag properties = map.get("Properties"); + if (properties == null) { + palettedBlocks[i] = factory.createSimpleBlock(map.getString("Name"), null); + } else { + Map propertiesMap = new HashMap<>(); + ((CompoundBinaryTag) properties).forEach(entry -> propertiesMap.put(entry.getKey(), ((StringBinaryTag) entry.getValue()).value())); + palettedBlocks[i] = factory.createSimpleBlock(map.getString("Name"), propertiesMap); } - - palettedBlocks[i] = factory.createSimpleBlock(map.getString("Name"), propertiesMap); } for (BinaryTag binaryTag : this.blocks) { CompoundBinaryTag blockMap = (CompoundBinaryTag) binaryTag; - ListBinaryTag posTag = blockMap.getList("pos"); + ListBinaryTag pos = blockMap.getList("pos"); VirtualBlock block = palettedBlocks[blockMap.getInt("state")]; - int x = offsetX + posTag.getInt(0); - int y = offsetY + posTag.getInt(1); - int z = offsetZ + posTag.getInt(2); - world.setBlock(x, y, z, block); + int posX = offsetX + pos.getInt(0); + int posY = offsetY + pos.getInt(1); + int posZ = offsetZ + pos.getInt(2); + world.setBlock(posX, posY, posZ, block); CompoundBinaryTag blockEntityNbt = blockMap.getCompound("nbt"); if (!blockEntityNbt.keySet().isEmpty()) { - world.setBlockEntity(x, y, z, blockEntityNbt, factory.getBlockEntity(block.getModernStringID())); + world.setBlockEntity(posX, posY, posZ, blockEntityNbt, factory.getBlockEntityFromModernId(block.modernId())); } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/file/WorldEditSchemFile.java b/plugin/src/main/java/net/elytrium/limboapi/file/WorldEditSchemFile.java index 6c2fa9c6..2fca6f2d 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/file/WorldEditSchemFile.java +++ b/plugin/src/main/java/net/elytrium/limboapi/file/WorldEditSchemFile.java @@ -21,9 +21,9 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import net.elytrium.limboapi.api.LimboFactory; -import net.elytrium.limboapi.api.chunk.VirtualBlock; -import net.elytrium.limboapi.api.chunk.VirtualWorld; -import net.elytrium.limboapi.api.file.WorldFile; +import net.elytrium.limboapi.api.world.chunk.block.VirtualBlock; +import net.elytrium.limboapi.api.world.VirtualWorld; +import net.elytrium.limboapi.api.world.WorldFile; import net.kyori.adventure.nbt.BinaryTag; import net.kyori.adventure.nbt.CompoundBinaryTag; import net.kyori.adventure.nbt.IntBinaryTag; @@ -46,24 +46,23 @@ public WorldEditSchemFile(CompoundBinaryTag tag) { ByteBuf blockDataBuf = Unpooled.wrappedBuffer(tag.getByteArray("BlockData")); this.blocks = new int[this.width * this.height * this.length]; - - for (int i = 0; i < this.blocks.length; i++) { + for (int i = 0; i < this.blocks.length; ++i) { this.blocks[i] = ProtocolUtils.readVarInt(blockDataBuf); } + blockDataBuf.release(); this.blockEntities = tag.getList("BlockEntities"); } @Override - public void toWorld(LimboFactory factory, VirtualWorld world, int offsetX, int offsetY, int offsetZ, int lightLevel) { + public void toWorld(LimboFactory factory, VirtualWorld world, int offsetX, int offsetY, int offsetZ, byte lightLevel) { VirtualBlock[] palettedBlocks = new VirtualBlock[this.palette.keySet().size()]; - this.palette.forEach((entry) -> palettedBlocks[((IntBinaryTag) entry.getValue()).value()] = factory.createSimpleBlock(entry.getKey())); + this.palette.forEach(entry -> palettedBlocks[((IntBinaryTag) entry.getValue()).value()] = factory.createSimpleBlock(entry.getKey())); for (int posX = 0; posX < this.width; ++posX) { for (int posY = 0; posY < this.height; ++posY) { for (int posZ = 0; posZ < this.length; ++posZ) { - int index = (posY * this.length + posZ) * this.width + posX; - world.setBlock(posX + offsetX, posY + offsetY, posZ + offsetZ, palettedBlocks[this.blocks[index]]); + world.setBlock(offsetX + posX, offsetY + posY, offsetZ + posZ, palettedBlocks[this.blocks[(posY * this.length + posZ) * this.width + posX]]); } } } @@ -71,12 +70,7 @@ public void toWorld(LimboFactory factory, VirtualWorld world, int offsetX, int o for (BinaryTag blockEntity : this.blockEntities) { CompoundBinaryTag blockEntityData = (CompoundBinaryTag) blockEntity; int[] posTag = blockEntityData.getIntArray("Pos"); - world.setBlockEntity( - offsetX + posTag[0], - offsetY + posTag[1], - offsetZ + posTag[2], - blockEntityData, - factory.getBlockEntity(blockEntityData.getString("Id"))); + world.setBlockEntity(offsetX + posTag[0], offsetY + posTag[1], offsetZ + posTag[2], blockEntityData, factory.getBlockEntityFromModernId(blockEntityData.getString("Id"))); } world.fillSkyLight(lightLevel); diff --git a/plugin/src/main/java/net/elytrium/limboapi/file/WorldFileRegistry.java b/plugin/src/main/java/net/elytrium/limboapi/file/WorldFileRegistry.java new file mode 100644 index 00000000..c03cc0c2 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/file/WorldFileRegistry.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2021 - 2025 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limboapi.file; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import net.elytrium.limboapi.api.world.BuiltInWorldFileType; +import net.elytrium.limboapi.api.world.WorldFile; +import net.kyori.adventure.nbt.BinaryTagIO; +import net.kyori.adventure.nbt.CompoundBinaryTag; + +public interface WorldFileRegistry { + + static WorldFile of(BuiltInWorldFileType type, Path file) throws IOException { + return WorldFileRegistry.of(type, BinaryTagIO.unlimitedReader().read(file, BinaryTagIO.Compression.GZIP)); + } + + static WorldFile of(BuiltInWorldFileType type, InputStream stream) throws IOException { + return WorldFileRegistry.of(type, BinaryTagIO.unlimitedReader().read(stream, BinaryTagIO.Compression.GZIP)); + } + + static WorldFile of(BuiltInWorldFileType type, CompoundBinaryTag tag) { + return switch (type) { + case SCHEMATIC -> new MCEditSchematicFile(tag); + case WORLDEDIT_SCHEM -> new WorldEditSchemFile(tag); + case STRUCTURE -> new StructureNbtFile(tag); + }; + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/file/WorldFileTypeRegistry.java b/plugin/src/main/java/net/elytrium/limboapi/file/WorldFileTypeRegistry.java deleted file mode 100644 index caca2e4d..00000000 --- a/plugin/src/main/java/net/elytrium/limboapi/file/WorldFileTypeRegistry.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package net.elytrium.limboapi.file; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Path; -import java.util.EnumMap; -import java.util.function.Function; -import net.elytrium.limboapi.api.file.BuiltInWorldFileType; -import net.elytrium.limboapi.api.file.WorldFile; -import net.kyori.adventure.nbt.BinaryTagIO; -import net.kyori.adventure.nbt.CompoundBinaryTag; - -public enum WorldFileTypeRegistry { - SCHEMATIC(BuiltInWorldFileType.SCHEMATIC, MCEditSchematicFile::new), - WORLDEDIT_SCHEM(BuiltInWorldFileType.WORLDEDIT_SCHEM, WorldEditSchemFile::new), - STRUCTURE(BuiltInWorldFileType.STRUCTURE, StructureNbtFile::new); - - private static final EnumMap API_TYPE_MAP = new EnumMap<>(BuiltInWorldFileType.class); - private final BuiltInWorldFileType apiType; - private final Function worldFileFunction; - - static { - for (WorldFileTypeRegistry pluginType : WorldFileTypeRegistry.values()) { - API_TYPE_MAP.put(pluginType.apiType, pluginType); - } - } - - WorldFileTypeRegistry(BuiltInWorldFileType apiType, Function worldFileFunction) { - this.apiType = apiType; - this.worldFileFunction = worldFileFunction; - } - - public static WorldFileTypeRegistry fromApiType(BuiltInWorldFileType apiType) { - return API_TYPE_MAP.get(apiType); - } - - public static WorldFile fromApiType(BuiltInWorldFileType apiType, Path file) throws IOException { - return fromApiType(apiType).fromNbt(file); - } - - public static WorldFile fromApiType(BuiltInWorldFileType apiType, InputStream stream) throws IOException { - return fromApiType(apiType).fromNbt(stream); - } - - public static WorldFile fromApiType(BuiltInWorldFileType apiType, CompoundBinaryTag tag) { - return fromApiType(apiType).fromNbt(tag); - } - - public WorldFile fromNbt(Path file) throws IOException { - return this.fromNbt(BinaryTagIO.unlimitedReader().read(file, BinaryTagIO.Compression.GZIP)); - } - - public WorldFile fromNbt(InputStream stream) throws IOException { - return this.fromNbt(BinaryTagIO.unlimitedReader().read(stream, BinaryTagIO.Compression.GZIP)); - } - - public WorldFile fromNbt(CompoundBinaryTag tag) { - return this.worldFileFunction.apply(tag); - } -} diff --git a/plugin/src/main/java/net/elytrium/limboapi/injection/dummy/ClosedMinecraftConnection.java b/plugin/src/main/java/net/elytrium/limboapi/injection/dummy/ClosedMinecraftConnection.java index 6b6bbdd4..c7663908 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/injection/dummy/ClosedMinecraftConnection.java +++ b/plugin/src/main/java/net/elytrium/limboapi/injection/dummy/ClosedMinecraftConnection.java @@ -17,14 +17,14 @@ package net.elytrium.limboapi.injection.dummy; -import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.connection.MinecraftConnection; -import io.netty.channel.Channel; public class ClosedMinecraftConnection extends MinecraftConnection { - public ClosedMinecraftConnection(Channel channel, VelocityServer server) { - super(channel, server); + public static final MinecraftConnection INSTANCE = new ClosedMinecraftConnection(); + + private ClosedMinecraftConnection() { + super(new ClosedChannel(new DummyEventLoop()), null); } @Override diff --git a/plugin/src/main/java/net/elytrium/limboapi/injection/dummy/DummyEventPool.java b/plugin/src/main/java/net/elytrium/limboapi/injection/dummy/DummyEventLoop.java similarity index 97% rename from plugin/src/main/java/net/elytrium/limboapi/injection/dummy/DummyEventPool.java rename to plugin/src/main/java/net/elytrium/limboapi/injection/dummy/DummyEventLoop.java index 9888c2bb..cb57a24e 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/injection/dummy/DummyEventPool.java +++ b/plugin/src/main/java/net/elytrium/limboapi/injection/dummy/DummyEventLoop.java @@ -37,7 +37,7 @@ @SuppressWarnings("ConstantConditions") @SuppressFBWarnings(value = "NP_NONNULL_RETURN_VIOLATION", justification = "This is dummy class.") -public class DummyEventPool implements EventLoop { +public class DummyEventLoop implements EventLoop { @Override public EventLoopGroup parent() { @@ -70,7 +70,7 @@ public Future newSucceededFuture(V v) { } @Override - public Future newFailedFuture(Throwable throwable) { + public Future newFailedFuture(Throwable t) { return null; } diff --git a/plugin/src/main/java/net/elytrium/limboapi/injection/event/EventManagerHook.java b/plugin/src/main/java/net/elytrium/limboapi/injection/event/EventManagerHook.java index 4f187fe3..d7eefbab 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/injection/event/EventManagerHook.java +++ b/plugin/src/main/java/net/elytrium/limboapi/injection/event/EventManagerHook.java @@ -27,13 +27,10 @@ import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.proxy.event.VelocityEventManager; import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.reflect.Array; import java.lang.reflect.Field; -import java.lang.reflect.Method; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -42,15 +39,17 @@ import net.elytrium.commons.utils.reflection.ReflectionException; import net.elytrium.limboapi.LimboAPI; import net.elytrium.limboapi.Settings; +import net.elytrium.limboapi.utils.Reflection; @SuppressWarnings("unchecked") public class EventManagerHook { - private static final Field HANDLERS_BY_TYPE_FIELD; - private static final Class HANDLER_REGISTRATION_CLASS; - private static final MethodHandle PLUGIN_FIELD; - private static final MethodHandle FIRE_METHOD; - private static final MethodHandle FUTURE_FIELD; + private static final MethodHandle FIRE_METHOD = Reflection.findVirtualVoid( + VelocityEventManager.class, + "fire", + CompletableFuture.class, Object.class, int.class, boolean.class, Reflection.findClass("com.velocitypowered.proxy.event.VelocityEventManager$HandlerRegistration").arrayType() + ).asType(MethodType.methodType(void.class, VelocityEventManager.class, CompletableFuture.class, Object.class, int.class, boolean.class, Object.class)); + private static final MethodHandle FUTURE_FIELD = Reflection.findGetter(Reflection.findClass("com.velocitypowered.proxy.event.VelocityEventManager$ContinuationTask"), "future", CompletableFuture.class); private final Set proceededProfiles = new HashSet<>(); private final LimboAPI plugin; @@ -66,8 +65,7 @@ public EventManagerHook(LimboAPI plugin, VelocityEventManager eventManager) { @Subscribe(order = PostOrder.FIRST) public EventTask onGameProfileRequest(GameProfileRequestEvent event) { - GameProfile originalProfile = event.getGameProfile(); - if (this.proceededProfiles.remove(originalProfile)) { + if (this.proceededProfiles.remove(event.getGameProfile())) { return null; } else { CompletableFuture fireFuture = new CompletableFuture<>(); @@ -76,17 +74,17 @@ public EventTask onGameProfileRequest(GameProfileRequestEvent event) { try { this.plugin.getLoginListener().hookLoginSession(modifiedEvent); hookFuture.complete(modifiedEvent); - } catch (Throwable e) { - throw new ReflectionException(e); + } catch (Throwable t) { + throw new ReflectionException(t); } }); if (this.hasHandlerRegistration) { try { - FIRE_METHOD.invoke(this.eventManager, fireFuture, event, 0, false, this.handlerRegistrations); - } catch (Throwable e) { + EventManagerHook.FIRE_METHOD.invokeExact(this.eventManager, fireFuture, event, 0, false/*currentlyAsync, passing false to run continuation tasks in asyncExecutor*/, this.handlerRegistrations); + } catch (Throwable t) { fireFuture.complete(event); - throw new ReflectionException(e); + throw new ReflectionException(t); } } else { fireFuture.complete(event); @@ -95,12 +93,12 @@ public EventTask onGameProfileRequest(GameProfileRequestEvent event) { // ignoring other subscribers by directly completing the future return EventTask.withContinuation(continuation -> hookFuture.whenComplete((result, cause) -> { try { - CompletableFuture future = (CompletableFuture) FUTURE_FIELD.invokeExact(continuation); + CompletableFuture future = (CompletableFuture) EventManagerHook.FUTURE_FIELD.invokeExact(continuation); if (future != null) { future.complete(result); } - } catch (Throwable e) { - throw new ReflectionException(e); + } catch (Throwable t) { + throw new ReflectionException(t); } })); } @@ -114,8 +112,8 @@ public EventTask onKickedFromServer(KickedFromServerEvent event) { if (callback == null || !callback.apply(event)) { hookFuture.complete(event); } - } catch (Throwable throwable) { - LimboAPI.getLogger().error("Failed to handle KickCallback, ignoring its result", throwable); + } catch (Throwable t) { + LimboAPI.getLogger().error("Failed to handle KickCallback, ignoring its result", t); hookFuture.complete(event); } @@ -129,9 +127,12 @@ public void proceedProfile(GameProfile profile) { } @SuppressWarnings("rawtypes") - public void reloadHandlers() throws IllegalAccessException { - ListMultimap, ?> handlersMap = (ListMultimap, ?>) HANDLERS_BY_TYPE_FIELD.get(this.eventManager); - List disabledHandlers = handlersMap.get(GameProfileRequestEvent.class); + public void reloadHandlers() throws Throwable { + Field handlersByTypeField = VelocityEventManager.class.getDeclaredField("handlersByType"); + handlersByTypeField.setAccessible(true); + ListMultimap, ?> handlersByType = (ListMultimap, ?>) handlersByTypeField.get(this.eventManager); + + List disabledHandlers = handlersByType.get(GameProfileRequestEvent.class); List preEvents = new ArrayList<>(); List newHandlers = new ArrayList<>(disabledHandlers); @@ -141,22 +142,20 @@ public void reloadHandlers() throws IllegalAccessException { } } - try { - for (Object handler : disabledHandlers) { - PluginContainer pluginContainer = (PluginContainer) PLUGIN_FIELD.invoke(handler); - String id = pluginContainer.getDescription().getId(); - if (Settings.IMP.MAIN.PRE_LIMBO_PROFILE_REQUEST_PLUGINS.contains(id)) { - LimboAPI.getLogger().info("Hooking all GameProfileRequestEvent events from {} ", id); - preEvents.add(handler); - newHandlers.remove(handler); - } + Class HandlerRegistration = Reflection.findClass("com.velocitypowered.proxy.event.VelocityEventManager$HandlerRegistration"); + Field pluginField = HandlerRegistration.getDeclaredField("plugin"); + pluginField.setAccessible(true); + for (Object handler : disabledHandlers) { + String id = ((PluginContainer) pluginField.get(handler)).getDescription().getId(); + if (Settings.IMP.MAIN.PRE_LIMBO_PROFILE_REQUEST_PLUGINS.contains(id)) { + LimboAPI.getLogger().info("Hooking all GameProfileRequestEvent events from {}", id); + preEvents.add(handler); + newHandlers.remove(handler); } - } catch (Throwable e) { - throw new ReflectionException(e); } - handlersMap.replaceValues(GameProfileRequestEvent.class, newHandlers); - this.handlerRegistrations = Array.newInstance(HANDLER_REGISTRATION_CLASS, preEvents.size()); + handlersByType.replaceValues(GameProfileRequestEvent.class, newHandlers); + this.handlerRegistrations = Array.newInstance(HandlerRegistration, preEvents.size()); for (int i = 0; i < preEvents.size(); ++i) { Array.set(this.handlerRegistrations, i, preEvents.get(i)); @@ -164,38 +163,4 @@ public void reloadHandlers() throws IllegalAccessException { this.hasHandlerRegistration = !preEvents.isEmpty(); } - - static { - try { - HANDLERS_BY_TYPE_FIELD = VelocityEventManager.class.getDeclaredField("handlersByType"); - HANDLERS_BY_TYPE_FIELD.setAccessible(true); - - HANDLER_REGISTRATION_CLASS = Class.forName("com.velocitypowered.proxy.event.VelocityEventManager$HandlerRegistration"); - PLUGIN_FIELD = MethodHandles.privateLookupIn(HANDLER_REGISTRATION_CLASS, MethodHandles.lookup()) - .findGetter(HANDLER_REGISTRATION_CLASS, "plugin", PluginContainer.class); - - Class continuationTaskClass = Class.forName("com.velocitypowered.proxy.event.VelocityEventManager$ContinuationTask"); - FUTURE_FIELD = MethodHandles.privateLookupIn(continuationTaskClass, MethodHandles.lookup()) - .findGetter(continuationTaskClass, "future", CompletableFuture.class); - - // The desired 5-argument fire method is private, and its 5th argument is the array of the private class, - // so we can't pass it into the Class#getDeclaredMethod(Class...) method. - Method fireMethod = Arrays.stream(VelocityEventManager.class.getDeclaredMethods()) - .filter(method -> method.getName().equals("fire") && method.getParameterCount() == 5) - .findFirst() - .orElseThrow(); - fireMethod.setAccessible(true); - FIRE_METHOD = MethodHandles.privateLookupIn(VelocityEventManager.class, MethodHandles.lookup()) - .findVirtual(VelocityEventManager.class, "fire", MethodType.methodType( - void.class, - CompletableFuture.class, - Object.class, - int.class, - boolean.class, - Array.newInstance(HANDLER_REGISTRATION_CLASS, 0).getClass() - )); - } catch (NoSuchFieldException | ClassNotFoundException | IllegalAccessException | NoSuchMethodException e) { - throw new ReflectionException(e); - } - } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/injection/login/LoginListener.java b/plugin/src/main/java/net/elytrium/limboapi/injection/login/LoginListener.java index 17584375..16c78568 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/injection/login/LoginListener.java +++ b/plugin/src/main/java/net/elytrium/limboapi/injection/login/LoginListener.java @@ -40,16 +40,13 @@ import com.velocitypowered.api.event.player.ServerConnectedEvent; import com.velocitypowered.api.network.HandshakeIntent; import com.velocitypowered.api.network.ProtocolVersion; -import com.velocitypowered.api.proxy.InboundConnection; +import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.crypto.IdentifiedKey; -import com.velocitypowered.api.proxy.player.TabList; import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.api.util.UuidUtils; -import com.velocitypowered.natives.compression.VelocityCompressor; import com.velocitypowered.natives.util.Natives; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.config.PlayerInfoForwarding; -import com.velocitypowered.proxy.config.VelocityConfiguration; import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.client.AuthSessionHandler; import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler; @@ -63,42 +60,38 @@ import com.velocitypowered.proxy.protocol.netty.MinecraftCompressorAndLengthEncoder; import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccessPacket; import com.velocitypowered.proxy.protocol.packet.SetCompressionPacket; +import com.velocitypowered.proxy.tablist.InternalTabList; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelOutboundHandlerAdapter; import io.netty.channel.ChannelPipeline; import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; -import java.lang.reflect.Field; import java.net.InetSocketAddress; import java.util.Objects; import java.util.UUID; -import java.util.function.BiConsumer; import net.elytrium.commons.utils.reflection.ReflectionException; import net.elytrium.limboapi.LimboAPI; import net.elytrium.limboapi.Settings; import net.elytrium.limboapi.api.event.LoginLimboRegisterEvent; -import net.elytrium.limboapi.injection.dummy.ClosedChannel; import net.elytrium.limboapi.injection.dummy.ClosedMinecraftConnection; -import net.elytrium.limboapi.injection.dummy.DummyEventPool; import net.elytrium.limboapi.injection.login.confirmation.LoginConfirmHandler; import net.elytrium.limboapi.injection.packet.ServerLoginSuccessHook; import net.elytrium.limboapi.injection.tablist.RewritingKeyedVelocityTabList; import net.elytrium.limboapi.injection.tablist.RewritingVelocityTabList; import net.elytrium.limboapi.injection.tablist.RewritingVelocityTabListLegacy; -import net.elytrium.limboapi.utils.LambdaUtil; +import net.elytrium.limboapi.utils.Reflection; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; public class LoginListener { - private static final ClosedMinecraftConnection CLOSED_MINECRAFT_CONNECTION; - - private static final MethodHandle DELEGATE_FIELD; - private static final BiConsumer MC_CONNECTION_SETTER; - private static final MethodHandle CONNECTED_PLAYER_CONSTRUCTOR; - private static final MethodHandle SPAWNED_FIELD; - private static final BiConsumer TAB_LIST_SETTER; + private static final MethodHandle DELEGATE_FIELD = Reflection.findGetter(LoginInboundConnection.class, "delegate", InitialInboundConnection.class); + static final MethodHandle MC_CONNECTION_SETTER = Reflection.findSetter(AuthSessionHandler.class, "mcConnection", MinecraftConnection.class); + private static final MethodHandle CONNECTED_PLAYER_CONSTRUCTOR = Reflection.findConstructor( + ConnectedPlayer.class, + VelocityServer.class, GameProfile.class, MinecraftConnection.class, InetSocketAddress.class, String.class, boolean.class, HandshakeIntent.class, IdentifiedKey.class + ); + private static final MethodHandle TAB_LIST_SETTER = Reflection.findSetter(ConnectedPlayer.class, "tabList", InternalTabList.class); + private static final MethodHandle SPAWNED_FIELD = Reflection.findSetter(ClientPlaySessionHandler.class, "spawned", boolean.class); private final LimboAPI plugin; private final VelocityServer server; @@ -110,17 +103,18 @@ public LoginListener(LimboAPI plugin, VelocityServer server) { @Subscribe public void hookInitialServer(PlayerChooseInitialServerEvent event) { - if (this.plugin.hasNextServer(event.getPlayer())) { - event.setInitialServer(this.plugin.getNextServer(event.getPlayer())); + Player player = event.getPlayer(); + if (this.plugin.hasNextServer(player)) { + event.setInitialServer(this.plugin.getNextServer(player)); } - this.plugin.setLimboJoined(event.getPlayer()); + this.plugin.setLimboJoined(player); } @SuppressWarnings("ConstantConditions") public void hookLoginSession(GameProfileRequestEvent event) throws Throwable { LoginInboundConnection inboundConnection = (LoginInboundConnection) event.getConnection(); - // In some cases, e.g. if the player logged out or was kicked right before the GameProfileRequestEvent hook, + // In some cases, e.g., if the player logged out or was kicked right before the GameProfileRequestEvent hook, // the connection will be broken (possibly by GC) and we can't get it from the delegate field. if (LoginInboundConnection.class.isAssignableFrom(inboundConnection.getClass())) { // Changing mcConnection to the closed one. For what? To break the "initializePlayer" @@ -133,23 +127,24 @@ public void hookLoginSession(GameProfileRequestEvent event) throws Throwable { connection.eventLoop().execute(() -> { try { this.hookLoginSession(event); - } catch (Throwable e) { - throw new IllegalStateException("failed to handle login request", e); + } catch (Throwable t) { + throw new IllegalStateException("failed to handle login request", t); } }); return; } - Object handler = connection.getActiveSessionHandler(); - MC_CONNECTION_SETTER.accept(handler, CLOSED_MINECRAFT_CONNECTION); + AuthSessionHandler handler = (AuthSessionHandler) connection.getActiveSessionHandler(); + MC_CONNECTION_SETTER.invokeExact(handler, ClosedMinecraftConnection.INSTANCE); + ProtocolVersion version = connection.getProtocolVersion(); LoginConfirmHandler loginHandler = null; - if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) >= 0) { - connection.setActiveSessionHandler(StateRegistry.LOGIN, - loginHandler = new LoginConfirmHandler(this.plugin, connection)); + boolean v1_20_2 = version.noLessThan(ProtocolVersion.MINECRAFT_1_20_2); + if (v1_20_2) { + connection.setActiveSessionHandler(StateRegistry.LOGIN, loginHandler = new LoginConfirmHandler(this.plugin, connection)); } - // From Velocity. + // From Velocity if (!connection.isClosed()) { try { IdentifiedKey playerKey = inboundConnection.getIdentifiedKey(); @@ -172,40 +167,38 @@ public void hookLoginSession(GameProfileRequestEvent event) throws Throwable { event.getGameProfile(), connection, inboundConnection.getVirtualHost().orElse(null), - ((InboundConnection) inboundConnection).getRawVirtualHost().orElse(null), + inboundConnection.getRawVirtualHost().orElse(null), event.isOnlineMode(), - ((InboundConnection) inboundConnection).getHandshakeIntent(), + inboundConnection.getHandshakeIntent(), playerKey ); - if (connection.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_19_3)) { - TAB_LIST_SETTER.accept(player, new RewritingVelocityTabList(player)); - } else if (connection.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_8)) { - TAB_LIST_SETTER.accept(player, new RewritingKeyedVelocityTabList(player, this.server)); - } else { - TAB_LIST_SETTER.accept(player, new RewritingVelocityTabListLegacy(player, this.server)); - } + TAB_LIST_SETTER.invokeExact(player, (InternalTabList) (version.noLessThan(ProtocolVersion.MINECRAFT_1_19_3) + ? new RewritingVelocityTabList(player) + : version.noLessThan(ProtocolVersion.MINECRAFT_1_8) + ? new RewritingKeyedVelocityTabList(player, this.server) + : new RewritingVelocityTabListLegacy(player, this.server) + )); - if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) >= 0) { + if (v1_20_2) { loginHandler.setPlayer(player); } + if (this.server.canRegisterConnection(player)) { if (!connection.isClosed()) { - // Complete the Login process. - int threshold = this.server.getConfiguration().getCompressionThreshold(); + // Complete the Login process + var configuration = this.server.getConfiguration(); + int threshold = configuration.getCompressionThreshold(); ChannelPipeline pipeline = connection.getChannel().pipeline(); - boolean compressionEnabled = threshold >= 0 && connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0; + boolean compressionEnabled = threshold >= 0 && version.noLessThan(ProtocolVersion.MINECRAFT_1_8); if (compressionEnabled) { connection.write(new SetCompressionPacket(threshold)); this.plugin.fixDecompressor(pipeline, threshold, true); - if (!Settings.IMP.MAIN.COMPATIBILITY_MODE) { - pipeline.addFirst(Connections.COMPRESSION_ENCODER, new ChannelOutboundHandlerAdapter()); - } else { - int level = this.server.getConfiguration().getCompressionLevel(); - VelocityCompressor compressor = Natives.compress.get().create(level); - pipeline.addBefore(Connections.MINECRAFT_ENCODER, Connections.COMPRESSION_ENCODER, - new MinecraftCompressorAndLengthEncoder(threshold, compressor)); + if (Settings.IMP.MAIN.COMPATIBILITY_MODE) { + pipeline.addBefore(Connections.MINECRAFT_ENCODER, Connections.COMPRESSION_ENCODER, new MinecraftCompressorAndLengthEncoder(threshold, Natives.compress.get().create(configuration.getCompressionLevel()))); pipeline.remove(Connections.FRAME_ENCODER); + } else { + pipeline.addFirst(Connections.COMPRESSION_ENCODER, new ChannelOutboundHandlerAdapter()); } } @@ -220,16 +213,15 @@ public void hookLoginSession(GameProfileRequestEvent event) throws Throwable { pipeline.fireUserEventTriggered(VelocityConnectionEvent.COMPRESSION_DISABLED); } - VelocityConfiguration configuration = this.server.getConfiguration(); - UUID playerUniqueID = player.getUniqueId(); + UUID playerUniqueId = player.getUniqueId(); if (configuration.getPlayerInfoForwardingMode() == PlayerInfoForwarding.NONE) { - playerUniqueID = UuidUtils.generateOfflinePlayerUuid(player.getUsername()); + playerUniqueId = UuidUtils.generateOfflinePlayerUuid(player.getUsername()); } ServerLoginSuccessPacket success = new ServerLoginSuccessPacket(); success.setUsername(player.getUsername()); success.setProperties(player.getGameProfileProperties()); - success.setUuid(playerUniqueID); + success.setUuid(playerUniqueId); if (Settings.IMP.MAIN.COMPATIBILITY_MODE) { connection.write(success); @@ -237,25 +229,25 @@ public void hookLoginSession(GameProfileRequestEvent event) throws Throwable { ServerLoginSuccessHook successHook = new ServerLoginSuccessHook(); successHook.setUsername(player.getUsername()); successHook.setProperties(player.getGameProfileProperties()); - successHook.setUuid(playerUniqueID); + successHook.setUuid(playerUniqueId); connection.write(successHook); ChannelHandler compressionHandler = pipeline.get(Connections.COMPRESSION_ENCODER); - if (compressionHandler != null) { - connection.write(this.plugin.encodeSingleLogin(success, connection.getProtocolVersion())); - } else { + if (compressionHandler == null) { ChannelHandler frameHandler = pipeline.get(Connections.FRAME_ENCODER); if (frameHandler != null) { pipeline.remove(frameHandler); } - connection.write(this.plugin.encodeSingleLoginUncompressed(success, connection.getProtocolVersion())); + connection.write(this.plugin.encodeSingleLoginUncompressed(success, version)); + } else { + connection.write(this.plugin.encodeSingleLogin(success, version)); } } - this.plugin.setInitialID(player, playerUniqueID); + LimboAPI.setClientUniqueId(player, playerUniqueId); - if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) >= 0) { + if (v1_20_2) { loginHandler.thenRun(() -> this.fireRegisterEvent(player, connection, inbound, handler)); } else { connection.setState(StateRegistry.PLAY); @@ -265,22 +257,21 @@ public void hookLoginSession(GameProfileRequestEvent event) throws Throwable { } else { player.disconnect0(Component.translatable("velocity.error.already-connected-proxy", NamedTextColor.RED), true); } - } catch (Throwable e) { - throw new ReflectionException(e); + } catch (Throwable t) { + throw new ReflectionException(t); } } } } - private void fireRegisterEvent(ConnectedPlayer player, MinecraftConnection connection, - InitialInboundConnection inbound, Object handler) { + private void fireRegisterEvent(ConnectedPlayer player, MinecraftConnection connection, InitialInboundConnection inbound, AuthSessionHandler handler) { this.server.getEventManager().fire(new LoginLimboRegisterEvent(player)).thenAcceptAsync(limboRegisterEvent -> { LoginTasksQueue queue = new LoginTasksQueue(this.plugin, handler, this.server, player, inbound, limboRegisterEvent.getOnJoinCallbacks()); this.plugin.addLoginQueue(player, queue); this.plugin.setKickCallback(player, limboRegisterEvent.getOnKickCallback()); queue.next(); }, connection.eventLoop()).exceptionally(t -> { - LimboAPI.getLogger().error("Exception while registering LimboAPI login handlers for {}.", player, t); + LimboAPI.getLogger().error("Exception while registering LimboAPI login handlers for {}", player.toString(), t); return null; }); } @@ -291,7 +282,7 @@ public void hookPlaySession(ServerConnectedEvent event) { MinecraftConnection connection = player.getConnection(); // 1.20.2+ can ignore this, as it should be despawned by default - if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) >= 0) { + if (connection.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) { return; } @@ -301,47 +292,10 @@ public void hookPlaySession(ServerConnectedEvent event) { ClientPlaySessionHandler playHandler = new ClientPlaySessionHandler(this.server, player); SPAWNED_FIELD.invokeExact(playHandler, this.plugin.isLimboJoined(player)); connection.setActiveSessionHandler(connection.getState(), playHandler); - } catch (Throwable e) { - throw new ReflectionException(e); + } catch (Throwable t) { + throw new ReflectionException(t); } } }); } - - static { - CLOSED_MINECRAFT_CONNECTION = new ClosedMinecraftConnection(new ClosedChannel(new DummyEventPool()), null); - - try { - CONNECTED_PLAYER_CONSTRUCTOR = MethodHandles.privateLookupIn(ConnectedPlayer.class, MethodHandles.lookup()) - .findConstructor(ConnectedPlayer.class, - MethodType.methodType( - void.class, - VelocityServer.class, - GameProfile.class, - MinecraftConnection.class, - InetSocketAddress.class, - String.class, - boolean.class, - HandshakeIntent.class, - IdentifiedKey.class - ) - ); - - DELEGATE_FIELD = MethodHandles.privateLookupIn(LoginInboundConnection.class, MethodHandles.lookup()) - .findGetter(LoginInboundConnection.class, "delegate", InitialInboundConnection.class); - - Field mcConnectionField = AuthSessionHandler.class.getDeclaredField("mcConnection"); - mcConnectionField.setAccessible(true); - MC_CONNECTION_SETTER = LambdaUtil.setterOf(mcConnectionField); - - SPAWNED_FIELD = MethodHandles.privateLookupIn(ClientPlaySessionHandler.class, MethodHandles.lookup()) - .findSetter(ClientPlaySessionHandler.class, "spawned", boolean.class); - - Field tabListField = ConnectedPlayer.class.getDeclaredField("tabList"); - tabListField.setAccessible(true); - TAB_LIST_SETTER = LambdaUtil.setterOf(tabListField); - } catch (Throwable e) { - throw new ReflectionException(e); - } - } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/injection/login/LoginTasksQueue.java b/plugin/src/main/java/net/elytrium/limboapi/injection/login/LoginTasksQueue.java index 6c5558e0..e51bb9ad 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/injection/login/LoginTasksQueue.java +++ b/plugin/src/main/java/net/elytrium/limboapi/injection/login/LoginTasksQueue.java @@ -61,9 +61,8 @@ import io.netty.channel.ChannelPipeline; import io.netty.channel.EventLoop; import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; import java.lang.reflect.Field; +import java.util.Collections; import java.util.EnumSet; import java.util.List; import java.util.Objects; @@ -71,35 +70,33 @@ import java.util.Queue; import java.util.UUID; import java.util.concurrent.CompletableFuture; -import java.util.function.BiConsumer; import net.elytrium.commons.utils.reflection.ReflectionException; import net.elytrium.limboapi.LimboAPI; import net.elytrium.limboapi.injection.login.confirmation.LoginConfirmHandler; import net.elytrium.limboapi.server.LimboSessionHandlerImpl; -import net.elytrium.limboapi.utils.LambdaUtil; +import net.elytrium.limboapi.utils.Reflection; import net.kyori.adventure.text.Component; import org.slf4j.Logger; public class LoginTasksQueue { - private static final MethodHandle PROFILE_FIELD; + private static final MethodHandle PROFILE_FIELD = Reflection.findSetter(ConnectedPlayer.class, "profile", GameProfile.class); + private static final MethodHandle SET_PERMISSION_FUNCTION_METHOD = Reflection.findVirtualVoid(ConnectedPlayer.class, "setPermissionFunction", PermissionFunction.class); + private static final MethodHandle INITIAL_CONNECT_SESSION_HANDLER_CONSTRUCTOR = Reflection.findConstructor(InitialConnectSessionHandler.class, ConnectedPlayer.class, VelocityServer.class); + private static final MethodHandle SET_CLIENT_BRAND = Reflection.findVirtualVoid(ConnectedPlayer.class, "setClientBrand", String.class); + public static final MethodHandle BRAND_CHANNEL_SETTER = Reflection.findSetter(ClientConfigSessionHandler.class, "brandChannel", String.class); + private static final MethodHandle CONNECT_TO_INITIAL_SERVER_METHOD = Reflection.findVirtual(AuthSessionHandler.class, "connectToInitialServer", CompletableFuture.class, ConnectedPlayer.class); + private static final PermissionProvider DEFAULT_PERMISSIONS; - private static final MethodHandle SET_PERMISSION_FUNCTION_METHOD; - private static final MethodHandle INITIAL_CONNECT_SESSION_HANDLER_CONSTRUCTOR; - private static final BiConsumer MC_CONNECTION_SETTER; - private static final MethodHandle CONNECT_TO_INITIAL_SERVER_METHOD; - private static final MethodHandle SET_CLIENT_BRAND; - public static final BiConsumer BRAND_CHANNEL_SETTER; private final LimboAPI plugin; - private final Object handler; + private final AuthSessionHandler handler; private final VelocityServer server; private final ConnectedPlayer player; private final InboundConnection inbound; private final Queue queue; - public LoginTasksQueue(LimboAPI plugin, Object handler, VelocityServer server, ConnectedPlayer player, - InboundConnection inbound, Queue queue) { + public LoginTasksQueue(LimboAPI plugin, AuthSessionHandler handler, VelocityServer server, ConnectedPlayer player, InboundConnection inbound, Queue queue) { this.plugin = plugin; this.handler = handler; this.server = server; @@ -120,6 +117,7 @@ public void next() { } } + @SuppressWarnings("UnnecessaryToStringCall") private void finish() { this.plugin.removeLoginQueue(this.player); @@ -128,79 +126,65 @@ private void finish() { Logger logger = LimboAPI.getLogger(); this.plugin.getEventManagerHook().proceedProfile(this.player.getGameProfile()); - eventManager.fire(new GameProfileRequestEvent(this.inbound, this.player.getGameProfile(), this.player.isOnlineMode())).thenAcceptAsync( - gameProfile -> { - try { - UUID uuid = this.plugin.getInitialID(this.player); - if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19_1) <= 0) { - connection.delayedWrite(new LegacyPlayerListItemPacket( - LegacyPlayerListItemPacket.REMOVE_PLAYER, - List.of(new LegacyPlayerListItemPacket.Item(uuid)) - )); - - connection.delayedWrite(new LegacyPlayerListItemPacket( - LegacyPlayerListItemPacket.ADD_PLAYER, - List.of( - new LegacyPlayerListItemPacket.Item(uuid) - .setName(gameProfile.getUsername()) - .setProperties(gameProfile.getGameProfile().getProperties()) - ) - )); - } else if (connection.getState() != StateRegistry.CONFIG) { - UpsertPlayerInfoPacket.Entry playerInfoEntry = new UpsertPlayerInfoPacket.Entry(uuid); - playerInfoEntry.setDisplayName(new ComponentHolder(this.player.getProtocolVersion(), Component.text(gameProfile.getUsername()))); - playerInfoEntry.setProfile(gameProfile.getGameProfile()); + eventManager.fire(new GameProfileRequestEvent(this.inbound, this.player.getGameProfile(), this.player.isOnlineMode())).thenAcceptAsync(gameProfile -> { + try { + UUID uuid = LimboAPI.getClientUniqueId(this.player); + if (connection.getProtocolVersion().noGreaterThan(ProtocolVersion.MINECRAFT_1_19_1)) { + // TODO try to remove it + connection.delayedWrite(new LegacyPlayerListItemPacket(LegacyPlayerListItemPacket.REMOVE_PLAYER, Collections.singletonList(new LegacyPlayerListItemPacket.Item(uuid)))); + connection.delayedWrite(new LegacyPlayerListItemPacket(LegacyPlayerListItemPacket.ADD_PLAYER, Collections.singletonList( + new LegacyPlayerListItemPacket.Item(uuid).setName(gameProfile.getUsername()).setProperties(gameProfile.getGameProfile().getProperties()) + ))); + } else if (connection.getState() != StateRegistry.CONFIG) { + UpsertPlayerInfoPacket.Entry playerInfoEntry = new UpsertPlayerInfoPacket.Entry(uuid); + playerInfoEntry.setDisplayName(new ComponentHolder(this.player.getProtocolVersion(), Component.text(gameProfile.getUsername()))); + playerInfoEntry.setProfile(gameProfile.getGameProfile()); + connection.delayedWrite(new UpsertPlayerInfoPacket(EnumSet.of(UpsertPlayerInfoPacket.Action.UPDATE_DISPLAY_NAME, UpsertPlayerInfoPacket.Action.ADD_PLAYER), Collections.singletonList(playerInfoEntry))); + } - connection.delayedWrite(new UpsertPlayerInfoPacket( - EnumSet.of( - UpsertPlayerInfoPacket.Action.UPDATE_DISPLAY_NAME, - UpsertPlayerInfoPacket.Action.ADD_PLAYER), - List.of(playerInfoEntry))); + PROFILE_FIELD.invokeExact(this.player, gameProfile.getGameProfile()); + + // From Velocity + eventManager.fire(new PermissionsSetupEvent(this.player, DEFAULT_PERMISSIONS)).thenAcceptAsync(event -> { + if (!connection.isClosed()) { + // Wait for permissions to load, then set the players' permission function + PermissionFunction function = event.createFunction(this.player); + if (function == null) { + logger.error( + "A plugin permission provider {} provided an invalid permission function" + + " for player {}. This is a bug in the plugin, not in Velocity. Falling" + + " back to the default permission function.", + event.getProvider().getClass().getName(), + this.player.getUsername() + ); + } else { + try { + SET_PERMISSION_FUNCTION_METHOD.invokeExact(this.player, function); + } catch (Throwable t) { + logger.error("Exception while completing injection to {}", this.player.toString(), t); + } } - PROFILE_FIELD.invokeExact(this.player, gameProfile.getGameProfile()); - - // From Velocity. - eventManager - .fire(new PermissionsSetupEvent(this.player, DEFAULT_PERMISSIONS)) - .thenAcceptAsync(event -> { - if (!connection.isClosed()) { - // Wait for permissions to load, then set the players' permission function. - PermissionFunction function = event.createFunction(this.player); - if (function == null) { - logger.error( - "A plugin permission provider {} provided an invalid permission function" - + " for player {}. This is a bug in the plugin, not in Velocity. Falling" - + " back to the default permission function.", - event.getProvider().getClass().getName(), - this.player.getUsername() - ); - } else { - try { - SET_PERMISSION_FUNCTION_METHOD.invokeExact(this.player, function); - } catch (Throwable ex) { - logger.error("Exception while completing injection to {}", this.player, ex); - } - } - try { - this.initialize(connection); - } catch (Throwable e) { - throw new ReflectionException(e); - } - } - }, connection.eventLoop()); - } catch (Throwable e) { - logger.error("Exception while completing injection to {}", this.player, e); + try { + this.initialize(connection); + } catch (Throwable t) { + throw new ReflectionException(t); + } } }, connection.eventLoop()); + } catch (Throwable t) { + logger.error("Exception while completing injection to {}", this.player.toString(), t); + } + }, connection.eventLoop()); } - // From Velocity. + // From Velocity + @SuppressWarnings("UnnecessaryToStringCall") @SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE") - private void initialize(MinecraftConnection connection) throws Throwable { + private void initialize(MinecraftConnection connection) { connection.setAssociation(this.player); - if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) < 0 - || connection.getState() != StateRegistry.CONFIG) { + ProtocolVersion version = connection.getProtocolVersion(); + if (version.lessThan(ProtocolVersion.MINECRAFT_1_20_2) || connection.getState() != StateRegistry.CONFIG) { this.plugin.setState(connection, StateRegistry.PLAY); } @@ -208,50 +192,45 @@ private void initialize(MinecraftConnection connection) throws Throwable { this.plugin.deject3rdParty(pipeline); if (pipeline.get(Connections.FRAME_ENCODER) == null) { - this.plugin.fixCompressor(pipeline, connection.getProtocolVersion()); + this.plugin.fixCompressor(pipeline); } - Logger logger = LimboAPI.getLogger(); this.server.getEventManager().fire(new LoginEvent(this.player)).thenAcceptAsync(event -> { if (connection.isClosed()) { - // The player was disconnected. + // The player was disconnected this.server.getEventManager().fireAndForget(new DisconnectEvent(this.player, DisconnectEvent.LoginStatus.CANCELLED_BY_USER_BEFORE_COMPLETE)); } else { Optional reason = event.getResult().getReasonComponent(); if (reason.isPresent()) { this.player.disconnect0(reason.get(), false); - } else { - if (this.server.registerConnection(this.player)) { - if (connection.getActiveSessionHandler() instanceof LoginConfirmHandler confirm) { - confirm.waitForConfirmation(() -> this.connectToServer(logger, this.player, connection)); - } else { - this.connectToServer(logger, this.player, connection); - } + } else if (this.server.registerConnection(this.player)) { + if (connection.getActiveSessionHandler() instanceof LoginConfirmHandler confirm) { + confirm.waitForConfirmation(() -> this.connectToServer(connection)); } else { - this.player.disconnect0(Component.translatable("velocity.error.already-connected-proxy"), false); + this.connectToServer(connection); } + } else { + this.player.disconnect0(Component.translatable("velocity.error.already-connected-proxy"), false); } } }, connection.eventLoop()).exceptionally(t -> { - logger.error("Exception while completing login initialisation phase for {}", this.player, t); + LimboAPI.getLogger().error("Exception while completing login initialisation phase for {}", this.player.toString(), t); return null; }); } + @SuppressWarnings({"DataFlowIssue", "unchecked", "UnnecessaryToStringCall"}) @SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE") - private void connectToServer(Logger logger, ConnectedPlayer player, MinecraftConnection connection) { - if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) < 0) { + private void connectToServer(MinecraftConnection connection) { + if (connection.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_20_2)) { try { - connection.setActiveSessionHandler(connection.getState(), - (InitialConnectSessionHandler) INITIAL_CONNECT_SESSION_HANDLER_CONSTRUCTOR.invokeExact(this.player, this.server)); - } catch (Throwable e) { - throw new ReflectionException(e); + connection.setActiveSessionHandler(connection.getState(), (InitialConnectSessionHandler) INITIAL_CONNECT_SESSION_HANDLER_CONSTRUCTOR.invokeExact(this.player, this.server)); + } catch (Throwable t) { + throw new ReflectionException(t); } } else if (connection.getState() == StateRegistry.PLAY) { // Synchronize with the client to ensure that it will not corrupt CONFIG state with PLAY packets - ((LimboSessionHandlerImpl) connection.getActiveSessionHandler()) - .disconnectToConfig(() -> this.connectToServer(logger, player, connection)); - + ((LimboSessionHandlerImpl) connection.getActiveSessionHandler()).disconnectToConfig(() -> this.connectToServer(connection)); return; // Re-running this method due to synchronization with the client } else { ClientConfigSessionHandler configHandler = new ClientConfigSessionHandler(this.server, this.player); @@ -268,9 +247,9 @@ private void connectToServer(Logger logger, ConnectedPlayer player, MinecraftCon try { this.server.getEventManager().fireAndForget(new PlayerClientBrandEvent(this.player, sessionHandler.getBrand())); SET_CLIENT_BRAND.invokeExact(this.player, sessionHandler.getBrand()); - BRAND_CHANNEL_SETTER.accept(configHandler, "minecraft:brand"); - } catch (Throwable e) { - throw new ReflectionException(e); + BRAND_CHANNEL_SETTER.invokeExact(configHandler, "minecraft:brand"); + } catch (Throwable t) { + throw new ReflectionException(t); } } } @@ -278,47 +257,26 @@ private void connectToServer(Logger logger, ConnectedPlayer player, MinecraftCon this.plugin.setActiveSessionHandler(connection, StateRegistry.CONFIG, configHandler); } - this.server.getEventManager().fire(new PostLoginEvent(this.player)).thenAccept(postLoginEvent -> { + this.server.getEventManager().fire(new PostLoginEvent(this.player)).thenCompose(postLoginEvent -> { try { - MC_CONNECTION_SETTER.accept(this.handler, connection); - CONNECT_TO_INITIAL_SERVER_METHOD.invoke((AuthSessionHandler) this.handler, this.player); - } catch (Throwable e) { - throw new ReflectionException(e); + LoginListener.MC_CONNECTION_SETTER.invokeExact(this.handler, connection); + return (CompletableFuture) CONNECT_TO_INITIAL_SERVER_METHOD.invokeExact(this.handler, this.player); + } catch (Throwable t) { + throw new ReflectionException(t); } + }).exceptionally(t -> { + LimboAPI.getLogger().error("Exception while connecting {} to initial server", this.player.toString(), t); + return null; }); } static { try { - PROFILE_FIELD = MethodHandles.privateLookupIn(ConnectedPlayer.class, MethodHandles.lookup()) - .findSetter(ConnectedPlayer.class, "profile", GameProfile.class); - Field defaultPermissionsField = ConnectedPlayer.class.getDeclaredField("DEFAULT_PERMISSIONS"); defaultPermissionsField.setAccessible(true); DEFAULT_PERMISSIONS = (PermissionProvider) defaultPermissionsField.get(null); - - SET_PERMISSION_FUNCTION_METHOD = MethodHandles.privateLookupIn(ConnectedPlayer.class, MethodHandles.lookup()) - .findVirtual(ConnectedPlayer.class, "setPermissionFunction", MethodType.methodType(void.class, PermissionFunction.class)); - - INITIAL_CONNECT_SESSION_HANDLER_CONSTRUCTOR = MethodHandles - .privateLookupIn(InitialConnectSessionHandler.class, MethodHandles.lookup()) - .findConstructor(InitialConnectSessionHandler.class, MethodType.methodType(void.class, ConnectedPlayer.class, VelocityServer.class)); - - CONNECT_TO_INITIAL_SERVER_METHOD = MethodHandles.privateLookupIn(AuthSessionHandler.class, MethodHandles.lookup()) - .findVirtual(AuthSessionHandler.class, "connectToInitialServer", MethodType.methodType(CompletableFuture.class, ConnectedPlayer.class)); - - Field mcConnectionField = AuthSessionHandler.class.getDeclaredField("mcConnection"); - mcConnectionField.setAccessible(true); - MC_CONNECTION_SETTER = LambdaUtil.setterOf(mcConnectionField); - - SET_CLIENT_BRAND = MethodHandles.privateLookupIn(ConnectedPlayer.class, MethodHandles.lookup()) - .findVirtual(ConnectedPlayer.class, "setClientBrand", MethodType.methodType(void.class, String.class)); - - Field brandChannelField = ClientConfigSessionHandler.class.getDeclaredField("brandChannel"); - brandChannelField.setAccessible(true); - BRAND_CHANNEL_SETTER = LambdaUtil.setterOf(brandChannelField); - } catch (Throwable e) { - throw new ReflectionException(e); + } catch (NoSuchFieldException | IllegalAccessException t) { + throw new ReflectionException(t); } } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/injection/login/confirmation/LoginConfirmHandler.java b/plugin/src/main/java/net/elytrium/limboapi/injection/login/confirmation/LoginConfirmHandler.java index adee13ac..6dcb8128 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/injection/login/confirmation/LoginConfirmHandler.java +++ b/plugin/src/main/java/net/elytrium/limboapi/injection/login/confirmation/LoginConfirmHandler.java @@ -26,19 +26,15 @@ import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.util.ReferenceCountUtil; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; import net.elytrium.commons.utils.reflection.ReflectionException; import net.elytrium.limboapi.LimboAPI; +import net.elytrium.limboapi.server.LimboSessionHandlerImpl; public class LoginConfirmHandler implements MinecraftSessionHandler { - private static final MethodHandle TEARDOWN_METHOD; - private final LimboAPI plugin; private final CompletableFuture confirmation = new CompletableFuture<>(); private final List queuedPackets = new ArrayList<>(); @@ -62,12 +58,13 @@ public CompletableFuture thenRun(Runnable runnable) { return this.confirmation.thenRun(runnable); } + @SuppressWarnings("UnnecessaryCallToStringValueOf") public void waitForConfirmation(Runnable runnable) { this.thenRun(() -> { try { runnable.run(); - } catch (Throwable throwable) { - LimboAPI.getLogger().error("Failed to confirm transition for " + this.player, throwable); + } catch (Throwable t) { + LimboAPI.getLogger().error("Failed to confirm transition for {}", this.player.toString(), t); } try { @@ -75,15 +72,14 @@ public void waitForConfirmation(Runnable runnable) { for (MinecraftPacket packet : this.queuedPackets) { try { this.connection.channelRead(ctx, packet); - } catch (Throwable throwable) { - LimboAPI.getLogger().error("{}: exception handling exception in {}", ctx.channel().remoteAddress(), - this.connection.getActiveSessionHandler(), throwable); + } catch (Throwable t) { + LimboAPI.getLogger().error("{}: failed to handle packet {} for handler {}", this.player.toString(), packet.toString(), String.valueOf(this.connection.getActiveSessionHandler()), t); } } this.queuedPackets.clear(); - } catch (Throwable throwable) { - LimboAPI.getLogger().error("Failed to process packet queue for " + this.player, throwable); + } catch (Throwable t) { + LimboAPI.getLogger().error("Failed to process packet queue for {}", this.player.toString(), t); } }); } @@ -97,7 +93,7 @@ public boolean handle(LoginAcknowledgedPacket packet) { @Override public void handleGeneric(MinecraftPacket packet) { - // As Velocity/LimboAPI can easly skip packets due to random delays, packets should be queued + // As Velocity/LimboAPI can easily skip packets due to random delays, packets should be queued if (this.connection.getState() == StateRegistry.CONFIG) { this.queuedPackets.add(ReferenceCountUtil.retain(packet)); } @@ -113,9 +109,9 @@ public void disconnected() { try { if (this.player != null) { try { - TEARDOWN_METHOD.invokeExact(this.player); - } catch (Throwable e) { - throw new ReflectionException(e); + LimboSessionHandlerImpl.TEARDOWN_METHOD.invokeExact(this.player); + } catch (Throwable t) { + throw new ReflectionException(t); } } } finally { @@ -124,13 +120,4 @@ public void disconnected() { } } } - - static { - try { - TEARDOWN_METHOD = MethodHandles.privateLookupIn(ConnectedPlayer.class, MethodHandles.lookup()) - .findVirtual(ConnectedPlayer.class, "teardown", MethodType.methodType(void.class)); - } catch (NoSuchMethodException | IllegalAccessException e) { - throw new ReflectionException(e); - } - } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/injection/packet/LegacyPlayerListItemHook.java b/plugin/src/main/java/net/elytrium/limboapi/injection/packet/LegacyPlayerListItemHook.java index f459ad08..94d7c63c 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/injection/packet/LegacyPlayerListItemHook.java +++ b/plugin/src/main/java/net/elytrium/limboapi/injection/packet/LegacyPlayerListItemHook.java @@ -27,11 +27,8 @@ import com.velocitypowered.proxy.protocol.packet.LegacyPlayerListItemPacket; import io.netty.util.collection.IntObjectMap; import it.unimi.dsi.fastutil.objects.Object2IntMap; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; import java.util.List; import java.util.Map; -import java.util.UUID; import java.util.function.Supplier; import net.elytrium.commons.utils.reflection.ReflectionException; import net.elytrium.limboapi.LimboAPI; @@ -40,14 +37,6 @@ @SuppressWarnings("unchecked") public class LegacyPlayerListItemHook extends LegacyPlayerListItemPacket { - private static final MethodHandle SERVER_CONN_FIELD; - - private final LimboAPI plugin; - - private LegacyPlayerListItemHook(LimboAPI plugin) { - this.plugin = plugin; - } - @Override public boolean handle(MinecraftSessionHandler handler) { if (handler instanceof BackendPlaySessionHandler) { @@ -55,19 +44,17 @@ public boolean handle(MinecraftSessionHandler handler) { for (int i = 0; i < items.size(); ++i) { try { Item item = items.get(i); - ConnectedPlayer player = ((VelocityServerConnection) SERVER_CONN_FIELD.invokeExact((BackendPlaySessionHandler) handler)).getPlayer(); - UUID initialID = this.plugin.getInitialID(player); - + ConnectedPlayer player = ((VelocityServerConnection) UpsertPlayerInfoHook.SERVER_CONN_FIELD.invokeExact((BackendPlaySessionHandler) handler)).getPlayer(); if (player.getUniqueId().equals(item.getUuid())) { - items.set(i, new Item(initialID) + items.set(i, new Item(LimboAPI.getClientUniqueId(player)) .setDisplayName(item.getDisplayName()) .setGameMode(item.getGameMode()) .setLatency(item.getLatency()) .setName(item.getName()) .setProperties(item.getProperties())); } - } catch (Throwable e) { - throw new ReflectionException(e); + } catch (Throwable t) { + throw new ReflectionException(t); } } } @@ -75,17 +62,7 @@ public boolean handle(MinecraftSessionHandler handler) { return super.handle(handler); } - static { - try { - SERVER_CONN_FIELD = MethodHandles.privateLookupIn(BackendPlaySessionHandler.class, MethodHandles.lookup()) - .findGetter(BackendPlaySessionHandler.class, "serverConn", VelocityServerConnection.class); - } catch (NoSuchFieldException | IllegalAccessException e) { - throw new ReflectionException(e); - } - } - - public static void init(LimboAPI plugin, StateRegistry.PacketRegistry registry) throws ReflectiveOperationException { - // See LimboProtocol#overlayRegistry about var. + public static void init(StateRegistry.PacketRegistry registry) throws ReflectiveOperationException { var playProtocolRegistryVersions = (Map) LimboProtocol.VERSIONS_FIELD.get(registry); playProtocolRegistryVersions.forEach((protocolVersion, protocolRegistry) -> { try { @@ -94,7 +71,7 @@ public static void init(LimboAPI plugin, StateRegistry.PacketRegistry registry) int id = packetClassToID.getInt(LegacyPlayerListItemPacket.class); packetClassToID.put(LegacyPlayerListItemHook.class, id); - packetIDToSupplier.put(id, () -> new LegacyPlayerListItemHook(plugin)); + packetIDToSupplier.put(id, LegacyPlayerListItemHook::new); } catch (ReflectiveOperationException e) { throw new ReflectionException(e); } diff --git a/plugin/src/main/java/net/elytrium/limboapi/injection/packet/LimboCompressDecoder.java b/plugin/src/main/java/net/elytrium/limboapi/injection/packet/LimboCompressDecoder.java index 81dccf5d..2a3350d9 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/injection/packet/LimboCompressDecoder.java +++ b/plugin/src/main/java/net/elytrium/limboapi/injection/packet/LimboCompressDecoder.java @@ -18,4 +18,5 @@ package net.elytrium.limboapi.injection.packet; public interface LimboCompressDecoder { + } diff --git a/plugin/src/main/java/net/elytrium/limboapi/injection/packet/MinecraftLimitedCompressDecoder.java b/plugin/src/main/java/net/elytrium/limboapi/injection/packet/MinecraftLimitedCompressDecoder.java index e3f73c65..054948ad 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/injection/packet/MinecraftLimitedCompressDecoder.java +++ b/plugin/src/main/java/net/elytrium/limboapi/injection/packet/MinecraftLimitedCompressDecoder.java @@ -61,23 +61,20 @@ protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) t int claimedUncompressedSize = ProtocolUtils.readVarInt(in); if (claimedUncompressedSize == 0) { out.add(in.retain()); - } else { - if (claimedUncompressedSize > Settings.IMP.MAIN.MAX_SINGLE_GENERIC_PACKET_LENGTH) { - ctx.close(); - } else { - if (claimedUncompressedSize >= this.threshold && claimedUncompressedSize <= this.uncompressedCap) { - ByteBuf compatibleIn = MoreByteBufUtils.ensureCompatible(ctx.alloc(), this.compressor, in); - ByteBuf uncompressed = MoreByteBufUtils.preferredBuffer(ctx.alloc(), this.compressor, claimedUncompressedSize); - try { - this.compressor.inflate(compatibleIn, uncompressed, claimedUncompressedSize); - out.add(uncompressed); - } catch (Exception e) { - uncompressed.release(); - throw e; - } finally { - compatibleIn.release(); - } - } + } else if (claimedUncompressedSize > Settings.IMP.MAIN.MAX_SINGLE_GENERIC_PACKET_LENGTH) { + ctx.close(); + } else if (claimedUncompressedSize >= this.threshold && claimedUncompressedSize <= this.uncompressedCap) { + var alloc = ctx.alloc(); + ByteBuf compatibleIn = MoreByteBufUtils.ensureCompatible(alloc, this.compressor, in); + ByteBuf uncompressed = MoreByteBufUtils.preferredBuffer(alloc, this.compressor, claimedUncompressedSize); + try { + this.compressor.inflate(compatibleIn, uncompressed, claimedUncompressedSize); + out.add(uncompressed); + } catch (Exception e) { + uncompressed.release(); + throw e; + } finally { + compatibleIn.release(); } } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/injection/packet/RemovePlayerInfoHook.java b/plugin/src/main/java/net/elytrium/limboapi/injection/packet/RemovePlayerInfoHook.java index 0986d209..266c52f1 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/injection/packet/RemovePlayerInfoHook.java +++ b/plugin/src/main/java/net/elytrium/limboapi/injection/packet/RemovePlayerInfoHook.java @@ -27,8 +27,6 @@ import com.velocitypowered.proxy.protocol.packet.RemovePlayerInfoPacket; import io.netty.util.collection.IntObjectMap; import it.unimi.dsi.fastutil.objects.Object2IntMap; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; import java.util.List; import java.util.Map; import java.util.UUID; @@ -40,20 +38,12 @@ @SuppressWarnings("unchecked") public class RemovePlayerInfoHook extends RemovePlayerInfoPacket { - private static final MethodHandle SERVER_CONN_FIELD; - - private final LimboAPI plugin; - - private RemovePlayerInfoHook(LimboAPI plugin) { - this.plugin = plugin; - } - @Override public boolean handle(MinecraftSessionHandler handler) { if (handler instanceof BackendPlaySessionHandler) { try { - ConnectedPlayer player = ((VelocityServerConnection) SERVER_CONN_FIELD.invokeExact((BackendPlaySessionHandler) handler)).getPlayer(); - UUID initialID = this.plugin.getInitialID(player); + ConnectedPlayer player = ((VelocityServerConnection) UpsertPlayerInfoHook.SERVER_CONN_FIELD.invokeExact((BackendPlaySessionHandler) handler)).getPlayer(); + UUID initialID = LimboAPI.getClientUniqueId(player); if (this.getProfilesToRemove() instanceof List uuids) { for (int i = 0; i < uuids.size(); i++) { if (player.getUniqueId().equals(uuids.get(i))) { @@ -61,25 +51,15 @@ public boolean handle(MinecraftSessionHandler handler) { } } } - } catch (Throwable e) { - throw new ReflectionException(e); + } catch (Throwable t) { + throw new ReflectionException(t); } } return super.handle(handler); } - static { - try { - SERVER_CONN_FIELD = MethodHandles.privateLookupIn(BackendPlaySessionHandler.class, MethodHandles.lookup()) - .findGetter(BackendPlaySessionHandler.class, "serverConn", VelocityServerConnection.class); - } catch (NoSuchFieldException | IllegalAccessException e) { - throw new ReflectionException(e); - } - } - - public static void init(LimboAPI plugin, StateRegistry.PacketRegistry registry) throws ReflectiveOperationException { - // See LimboProtocol#overlayRegistry about var. + public static void init(StateRegistry.PacketRegistry registry) throws ReflectiveOperationException { var playProtocolRegistryVersions = (Map) LimboProtocol.VERSIONS_FIELD.get(registry); playProtocolRegistryVersions.forEach((protocolVersion, protocolRegistry) -> { try { @@ -88,7 +68,7 @@ public static void init(LimboAPI plugin, StateRegistry.PacketRegistry registry) int id = packetClassToID.getInt(RemovePlayerInfoPacket.class); packetClassToID.put(RemovePlayerInfoHook.class, id); - packetIDToSupplier.put(id, () -> new RemovePlayerInfoHook(plugin)); + packetIDToSupplier.put(id, RemovePlayerInfoHook::new); } catch (ReflectiveOperationException e) { throw new ReflectionException(e); } diff --git a/plugin/src/main/java/net/elytrium/limboapi/injection/packet/UpsertPlayerInfoHook.java b/plugin/src/main/java/net/elytrium/limboapi/injection/packet/UpsertPlayerInfoHook.java index aa216b4c..8e4ef30b 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/injection/packet/UpsertPlayerInfoHook.java +++ b/plugin/src/main/java/net/elytrium/limboapi/injection/packet/UpsertPlayerInfoHook.java @@ -29,7 +29,6 @@ import io.netty.util.collection.IntObjectMap; import it.unimi.dsi.fastutil.objects.Object2IntMap; import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; import java.util.List; import java.util.Map; import java.util.UUID; @@ -37,66 +36,49 @@ import net.elytrium.commons.utils.reflection.ReflectionException; import net.elytrium.limboapi.LimboAPI; import net.elytrium.limboapi.protocol.LimboProtocol; +import net.elytrium.limboapi.utils.Reflection; @SuppressWarnings("unchecked") public class UpsertPlayerInfoHook extends UpsertPlayerInfoPacket { - private static final MethodHandle SERVER_CONN_FIELD; - - private final LimboAPI plugin; - - private UpsertPlayerInfoHook(LimboAPI plugin) { - this.plugin = plugin; - } + static final MethodHandle SERVER_CONN_FIELD = Reflection.findGetter(BackendPlaySessionHandler.class, "serverConn", VelocityServerConnection.class); @Override public boolean handle(MinecraftSessionHandler handler) { if (handler instanceof BackendPlaySessionHandler) { try { ConnectedPlayer player = ((VelocityServerConnection) SERVER_CONN_FIELD.invokeExact((BackendPlaySessionHandler) handler)).getPlayer(); - UUID initialID = this.plugin.getInitialID(player); + UUID initialId = LimboAPI.getClientUniqueId(player); List items = this.getEntries(); - for (int i = 0; i < items.size(); ++i) { Entry item = items.get(i); - if (player.getUniqueId().equals(item.getProfileId())) { - Entry fixedEntry = new Entry(initialID); - fixedEntry.setDisplayName(item.getDisplayName()); - fixedEntry.setGameMode(item.getGameMode()); - fixedEntry.setLatency(item.getLatency()); - fixedEntry.setDisplayName(item.getDisplayName()); - if (item.getProfile() != null && item.getProfile().getId().equals(player.getUniqueId())) { - fixedEntry.setProfile(new GameProfile(initialID, item.getProfile().getName(), item.getProfile().getProperties())); - } else { - fixedEntry.setProfile(item.getProfile()); - } - fixedEntry.setListed(item.isListed()); - fixedEntry.setListOrder(item.getListOrder()); - fixedEntry.setChatSession(item.getChatSession()); - - items.set(i, fixedEntry); + items.set(i, UpsertPlayerInfoHook.createFixedEntry(initialId, item, player)); } } - } catch (Throwable e) { - throw new ReflectionException(e); + } catch (Throwable t) { + throw new ReflectionException(t); } } return super.handle(handler); } - static { - try { - SERVER_CONN_FIELD = MethodHandles.privateLookupIn(BackendPlaySessionHandler.class, MethodHandles.lookup()) - .findGetter(BackendPlaySessionHandler.class, "serverConn", VelocityServerConnection.class); - } catch (NoSuchFieldException | IllegalAccessException e) { - throw new ReflectionException(e); - } + private static Entry createFixedEntry(UUID initialId, Entry item, ConnectedPlayer player) { + Entry fixedEntry = new Entry(initialId); + GameProfile profile = item.getProfile(); + fixedEntry.setProfile(profile == null || !profile.getId().equals(player.getUniqueId()) ? profile : new GameProfile(initialId, profile.getName(), profile.getProperties())); + fixedEntry.setListed(item.isListed()); + fixedEntry.setLatency(item.getLatency()); + fixedEntry.setGameMode(item.getGameMode()); + fixedEntry.setDisplayName(item.getDisplayName()); + fixedEntry.setShowHat(item.isShowHat()); + fixedEntry.setListOrder(item.getListOrder()); + fixedEntry.setChatSession(item.getChatSession()); + return fixedEntry; } - public static void init(LimboAPI plugin, StateRegistry.PacketRegistry registry) throws ReflectiveOperationException { - // See LimboProtocol#overlayRegistry about var. + public static void init(StateRegistry.PacketRegistry registry) throws ReflectiveOperationException { var playProtocolRegistryVersions = (Map) LimboProtocol.VERSIONS_FIELD.get(registry); playProtocolRegistryVersions.forEach((protocolVersion, protocolRegistry) -> { try { @@ -105,7 +87,7 @@ public static void init(LimboAPI plugin, StateRegistry.PacketRegistry registry) int id = packetClassToID.getInt(UpsertPlayerInfoPacket.class); packetClassToID.put(UpsertPlayerInfoHook.class, id); - packetIDToSupplier.put(id, () -> new UpsertPlayerInfoHook(plugin)); + packetIDToSupplier.put(id, UpsertPlayerInfoHook::new); } catch (ReflectiveOperationException e) { throw new ReflectionException(e); } diff --git a/plugin/src/main/java/net/elytrium/limboapi/injection/tablist/RewritingKeyedVelocityTabList.java b/plugin/src/main/java/net/elytrium/limboapi/injection/tablist/RewritingKeyedVelocityTabList.java index 48fd4ced..6f435339 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/injection/tablist/RewritingKeyedVelocityTabList.java +++ b/plugin/src/main/java/net/elytrium/limboapi/injection/tablist/RewritingKeyedVelocityTabList.java @@ -19,28 +19,15 @@ import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.player.TabListEntry; -import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.tablist.KeyedVelocityTabList; -import com.velocitypowered.proxy.tablist.KeyedVelocityTabListEntry; -import java.util.Map; import java.util.Optional; import java.util.UUID; public class RewritingKeyedVelocityTabList extends KeyedVelocityTabList implements RewritingTabList { - // To keep compatibility with other plugins that use internal fields - protected final ConnectedPlayer player; - protected final MinecraftConnection connection; - protected final ProxyServer proxyServer; - protected final Map entries; - public RewritingKeyedVelocityTabList(ConnectedPlayer player, ProxyServer proxyServer) { super(player, proxyServer); - this.player = super.player; - this.connection = super.connection; - this.proxyServer = super.proxyServer; - this.entries = super.entries; } @Override diff --git a/plugin/src/main/java/net/elytrium/limboapi/injection/tablist/RewritingTabList.java b/plugin/src/main/java/net/elytrium/limboapi/injection/tablist/RewritingTabList.java index bbe0079d..fc79e261 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/injection/tablist/RewritingTabList.java +++ b/plugin/src/main/java/net/elytrium/limboapi/injection/tablist/RewritingTabList.java @@ -28,28 +28,30 @@ public interface RewritingTabList { Player getPlayer(); default TabListEntry rewriteEntry(TabListEntry entry) { - if (entry == null || entry.getProfile() == null || !this.getPlayer().getUniqueId().equals(entry.getProfile().getId())) { + GameProfile profile; + UUID profileId; + if (entry == null || (profile = entry.getProfile()) == null || !this.getPlayer().getUniqueId().equals(profileId = profile.getId())) { return entry; } - TabListEntry.Builder builder = TabListEntry.builder(); - builder.tabList(entry.getTabList()); - builder.profile(new GameProfile(this.rewriteUuid(entry.getProfile().getId()), - entry.getProfile().getName(), entry.getProfile().getProperties())); - builder.listed(entry.isListed()); - builder.latency(entry.getLatency()); - builder.gameMode(entry.getGameMode()); - entry.getDisplayNameComponent().ifPresent(builder::displayName); - builder.chatSession(entry.getChatSession()); - builder.listOrder(entry.getListOrder()); - builder.showHat(entry.isShowHat()); - - return builder.build(); + return TabListEntry.builder() + .tabList(entry.getTabList()) + .profile(new GameProfile(this.rewriteUuid(profileId), profile.getName(), profile.getProperties())) + .chatSession(entry.getChatSession()) + .displayName(entry.getDisplayNameComponent().orElse(null)) + .latency(entry.getLatency()) + .gameMode(entry.getGameMode()) + .listed(entry.isListed()) + .listOrder(entry.getListOrder()) + .showHat(entry.isShowHat()) + .build(); } default UUID rewriteUuid(UUID uuid) { - if (this.getPlayer().getUniqueId().equals(uuid)) { - return LimboAPI.INITIAL_ID.getOrDefault(this.getPlayer(), uuid); + Player player = this.getPlayer(); + if (player.getUniqueId().equals(uuid)) { + UUID clientUniqueId = LimboAPI.getClientUniqueId(player); + return clientUniqueId == null ? uuid : clientUniqueId; } return uuid; diff --git a/plugin/src/main/java/net/elytrium/limboapi/injection/tablist/RewritingVelocityTabList.java b/plugin/src/main/java/net/elytrium/limboapi/injection/tablist/RewritingVelocityTabList.java index a5124562..0a4922a4 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/injection/tablist/RewritingVelocityTabList.java +++ b/plugin/src/main/java/net/elytrium/limboapi/injection/tablist/RewritingVelocityTabList.java @@ -18,45 +18,15 @@ package net.elytrium.limboapi.injection.tablist; import com.velocitypowered.api.proxy.player.TabListEntry; -import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.tablist.VelocityTabList; -import com.velocitypowered.proxy.tablist.VelocityTabListEntry; -import java.lang.reflect.Field; -import java.util.Map; import java.util.Optional; import java.util.UUID; -import java.util.function.Function; -import net.elytrium.limboapi.utils.LambdaUtil; public class RewritingVelocityTabList extends VelocityTabList implements RewritingTabList { - private static final Function> ENTRIES_GETTER; - - static { - try { - Field field = VelocityTabList.class.getDeclaredField("entries"); - field.setAccessible(true); - ENTRIES_GETTER = LambdaUtil.getterOf(field); - } catch (Throwable throwable) { - throw new ExceptionInInitializerError(throwable); - } - } - - // To keep compatibility with other plugins that use internal fields - private final ConnectedPlayer player; - private final MinecraftConnection connection; - private final Map entries; - public RewritingVelocityTabList(ConnectedPlayer player) { super(player); - try { - this.player = player; - this.connection = player.getConnection(); - this.entries = ENTRIES_GETTER.apply(this); - } catch (Throwable e) { - throw new IllegalStateException(e); - } } @Override diff --git a/plugin/src/main/java/net/elytrium/limboapi/injection/tablist/RewritingVelocityTabListLegacy.java b/plugin/src/main/java/net/elytrium/limboapi/injection/tablist/RewritingVelocityTabListLegacy.java index 643b7b5b..4c65dee0 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/injection/tablist/RewritingVelocityTabListLegacy.java +++ b/plugin/src/main/java/net/elytrium/limboapi/injection/tablist/RewritingVelocityTabListLegacy.java @@ -19,28 +19,15 @@ import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.player.TabListEntry; -import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; -import com.velocitypowered.proxy.tablist.KeyedVelocityTabListEntry; import com.velocitypowered.proxy.tablist.VelocityTabListLegacy; -import java.util.Map; import java.util.Optional; import java.util.UUID; public class RewritingVelocityTabListLegacy extends VelocityTabListLegacy implements RewritingTabList { - // To keep compatibility with other plugins that use internal fields - protected final ConnectedPlayer player; - protected final MinecraftConnection connection; - protected final ProxyServer proxyServer; - protected final Map entries; - public RewritingVelocityTabListLegacy(ConnectedPlayer player, ProxyServer proxyServer) { super(player, proxyServer); - this.player = super.player; - this.connection = super.connection; - this.proxyServer = super.proxyServer; - this.entries = super.entries; } @Override diff --git a/plugin/src/main/java/net/elytrium/limboapi/injection/disconnect/DisconnectListener.java b/plugin/src/main/java/net/elytrium/limboapi/listener/DisconnectListener.java similarity index 93% rename from plugin/src/main/java/net/elytrium/limboapi/injection/disconnect/DisconnectListener.java rename to plugin/src/main/java/net/elytrium/limboapi/listener/DisconnectListener.java index aaae9519..86b001a6 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/injection/disconnect/DisconnectListener.java +++ b/plugin/src/main/java/net/elytrium/limboapi/listener/DisconnectListener.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package net.elytrium.limboapi.injection.disconnect; +package net.elytrium.limboapi.listener; import com.velocitypowered.api.event.Subscribe; import com.velocitypowered.api.event.connection.DisconnectEvent; @@ -37,6 +37,6 @@ public void onDisconnect(DisconnectEvent event) { this.plugin.removeLoginQueue(player); this.plugin.removeKickCallback(player); this.plugin.removeNextServer(player); - this.plugin.removeInitialID(player); + LimboAPI.removeClientUniqueId(player); } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/utils/ReloadListener.java b/plugin/src/main/java/net/elytrium/limboapi/listener/ReloadListener.java similarity index 96% rename from plugin/src/main/java/net/elytrium/limboapi/utils/ReloadListener.java rename to plugin/src/main/java/net/elytrium/limboapi/listener/ReloadListener.java index 4059200a..561a4878 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/utils/ReloadListener.java +++ b/plugin/src/main/java/net/elytrium/limboapi/listener/ReloadListener.java @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package net.elytrium.limboapi.utils; +package net.elytrium.limboapi.listener; import com.velocitypowered.api.event.Subscribe; import com.velocitypowered.api.event.proxy.ProxyReloadEvent; diff --git a/plugin/src/main/java/net/elytrium/limboapi/material/Biome.java b/plugin/src/main/java/net/elytrium/limboapi/material/Biome.java deleted file mode 100644 index 6ad152b5..00000000 --- a/plugin/src/main/java/net/elytrium/limboapi/material/Biome.java +++ /dev/null @@ -1,704 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package net.elytrium.limboapi.material; - -import com.velocitypowered.api.network.ProtocolVersion; -import java.util.Arrays; -import java.util.EnumMap; -import java.util.stream.Collectors; -import net.elytrium.limboapi.api.chunk.BuiltInBiome; -import net.elytrium.limboapi.api.chunk.VirtualBiome; -import net.elytrium.limboapi.material.Biome.Effects.MoodSound; -import net.kyori.adventure.nbt.CompoundBinaryTag; -import net.kyori.adventure.nbt.CompoundBinaryTag.Builder; -import net.kyori.adventure.nbt.ListBinaryTag; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; - -public enum Biome implements VirtualBiome { - - PLAINS( - BuiltInBiome.PLAINS, - "minecraft:plains", - 1, - new Element( - true, 0.125F, 0.8F, 0.05F, 0.4F, "plains", - Effects.builder(7907327, 329011, 12638463, 415920) - .moodSound(MoodSound.of(6000, 2.0, 8, "minecraft:ambient.cave")) - .build() - ) - ), - SWAMP( - BuiltInBiome.SWAMP, - "minecraft:swamp", - 6, - new Element( - true, -0.2F, 0.8F, 0.1F, 0.9F, "swamp", - Effects.builder(7907327, 329011, 12638463, 415920) - .grassColorModifier("swamp") - .foliageColor(6975545) - .moodSound(MoodSound.of(6000, 2.0, 8, "minecraft:ambient.cave")) - .build() - ) - ), - SWAMP_HILLS( - BuiltInBiome.SWAMP_HILLS, - "minecraft:swamp_hills", - 134, - new Element( - true, -0.1F, 0.8F, 0.3F, 0.9F, "swamp", - Effects.builder(7907327, 329011, 12638463, 415920) - .grassColorModifier("swamp") - .foliageColor(6975545) - .moodSound(MoodSound.of(6000, 2.0, 8, "minecraft:ambient.cave")) - .build() - ) - ), - NETHER_WASTES( - BuiltInBiome.NETHER_WASTES, - "minecraft:nether_wastes", - 8, - new Element(false, 0.1f, 2.0f, 0.2f, 0.0f, "nether", - Effects.builder(7254527, 329011, 3344392, 4159204) - .moodSound(MoodSound.of(6000, 2.0, 8, "minecraft:ambient.nether_wastes.mood")) - .build() - ) - ), - THE_END( - BuiltInBiome.THE_END, - "minecraft:the_end", - 9, - new Element(false, 0.1f, 0.5f, 0.2f, 0.5f, "the_end", - Effects.builder(0, 10518688, 12638463, 4159204) - .moodSound(MoodSound.of(6000, 2.0, 8, "minecraft:ambient.cave")) - .build() - ) - ); - - private static final EnumMap BUILT_IN_BIOME_MAP = new EnumMap<>(BuiltInBiome.class); - - private final BuiltInBiome index; - private final String name; - private final int id; - private final Element element; - - Biome(BuiltInBiome index, String name, int id, Element element) { - this.index = index; - this.name = name; - this.id = id; - this.element = element; - } - - public CompoundBinaryTag encodeBiome(ProtocolVersion version) { - return CompoundBinaryTag.builder() - .putString("name", this.name) - .putInt("id", this.id) - .put("element", this.element.encode(version)) - .build(); - } - - @Override - public String getName() { - return this.name; - } - - @Override - public int getID() { - return this.id; - } - - public Element getElement() { - return this.element; - } - - static { - for (Biome biome : Biome.values()) { - BUILT_IN_BIOME_MAP.put(biome.index, biome); - } - } - - public static Biome of(BuiltInBiome index) { - return BUILT_IN_BIOME_MAP.get(index); - } - - public static CompoundBinaryTag getRegistry(ProtocolVersion version) { - return CompoundBinaryTag.builder() - .putString("type", "minecraft:worldgen/biome") - .put("value", ListBinaryTag.from(Arrays.stream(Biome.values()).map(biome -> biome.encodeBiome(version)).collect(Collectors.toList()))) - .build(); - } - - public static class Element { - - public final boolean hasPrecipitation; - public final float depth; - public final float temperature; - public final float scale; - public final float downfall; - public final String category; - public final Effects effects; - - public Element(boolean hasPrecipitation, float depth, float temperature, float scale, float downfall, String category, Effects effects) { - this.hasPrecipitation = hasPrecipitation; - this.depth = depth; - this.temperature = temperature; - this.scale = scale; - this.downfall = downfall; - this.category = category; - this.effects = effects; - } - - public CompoundBinaryTag encode(ProtocolVersion version) { - CompoundBinaryTag.Builder tagBuilder = CompoundBinaryTag.builder() - .putFloat("depth", this.depth) - .putFloat("temperature", this.temperature) - .putFloat("scale", this.scale) - .putFloat("downfall", this.downfall) - .putString("category", this.category) - .put("effects", this.effects.encode()); - - if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_4) < 0) { - tagBuilder.putString("precipitation", this.hasPrecipitation ? "rain" : "none"); - } else { - tagBuilder.putBoolean("has_precipitation", this.hasPrecipitation); - } - - return tagBuilder.build(); - } - - public boolean hasPrecipitation() { - return this.hasPrecipitation; - } - - public float getDepth() { - return this.depth; - } - - public float getTemperature() { - return this.temperature; - } - - public float getScale() { - return this.scale; - } - - public float getDownfall() { - return this.downfall; - } - - public String getCategory() { - return this.category; - } - - public Effects getEffects() { - return this.effects; - } - - @Override - public String toString() { - return "Biome.Element{" - + "hasPrecipitation=" + this.hasPrecipitation - + ", depth=" + this.depth - + ", temperature=" + this.temperature - + ", scale=" + this.scale - + ", downfall=" + this.downfall - + ", category=" + this.category - + ", effects=" + this.effects - + "}"; - } - } - - public static class Effects { - - private final int skyColor; - private final int waterFogColor; - private final int fogColor; - private final int waterColor; - - @Nullable - private final Integer foliageColor; - @Nullable - private final String grassColorModifier; - @Nullable - private final Music music; - @Nullable - private final String ambientSound; - @Nullable - private final AdditionsSound additionsSound; - @Nullable - private final MoodSound moodSound; - @Nullable - private final Particle particle; - - public Effects(int skyColor, - int waterFogColor, int fogColor, int waterColor, - @Nullable Integer foliageColor, @Nullable String grassColorModifier, @Nullable Music music, - @Nullable String ambientSound, @Nullable AdditionsSound additionsSound, - @Nullable MoodSound moodSound, @Nullable Particle particle) { - this.skyColor = skyColor; - this.waterFogColor = waterFogColor; - this.fogColor = fogColor; - this.waterColor = waterColor; - this.foliageColor = foliageColor; - this.grassColorModifier = grassColorModifier; - this.music = music; - this.ambientSound = ambientSound; - this.additionsSound = additionsSound; - this.moodSound = moodSound; - this.particle = particle; - } - - public CompoundBinaryTag encode() { - Builder result = CompoundBinaryTag.builder(); - - result.putInt("sky_color", this.skyColor); - result.putInt("water_fog_color", this.waterColor); - result.putInt("fog_color", this.fogColor); - result.putInt("water_color", this.waterColor); - - if (this.foliageColor != null) { - result.putInt("foliage_color", this.foliageColor); - } - - if (this.grassColorModifier != null) { - result.putString("grass_color_modifier", this.grassColorModifier); - } - - if (this.music != null) { - result.put("music", this.music.encode()); - } - - if (this.ambientSound != null) { - result.putString("ambient_sound", this.ambientSound); - } - - if (this.additionsSound != null) { - result.put("additions_sound", this.additionsSound.encode()); - } - - if (this.moodSound != null) { - result.put("mood_sound", this.moodSound.encode()); - } - - if (this.particle != null) { - result.put("particle", this.particle.encode()); - } - - return result.build(); - } - - public static EffectsBuilder builder(int skyColor, int waterFogColor, int fogColor, int waterColor) { - return new EffectsBuilder() - .skyColor(skyColor) - .waterFogColor(waterFogColor) - .fogColor(fogColor) - .waterColor(waterColor); - } - - public int getSkyColor() { - return this.skyColor; - } - - public int getWaterFogColor() { - return this.waterFogColor; - } - - public int getFogColor() { - return this.fogColor; - } - - public int getWaterColor() { - return this.waterColor; - } - - @Nullable - public Integer getFoliageColor() { - return this.foliageColor; - } - - @Nullable - public String getGrassColorModifier() { - return this.grassColorModifier; - } - - @Nullable - public Music getMusic() { - return this.music; - } - - @Nullable - public String getAmbientSound() { - return this.ambientSound; - } - - @Nullable - public AdditionsSound getAdditionsSound() { - return this.additionsSound; - } - - @Nullable - public MoodSound getMoodSound() { - return this.moodSound; - } - - @Nullable - public Particle getParticle() { - return this.particle; - } - - @Override - public String toString() { - return "Biome.Effects{" - + "skyColor=" + this.skyColor - + ", waterFogColor=" + this.waterFogColor - + ", fogColor=" + this.fogColor - + ", waterColor=" + this.waterColor - + ", foliageColor=" + this.foliageColor - + ", grassColorModifier=" + this.grassColorModifier - + ", music=" + this.music - + ", ambientSound=" + this.ambientSound - + ", additionsSound=" + this.additionsSound - + ", moodSound=" + this.moodSound - + ", particle=" + this.particle - + "}"; - } - - public static final class MoodSound { - - private final int tickDelay; - private final double offset; - private final int blockSearchExtent; - @NonNull - private final String sound; - - private MoodSound(int tickDelay, double offset, int blockSearchExtent, @NonNull String sound) { - this.tickDelay = tickDelay; - this.offset = offset; - this.blockSearchExtent = blockSearchExtent; - this.sound = sound; - } - - public static MoodSound of(int tickDelay, double offset, int blockSearchExtent, @NonNull String sound) { - return new MoodSound(tickDelay, offset, blockSearchExtent, sound); - } - - public CompoundBinaryTag encode() { - return CompoundBinaryTag.builder() - .putInt("tick_delay", this.tickDelay) - .putDouble("offset", this.offset) - .putInt("block_search_extent", this.blockSearchExtent) - .putString("sound", this.sound) - .build(); - } - - public int getTickDelay() { - return this.tickDelay; - } - - public double getOffset() { - return this.offset; - } - - public int getBlockSearchExtent() { - return this.blockSearchExtent; - } - - @NonNull - public String getSound() { - return this.sound; - } - - @Override - public String toString() { - return "Biome.Effects.MoodSound{" - + "tickDelay=" + this.tickDelay - + ", offset=" + this.offset - + ", blockSearchExtent=" + this.blockSearchExtent - + ", sound=" + this.sound - + "}"; - } - } - - public static final class Music { - - private final boolean replaceCurrentMusic; - @NonNull - private final String sound; - private final int maxDelay; - private final int minDelay; - - private Music(boolean replaceCurrentMusic, @NonNull String sound, int maxDelay, int minDelay) { - this.replaceCurrentMusic = replaceCurrentMusic; - this.sound = sound; - this.maxDelay = maxDelay; - this.minDelay = minDelay; - } - - public static Music of(boolean replaceCurrentMusic, @NonNull String sound, int maxDelay, int minDelay) { - return new Music(replaceCurrentMusic, sound, maxDelay, minDelay); - } - - public CompoundBinaryTag encode() { - return CompoundBinaryTag.builder() - .putBoolean("replace_current_music", this.replaceCurrentMusic) - .putString("sound", this.sound) - .putInt("max_delay", this.maxDelay) - .putInt("min_delay", this.minDelay) - .build(); - } - - public boolean isReplaceCurrentMusic() { - return this.replaceCurrentMusic; - } - - @NonNull - public String getSound() { - return this.sound; - } - - public int getMaxDelay() { - return this.maxDelay; - } - - public int getMinDelay() { - return this.minDelay; - } - - @Override - public String toString() { - return "Biome.Effects.Music{" - + "replaceCurrentMusic=" + this.replaceCurrentMusic - + ", sound=" + this.sound - + ", maxDelay=" + this.maxDelay - + ", minDelay=" + this.minDelay - + "}"; - } - } - - public static final class AdditionsSound { - - @NonNull - private final String sound; - private final double tickChance; - - private AdditionsSound(@NonNull String sound, double tickChance) { - this.sound = sound; - this.tickChance = tickChance; - } - - public static AdditionsSound of(@NonNull String sound, double tickChance) { - return new AdditionsSound(sound, tickChance); - } - - public CompoundBinaryTag encode() { - return CompoundBinaryTag.builder() - .putString("sound", this.sound) - .putDouble("tick_chance", this.tickChance) - .build(); - } - - @NonNull - public String getSound() { - return this.sound; - } - - public double getTickChance() { - return this.tickChance; - } - - @Override - public String toString() { - return "Biome.Effects.AdditionsSound{" - + "sound=" + this.sound - + ", tickChance=" + this.tickChance - + "}"; - } - } - - public static final class Particle { - - private final float probability; - @NonNull - private final ParticleOptions options; - - private Particle(float probability, @NonNull ParticleOptions options) { - this.probability = probability; - this.options = options; - } - - public static Particle of(float probability, @NonNull ParticleOptions options) { - return new Particle(probability, options); - } - - public CompoundBinaryTag encode() { - return CompoundBinaryTag.builder() - .putFloat("probability", this.probability) - .put("options", this.options.encode()) - .build(); - } - - public float getProbability() { - return this.probability; - } - - @NonNull - public ParticleOptions getOptions() { - return this.options; - } - - @Override - public String toString() { - return "Biome.Effects.Particle{" - + "probability=" + this.probability - + ", options=" + this.options - + "}"; - } - - public static class ParticleOptions { - - @NonNull - private final String type; - - public ParticleOptions(@NonNull String type) { - this.type = type; - } - - public CompoundBinaryTag encode() { - return CompoundBinaryTag.builder() - .putString("type", this.type) - .build(); - } - - @NonNull - public String getType() { - return this.type; - } - - @Override - public String toString() { - return "Biome.Effects.Particle.ParticleOptions{" - + "type=" + this.type - + "}"; - } - } - } - - public static class EffectsBuilder { - - private int skyColor; - private int waterFogColor; - private int fogColor; - private int waterColor; - private Integer foliageColor; - private String grassColorModifier; - private Music music; - private String ambientSound; - private AdditionsSound additionsSound; - private MoodSound moodSound; - private Particle particle; - - public EffectsBuilder skyColor(int skyColor) { - this.skyColor = skyColor; - return this; - } - - public EffectsBuilder waterFogColor(int waterFogColor) { - this.waterFogColor = waterFogColor; - return this; - } - - public EffectsBuilder fogColor(int fogColor) { - this.fogColor = fogColor; - return this; - } - - public EffectsBuilder waterColor(int waterColor) { - this.waterColor = waterColor; - return this; - } - - public EffectsBuilder foliageColor(Integer foliageColor) { - this.foliageColor = foliageColor; - return this; - } - - public EffectsBuilder grassColorModifier(String grassColorModifier) { - this.grassColorModifier = grassColorModifier; - return this; - } - - public EffectsBuilder music(Music music) { - this.music = music; - return this; - } - - public EffectsBuilder ambientSound(String ambientSound) { - this.ambientSound = ambientSound; - return this; - } - - public EffectsBuilder additionsSound(AdditionsSound additionsSound) { - this.additionsSound = additionsSound; - return this; - } - - public EffectsBuilder moodSound(MoodSound moodSound) { - this.moodSound = moodSound; - return this; - } - - public EffectsBuilder particle(Particle particle) { - this.particle = particle; - return this; - } - - public Effects build() { - return new Effects( - this.skyColor, - this.waterFogColor, - this.fogColor, - this.waterColor, - this.foliageColor, - this.grassColorModifier, - this.music, - this.ambientSound, - this.additionsSound, - this.moodSound, - this.particle - ); - } - - @Override - public String toString() { - return "Biome.Effects.EffectsBuilder{" - + "skyColor=" + this.skyColor - + ", waterFogColor=" + this.waterFogColor - + ", fogColor=" + this.fogColor - + ", waterColor=" + this.waterColor - + ", foliageColor=" + this.foliageColor - + ", grassColorModifier=" + this.grassColorModifier - + ", music=" + this.music - + ", ambientSound=" + this.ambientSound - + ", additionsSound=" + this.additionsSound - + ", moodSound=" + this.moodSound - + ", particle=" + this.particle - + "}"; - } - } - } -} diff --git a/plugin/src/main/java/net/elytrium/limboapi/mcprotocollib/BitStorage116.java b/plugin/src/main/java/net/elytrium/limboapi/mcprotocollib/BitStorage116.java index e4c9632a..0ab15218 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/mcprotocollib/BitStorage116.java +++ b/plugin/src/main/java/net/elytrium/limboapi/mcprotocollib/BitStorage116.java @@ -29,31 +29,19 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; import java.util.Arrays; -import net.elytrium.limboapi.api.chunk.util.CompactStorage; +import net.elytrium.limboapi.api.world.chunk.util.CompactStorage; public class BitStorage116 implements CompactStorage { - private static final int[] MAGIC_VALUES = { - -1, -1, 0, Integer.MIN_VALUE, 0, 0, 1431655765, 1431655765, 0, Integer.MIN_VALUE, - 0, 1, 858993459, 858993459, 0, 715827882, 715827882, 0, 613566756, 613566756, - 0, Integer.MIN_VALUE, 0, 2, 477218588, 477218588, 0, 429496729, 429496729, 0, - 390451572, 390451572, 0, 357913941, 357913941, 0, 330382099, 330382099, 0, 306783378, - 306783378, 0, 286331153, 286331153, 0, Integer.MIN_VALUE, 0, 3, 252645135, 252645135, - 0, 238609294, 238609294, 0, 226050910, 226050910, 0, 214748364, 214748364, 0, - 204522252, 204522252, 0, 195225786, 195225786, 0, 186737708, 186737708, 0, 178956970, - 178956970, 0, 171798691, 171798691, 0, 165191049, 165191049, 0, 159072862, 159072862, - 0, 153391689, 153391689, 0, 148102320, 148102320, 0, 143165576, 143165576, 0, - 138547332, 138547332, 0, Integer.MIN_VALUE, 0, 4, 130150524, 130150524, 0, 126322567, - 126322567, 0, 122713351, 122713351, 0, 119304647, 119304647, 0, 116080197, 116080197, - 0, 113025455, 113025455, 0, 110127366, 110127366, 0, 107374182, 107374182, 0, - 104755299, 104755299, 0, 102261126, 102261126, 0, 99882960, 99882960, 0, 97612893, - 97612893, 0, 95443717, 95443717, 0, 93368854, 93368854, 0, 91382282, 91382282, - 0, 89478485, 89478485, 0, 87652393, 87652393, 0, 85899345, 85899345, 0, - 84215045, 84215045, 0, 82595524, 82595524, 0, 81037118, 81037118, 0, 79536431, - 79536431, 0, 78090314, 78090314, 0, 76695844, 76695844, 0, 75350303, 75350303, - 0, 74051160, 74051160, 0, 72796055, 72796055, 0, 71582788, 71582788, 0, - 70409299, 70409299, 0, 69273666, 69273666, 0, 68174084, 68174084, 0, Integer.MIN_VALUE, - 0, 5 + private static final int[] MAGIC = { + -1, -1, 0, Integer.MIN_VALUE, 0, 0, 1431655765, 1431655765, 0, Integer.MIN_VALUE, 0, 1, 858993459, 858993459, 0, 715827882, 715827882, 0, 613566756, 613566756, 0, Integer.MIN_VALUE, 0, 2, 477218588, + 477218588, 0, 429496729, 429496729, 0, 390451572, 390451572, 0, 357913941, 357913941, 0, 330382099, 330382099, 0, 306783378, 306783378, 0, 286331153, 286331153, 0, Integer.MIN_VALUE, 0, 3, 252645135, + 252645135, 0, 238609294, 238609294, 0, 226050910, 226050910, 0, 214748364, 214748364, 0, 204522252, 204522252, 0, 195225786, 195225786, 0, 186737708, 186737708, 0, 178956970, 178956970, 0, 171798691, + 171798691, 0, 165191049, 165191049, 0, 159072862, 159072862, 0, 153391689, 153391689, 0, 148102320, 148102320, 0, 143165576, 143165576, 0, 138547332, 138547332, 0, Integer.MIN_VALUE, 0, 4, 130150524, + 130150524, 0, 126322567, 126322567, 0, 122713351, 122713351, 0, 119304647, 119304647, 0, 116080197, 116080197, 0, 113025455, 113025455, 0, 110127366, 110127366, 0, 107374182, 107374182, 0, 104755299, + 104755299, 0, 102261126, 102261126, 0, 99882960, 99882960, 0, 97612893, 97612893, 0, 95443717, 95443717, 0, 93368854, 93368854, 0, 91382282, 91382282, 0, 89478485, 89478485, 0, 87652393, 87652393, 0, + 85899345, 85899345, 0, 84215045, 84215045, 0, 82595524, 82595524, 0, 81037118, 81037118, 0, 79536431, 79536431, 0, 78090314, 78090314, 0, 76695844, 76695844, 0, 75350303, 75350303, 0, 74051160, + 74051160, 0, 72796055, 72796055, 0, 71582788, 71582788, 0, 70409299, 70409299, 0, 69273666, 69273666, 0, 68174084, 68174084, 0, Integer.MIN_VALUE, 0, 5 }; private final long[] data; @@ -72,7 +60,7 @@ public BitStorage116(int bitsPerEntry, int size) { public BitStorage116(int bitsPerEntry, int size, long[] data) { if (bitsPerEntry < 1 || bitsPerEntry > 32) { - throw new IllegalArgumentException("bitsPerEntry must be between 1 and 32, inclusive."); + throw new IllegalArgumentException("bitsPerEntry must be between 1 and 32, inclusive"); } this.bitsPerEntry = bitsPerEntry; @@ -81,20 +69,20 @@ public BitStorage116(int bitsPerEntry, int size, long[] data) { this.maxValue = (1L << bitsPerEntry) - 1L; this.valuesPerLong = (char) (64 / bitsPerEntry); int expectedLength = (size + this.valuesPerLong - 1) / this.valuesPerLong; - if (data != null) { + if (data == null) { + this.data = new long[expectedLength]; + } else { if (data.length != expectedLength) { throw new IllegalArgumentException("Expected " + expectedLength + " longs but got " + data.length + " longs"); } - this.data = Arrays.copyOf(data, data.length); - } else { - this.data = new long[expectedLength]; + this.data = data; } int magicIndex = 3 * (this.valuesPerLong - 1); - this.divideMultiply = Integer.toUnsignedLong(MAGIC_VALUES[magicIndex]); - this.divideAdd = Integer.toUnsignedLong(MAGIC_VALUES[magicIndex + 1]); - this.divideShift = MAGIC_VALUES[magicIndex + 2]; + this.divideMultiply = Integer.toUnsignedLong(BitStorage116.MAGIC[magicIndex]); + this.divideAdd = Integer.toUnsignedLong(BitStorage116.MAGIC[magicIndex + 1]); + this.divideShift = BitStorage116.MAGIC[magicIndex + 2]; } @Override @@ -102,7 +90,7 @@ public void set(int index, int value) { if (index < 0 || index > this.size - 1) { throw new IndexOutOfBoundsException(); } else if (value < 0 || value > this.maxValue) { - throw new IllegalArgumentException("Value cannot be outside of accepted range."); + throw new IllegalArgumentException("Value cannot be outside of accepted range"); } else { int cellIndex = this.cellIndex(index); int bitIndex = this.bitIndex(index, cellIndex); @@ -122,14 +110,14 @@ public int get(int index) { } @Override - public void write(Object byteBufObject, ProtocolVersion version) { - Preconditions.checkArgument(byteBufObject instanceof ByteBuf); - ByteBuf buf = (ByteBuf) byteBufObject; - if (version.compareTo(ProtocolVersion.MINECRAFT_1_21_5) < 0) { + public void write(Object bufObj, ProtocolVersion version) { + Preconditions.checkArgument(bufObj instanceof ByteBuf); + ByteBuf buf = (ByteBuf) bufObj; + if (version.lessThan(ProtocolVersion.MINECRAFT_1_21_5)) { ProtocolUtils.writeVarInt(buf, this.data.length); } - for (long l : this.data) { - buf.writeLong(l); + for (long value : this.data) { + buf.writeLong(value); } } @@ -140,11 +128,9 @@ public int getBitsPerEntry() { @Override public int getDataLength(ProtocolVersion version) { - if (version.compareTo(ProtocolVersion.MINECRAFT_1_21_5) >= 0) { - return this.data.length * 8; - } - - return ProtocolUtils.varIntBytes(this.data.length) + this.data.length * 8; + return version.noLessThan(ProtocolVersion.MINECRAFT_1_21_5) + ? this.data.length * 8 + : ProtocolUtils.varIntBytes(this.data.length) + this.data.length * 8; } @Override diff --git a/plugin/src/main/java/net/elytrium/limboapi/mcprotocollib/BitStorage19.java b/plugin/src/main/java/net/elytrium/limboapi/mcprotocollib/BitStorage19.java index 054fa58d..9d0b2e28 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/mcprotocollib/BitStorage19.java +++ b/plugin/src/main/java/net/elytrium/limboapi/mcprotocollib/BitStorage19.java @@ -29,7 +29,8 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; import java.util.Arrays; -import net.elytrium.limboapi.api.chunk.util.CompactStorage; +import net.elytrium.limboapi.api.world.chunk.util.CompactStorage; +import net.elytrium.limboapi.protocol.util.LimboProtocolUtils; public class BitStorage19 implements CompactStorage { @@ -39,7 +40,7 @@ public class BitStorage19 implements CompactStorage { private final long maxEntryValue; public BitStorage19(int bitsPerEntry, int size) { - this(bitsPerEntry, new long[((size * bitsPerEntry - 1) >> 6) + 1]); + this(bitsPerEntry, new long[((size * Math.max(bitsPerEntry, 4) - 1) >> 6) + 1]); } public BitStorage19(int bitsPerEntry, long[] data) { @@ -64,7 +65,7 @@ public void set(int index, int value) { int bitIndex = index * this.bitsPerEntry; int startIndex = bitIndex >> 6; int endIndex = ((index + 1) * this.bitsPerEntry - 1) >> 6; - int startBitSubIndex = bitIndex & 63; + int startBitSubIndex = bitIndex & 0x3F; this.data[startIndex] = this.data[startIndex] & ~(this.maxEntryValue << startBitSubIndex) | ((long) value & this.maxEntryValue) << startBitSubIndex; if (startIndex != endIndex) { int endBitSubIndex = 64 - startBitSubIndex; @@ -81,24 +82,17 @@ public int get(int index) { int bitIndex = index * this.bitsPerEntry; int startIndex = bitIndex >> 6; int endIndex = ((index + 1) * this.bitsPerEntry - 1) >> 6; - int startBitSubIndex = bitIndex & 63; - if (startIndex == endIndex) { - return (int) (this.data[startIndex] >>> startBitSubIndex & this.maxEntryValue); - } else { - int endBitSubIndex = 64 - startBitSubIndex; - return (int) ((this.data[startIndex] >>> startBitSubIndex | this.data[endIndex] << endBitSubIndex) & this.maxEntryValue); - } + int startBitSubIndex = bitIndex & 0x3F; + return (int) (startIndex == endIndex + ? (this.data[startIndex] >>> startBitSubIndex & this.maxEntryValue) + : (this.data[startIndex] >>> startBitSubIndex | this.data[endIndex] << 64 - startBitSubIndex) & this.maxEntryValue); } } @Override - public void write(Object byteBufObject, ProtocolVersion version) { - Preconditions.checkArgument(byteBufObject instanceof ByteBuf); - ByteBuf buf = (ByteBuf) byteBufObject; - ProtocolUtils.writeVarInt(buf, this.data.length); - for (long l : this.data) { - buf.writeLong(l); - } + public void write(Object bufObj, ProtocolVersion version) { + Preconditions.checkArgument(bufObj instanceof ByteBuf); + LimboProtocolUtils.writeLongArray((ByteBuf) bufObj, this.data); } @Override diff --git a/plugin/src/main/java/net/elytrium/limboapi/protocol/LimboProtocol.java b/plugin/src/main/java/net/elytrium/limboapi/protocol/LimboProtocol.java index c4666ceb..29171f5b 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/protocol/LimboProtocol.java +++ b/plugin/src/main/java/net/elytrium/limboapi/protocol/LimboProtocol.java @@ -25,8 +25,6 @@ import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; import java.lang.reflect.Field; import java.util.Arrays; import java.util.Collections; @@ -37,38 +35,37 @@ import java.util.stream.Stream; import net.elytrium.commons.utils.reflection.ReflectionException; import net.elytrium.limboapi.api.protocol.PacketDirection; -import net.elytrium.limboapi.api.protocol.packets.PacketMapping; -import net.elytrium.limboapi.api.utils.OverlayMap; +import net.elytrium.limboapi.api.protocol.PacketMapping; +import net.elytrium.limboapi.utils.overlay.OverlayMap; +import net.elytrium.limboapi.protocol.packets.c2s.AcceptTeleportationPacket; +import net.elytrium.limboapi.protocol.packets.c2s.ChatSessionUpdatePacket; import net.elytrium.limboapi.protocol.packets.c2s.MoveOnGroundOnlyPacket; import net.elytrium.limboapi.protocol.packets.c2s.MovePacket; import net.elytrium.limboapi.protocol.packets.c2s.MovePositionOnlyPacket; import net.elytrium.limboapi.protocol.packets.c2s.MoveRotationOnlyPacket; -import net.elytrium.limboapi.protocol.packets.c2s.PlayerChatSessionPacket; -import net.elytrium.limboapi.protocol.packets.c2s.TeleportConfirmPacket; -import net.elytrium.limboapi.protocol.packets.s2c.ChangeGameStatePacket; +import net.elytrium.limboapi.protocol.packets.s2c.BlockEntityDataPacket; import net.elytrium.limboapi.protocol.packets.s2c.ChunkDataPacket; import net.elytrium.limboapi.protocol.packets.s2c.ChunkUnloadPacket; import net.elytrium.limboapi.protocol.packets.s2c.DefaultSpawnPositionPacket; +import net.elytrium.limboapi.protocol.packets.s2c.GameEventPacket; +import net.elytrium.limboapi.protocol.packets.s2c.LightUpdatePacket; import net.elytrium.limboapi.protocol.packets.s2c.MapDataPacket; import net.elytrium.limboapi.protocol.packets.s2c.PlayerAbilitiesPacket; -import net.elytrium.limboapi.protocol.packets.s2c.PositionRotationPacket; +import net.elytrium.limboapi.protocol.packets.s2c.PlayerPositionPacket; +import net.elytrium.limboapi.protocol.packets.s2c.SetChunkCacheCenterPacket; +import net.elytrium.limboapi.protocol.packets.s2c.SetEntityDataPacket; import net.elytrium.limboapi.protocol.packets.s2c.SetExperiencePacket; import net.elytrium.limboapi.protocol.packets.s2c.SetSlotPacket; -import net.elytrium.limboapi.protocol.packets.s2c.TimeUpdatePacket; +import net.elytrium.limboapi.protocol.packets.s2c.SetTimePacket; +import net.elytrium.limboapi.protocol.packets.s2c.UpdateSignPacket; import net.elytrium.limboapi.protocol.packets.s2c.UpdateTagsPacket; -import net.elytrium.limboapi.protocol.packets.s2c.UpdateViewPositionPacket; -import net.elytrium.limboapi.utils.OverlayIntObjectMap; -import net.elytrium.limboapi.utils.OverlayObject2IntMap; -import sun.misc.Unsafe; +import net.elytrium.limboapi.utils.overlay.OverlayIntObjectMap; +import net.elytrium.limboapi.utils.overlay.OverlayObject2IntMap; +import net.elytrium.limboapi.utils.Reflection; @SuppressWarnings("unchecked") public class LimboProtocol { - private static final StateRegistry LIMBO_STATE_REGISTRY; - private static final MethodHandle REGISTER_METHOD; - private static final MethodHandle PACKET_MAPPING_CONSTRUCTOR; - private static final Unsafe UNSAFE; - public static final String READ_TIMEOUT = "limboapi-read-timeout"; public static final MethodHandle VERSIONS_GETTER; @@ -83,34 +80,28 @@ public class LimboProtocol { public static final StateRegistry.PacketRegistry LIMBO_SERVERBOUND_REGISTRY; public static final MethodHandle SERVERBOUND_REGISTRY_GETTER; public static final MethodHandle CLIENTBOUND_REGISTRY_GETTER; + private static final StateRegistry LIMBO_STATE_REGISTRY; + private static final MethodHandle REGISTER_METHOD; + private static final MethodHandle PACKET_MAPPING_CONSTRUCTOR; static { try { - Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); - unsafeField.setAccessible(true); - UNSAFE = (Unsafe) unsafeField.get(null); - - LIMBO_STATE_REGISTRY = (StateRegistry) UNSAFE.allocateInstance(StateRegistry.class); + LIMBO_STATE_REGISTRY = (StateRegistry) Reflection.UNSAFE.allocateInstance(StateRegistry.class); - VERSIONS_GETTER = MethodHandles.privateLookupIn(StateRegistry.PacketRegistry.class, MethodHandles.lookup()) - .findGetter(StateRegistry.PacketRegistry.class, "versions", Map.class); + VERSIONS_GETTER = Reflection.findGetter(StateRegistry.PacketRegistry.class, "versions", Map.class); VERSIONS_FIELD = StateRegistry.PacketRegistry.class.getDeclaredField("versions"); VERSIONS_FIELD.setAccessible(true); - PACKET_ID_TO_SUPPLIER_GETTER = MethodHandles.privateLookupIn(StateRegistry.PacketRegistry.ProtocolRegistry.class, MethodHandles.lookup()) - .findGetter(StateRegistry.PacketRegistry.ProtocolRegistry.class, "packetIdToSupplier", IntObjectMap.class); + PACKET_ID_TO_SUPPLIER_GETTER = Reflection.findGetter(StateRegistry.PacketRegistry.ProtocolRegistry.class, "packetIdToSupplier", IntObjectMap.class); PACKET_ID_TO_SUPPLIER_FIELD = StateRegistry.PacketRegistry.ProtocolRegistry.class.getDeclaredField("packetIdToSupplier"); PACKET_ID_TO_SUPPLIER_FIELD.setAccessible(true); - PACKET_CLASS_TO_ID_GETTER = MethodHandles.privateLookupIn(StateRegistry.PacketRegistry.ProtocolRegistry.class, MethodHandles.lookup()) - .findGetter(StateRegistry.PacketRegistry.ProtocolRegistry.class, "packetClassToId", Object2IntMap.class); + PACKET_CLASS_TO_ID_GETTER = Reflection.findGetter(StateRegistry.PacketRegistry.ProtocolRegistry.class, "packetClassToId", Object2IntMap.class); PACKET_CLASS_TO_ID_FIELD = StateRegistry.PacketRegistry.ProtocolRegistry.class.getDeclaredField("packetClassToId"); PACKET_CLASS_TO_ID_FIELD.setAccessible(true); - CLIENTBOUND_REGISTRY_GETTER = MethodHandles.privateLookupIn(StateRegistry.class, MethodHandles.lookup()) - .findGetter(StateRegistry.class, "clientbound", StateRegistry.PacketRegistry.class); - SERVERBOUND_REGISTRY_GETTER = MethodHandles.privateLookupIn(StateRegistry.class, MethodHandles.lookup()) - .findGetter(StateRegistry.class, "serverbound", StateRegistry.PacketRegistry.class); + CLIENTBOUND_REGISTRY_GETTER = Reflection.findGetter(StateRegistry.class, "clientbound", StateRegistry.PacketRegistry.class); + SERVERBOUND_REGISTRY_GETTER = Reflection.findGetter(StateRegistry.class, "serverbound", StateRegistry.PacketRegistry.class); PLAY_CLIENTBOUND_REGISTRY = (StateRegistry.PacketRegistry) CLIENTBOUND_REGISTRY_GETTER.invokeExact(StateRegistry.PLAY); PLAY_SERVERBOUND_REGISTRY = (StateRegistry.PacketRegistry) SERVERBOUND_REGISTRY_GETTER.invokeExact(StateRegistry.PLAY); @@ -121,21 +112,16 @@ public class LimboProtocol { LIMBO_CLIENTBOUND_REGISTRY = (StateRegistry.PacketRegistry) CLIENTBOUND_REGISTRY_GETTER.invokeExact(LIMBO_STATE_REGISTRY); LIMBO_SERVERBOUND_REGISTRY = (StateRegistry.PacketRegistry) SERVERBOUND_REGISTRY_GETTER.invokeExact(LIMBO_STATE_REGISTRY); - REGISTER_METHOD = MethodHandles.privateLookupIn(StateRegistry.PacketRegistry.class, MethodHandles.lookup()) - .findVirtual(StateRegistry.PacketRegistry.class, "register", - MethodType.methodType(void.class, Class.class, Supplier.class, StateRegistry.PacketMapping[].class)); + REGISTER_METHOD = Reflection.findVirtualVoid(StateRegistry.PacketRegistry.class, "register", Class.class, Supplier.class, StateRegistry.PacketMapping[].class); - PACKET_MAPPING_CONSTRUCTOR = MethodHandles.privateLookupIn(StateRegistry.PacketRegistry.class, MethodHandles.lookup()) - .findConstructor(StateRegistry.PacketMapping.class, - MethodType.methodType(void.class, int.class, ProtocolVersion.class, ProtocolVersion.class, boolean.class)); - } catch (Throwable e) { - throw new ReflectionException(e); + PACKET_MAPPING_CONSTRUCTOR = Reflection.findConstructor(StateRegistry.PacketMapping.class, int.class, ProtocolVersion.class, ProtocolVersion.class, boolean.class); + } catch (Throwable t) { + throw new ReflectionException(t); } } - private static void overlayRegistry(StateRegistry stateRegistry, - String registryName, StateRegistry.PacketRegistry playRegistry) throws Throwable { - StateRegistry.PacketRegistry registry = (StateRegistry.PacketRegistry) UNSAFE.allocateInstance(StateRegistry.PacketRegistry.class); + private static void overlayRegistry(StateRegistry stateRegistry, String registryName, StateRegistry.PacketRegistry playRegistry) throws Throwable { + StateRegistry.PacketRegistry registry = (StateRegistry.PacketRegistry) Reflection.UNSAFE.allocateInstance(StateRegistry.PacketRegistry.class); Field directionField = StateRegistry.PacketRegistry.class.getDeclaredField("direction"); directionField.setAccessible(true); @@ -144,25 +130,24 @@ private static void overlayRegistry(StateRegistry stateRegistry, Field versionField = StateRegistry.PacketRegistry.ProtocolRegistry.class.getDeclaredField("version"); versionField.setAccessible(true); - // Overlay packets from PLAY state registry. - // P.S. I hate it when someone uses var in code, but there I had no choice. - var playProtocolRegistryVersions = - (Map) VERSIONS_GETTER.invokeExact(playRegistry); - Map versions = new EnumMap<>(ProtocolVersion.class); + // Overlay packets from PLAY state registry + var playProtocolRegistryVersions = (Map) VERSIONS_GETTER.invokeExact(playRegistry); + EnumMap versions = new EnumMap<>(ProtocolVersion.class); for (ProtocolVersion version : ProtocolVersion.values()) { if (!version.isLegacy() && !version.isUnknown()) { StateRegistry.PacketRegistry.ProtocolRegistry playProtoRegistry = playProtocolRegistryVersions.get(version); - var protoRegistry = (StateRegistry.PacketRegistry.ProtocolRegistry) UNSAFE.allocateInstance(StateRegistry.PacketRegistry.ProtocolRegistry.class); + var protoRegistry = (StateRegistry.PacketRegistry.ProtocolRegistry) Reflection.UNSAFE.allocateInstance(StateRegistry.PacketRegistry.ProtocolRegistry.class); versionField.set(protoRegistry, version); - var playPacketIDToSupplier = (IntObjectMap>) PACKET_ID_TO_SUPPLIER_GETTER.invokeExact(playProtoRegistry); - PACKET_ID_TO_SUPPLIER_FIELD.set(protoRegistry, new OverlayIntObjectMap<>(playPacketIDToSupplier, new IntObjectHashMap<>(16, 0.5F))); - - var playPacketClassToID = (Object2IntMap>) PACKET_CLASS_TO_ID_GETTER.invokeExact(playProtoRegistry); - Object2IntMap> packetClassToID = new Object2IntOpenHashMap<>(16, 0.5F); - packetClassToID.defaultReturnValue(playPacketClassToID.defaultReturnValue()); - PACKET_CLASS_TO_ID_FIELD.set(protoRegistry, new OverlayObject2IntMap<>(playPacketClassToID, packetClassToID)); + PACKET_ID_TO_SUPPLIER_FIELD.set(protoRegistry, new OverlayIntObjectMap<>( + (IntObjectMap>) PACKET_ID_TO_SUPPLIER_GETTER.invokeExact(playProtoRegistry), + new IntObjectHashMap<>(16, 0.5F) + )); + PACKET_CLASS_TO_ID_FIELD.set(protoRegistry, new OverlayObject2IntMap<>( + (Object2IntMap>) PACKET_CLASS_TO_ID_GETTER.invokeExact(playProtoRegistry), + new Object2IntOpenHashMap<>(16, 0.5F) + )); versions.put(version, protoRegistry); } @@ -181,7 +166,7 @@ private static void overlayRegistry(StateRegistry stateRegistry, public static void init() throws Throwable { register(LIMBO_STATE_REGISTRY, PacketDirection.CLIENTBOUND, - ChangeGameStatePacket.class, ChangeGameStatePacket::new, + GameEventPacket.class, null, createMapping(0x2B, ProtocolVersion.MINECRAFT_1_7_2, true), createMapping(0x1E, ProtocolVersion.MINECRAFT_1_9, true), createMapping(0x20, ProtocolVersion.MINECRAFT_1_13, true), @@ -197,10 +182,11 @@ public static void init() throws Throwable { createMapping(0x20, ProtocolVersion.MINECRAFT_1_20_2, true), createMapping(0x22, ProtocolVersion.MINECRAFT_1_20_5, true), createMapping(0x23, ProtocolVersion.MINECRAFT_1_21_2, true), - createMapping(0x22, ProtocolVersion.MINECRAFT_1_21_5, true) + createMapping(0x22, ProtocolVersion.MINECRAFT_1_21_5, true), + createMapping(0x26, ProtocolVersion.MINECRAFT_1_21_9, true) ); register(LIMBO_STATE_REGISTRY, PacketDirection.CLIENTBOUND, - ChunkDataPacket.class, ChunkDataPacket::new, + ChunkDataPacket.class, null, createMapping(0x21, ProtocolVersion.MINECRAFT_1_7_2, true), createMapping(0x20, ProtocolVersion.MINECRAFT_1_9, true), createMapping(0x22, ProtocolVersion.MINECRAFT_1_13, true), @@ -216,10 +202,44 @@ public static void init() throws Throwable { createMapping(0x25, ProtocolVersion.MINECRAFT_1_20_2, true), createMapping(0x27, ProtocolVersion.MINECRAFT_1_20_5, true), createMapping(0x28, ProtocolVersion.MINECRAFT_1_21_2, true), - createMapping(0x27, ProtocolVersion.MINECRAFT_1_21_5, true) + createMapping(0x27, ProtocolVersion.MINECRAFT_1_21_5, true), + createMapping(0x2C, ProtocolVersion.MINECRAFT_1_21_9, true) ); register(LIMBO_STATE_REGISTRY, PacketDirection.CLIENTBOUND, - ChunkUnloadPacket.class, ChunkUnloadPacket::new, + LightUpdatePacket.class, null, + createMapping(0x24, ProtocolVersion.MINECRAFT_1_14, true), + createMapping(0x25, ProtocolVersion.MINECRAFT_1_15, true), + createMapping(0x24, ProtocolVersion.MINECRAFT_1_16, true), + createMapping(0x23, ProtocolVersion.MINECRAFT_1_16_2, true), + createMapping(0x25, ProtocolVersion.MINECRAFT_1_17, true), + createMapping(0x22, ProtocolVersion.MINECRAFT_1_19, true), + createMapping(0x24, ProtocolVersion.MINECRAFT_1_19_1, true), + createMapping(0x23, ProtocolVersion.MINECRAFT_1_19_3, true), + createMapping(0x27, ProtocolVersion.MINECRAFT_1_19_4, true), + createMapping(0x28, ProtocolVersion.MINECRAFT_1_20_2, true), + createMapping(0x2A, ProtocolVersion.MINECRAFT_1_20_5, true), + createMapping(0x2B, ProtocolVersion.MINECRAFT_1_21_2, true), + createMapping(0x2A, ProtocolVersion.MINECRAFT_1_21_5, true), + createMapping(0x2F, ProtocolVersion.MINECRAFT_1_21_9, true) + ); + register(LIMBO_STATE_REGISTRY, PacketDirection.CLIENTBOUND, + BlockEntityDataPacket.class, null, + createMapping(0x35, ProtocolVersion.MINECRAFT_1_7_2, true), + createMapping(0x09, ProtocolVersion.MINECRAFT_1_9, true), + createMapping(0x0A, ProtocolVersion.MINECRAFT_1_15, true), + createMapping(0x09, ProtocolVersion.MINECRAFT_1_16, true), + createMapping(0x0A, ProtocolVersion.MINECRAFT_1_17, true), + createMapping(0x07, ProtocolVersion.MINECRAFT_1_19, true), + createMapping(0x08, ProtocolVersion.MINECRAFT_1_19_4, true), + createMapping(0x07, ProtocolVersion.MINECRAFT_1_20_2, true) + ); + register(LIMBO_STATE_REGISTRY, PacketDirection.CLIENTBOUND, + UpdateSignPacket.class, null, + createMapping(0x33, ProtocolVersion.MINECRAFT_1_7_2, true), + createMapping(0x46, ProtocolVersion.MINECRAFT_1_9, ProtocolVersion.MINECRAFT_1_9_2, true) + ); + register(LIMBO_STATE_REGISTRY, PacketDirection.CLIENTBOUND, + ChunkUnloadPacket.class, null, // on <=1.8, there is no ChunkUnload; its role is handled by specially encoded ChunkData, so the id will be the same as for ChunkData createMapping(0x21, ProtocolVersion.MINECRAFT_1_7_2, true), createMapping(0x1D, ProtocolVersion.MINECRAFT_1_9, true), @@ -236,10 +256,11 @@ public static void init() throws Throwable { createMapping(0x1F, ProtocolVersion.MINECRAFT_1_20_2, true), createMapping(0x21, ProtocolVersion.MINECRAFT_1_20_5, true), createMapping(0x22, ProtocolVersion.MINECRAFT_1_21_2, true), - createMapping(0x21, ProtocolVersion.MINECRAFT_1_21_5, true) + createMapping(0x21, ProtocolVersion.MINECRAFT_1_21_5, true), + createMapping(0x25, ProtocolVersion.MINECRAFT_1_21_9, true) ); register(LIMBO_STATE_REGISTRY, PacketDirection.CLIENTBOUND, - DefaultSpawnPositionPacket.class, DefaultSpawnPositionPacket::new, + DefaultSpawnPositionPacket.class, null, createMapping(0x05, ProtocolVersion.MINECRAFT_1_7_2, true), createMapping(0x43, ProtocolVersion.MINECRAFT_1_9, true), createMapping(0x45, ProtocolVersion.MINECRAFT_1_12, true), @@ -257,10 +278,11 @@ public static void init() throws Throwable { createMapping(0x54, ProtocolVersion.MINECRAFT_1_20_3, true), createMapping(0x56, ProtocolVersion.MINECRAFT_1_20_5, true), createMapping(0x5B, ProtocolVersion.MINECRAFT_1_21_2, true), - createMapping(0x5A, ProtocolVersion.MINECRAFT_1_21_5, true) + createMapping(0x5A, ProtocolVersion.MINECRAFT_1_21_5, true), + createMapping(0x5F, ProtocolVersion.MINECRAFT_1_21_9, true) ); register(LIMBO_STATE_REGISTRY, PacketDirection.CLIENTBOUND, - MapDataPacket.class, MapDataPacket::new, + MapDataPacket.class, null, createMapping(0x34, ProtocolVersion.MINECRAFT_1_7_2, true), createMapping(0x24, ProtocolVersion.MINECRAFT_1_9, true), createMapping(0x26, ProtocolVersion.MINECRAFT_1_13, true), @@ -275,10 +297,11 @@ public static void init() throws Throwable { createMapping(0x2A, ProtocolVersion.MINECRAFT_1_20_2, true), createMapping(0x2C, ProtocolVersion.MINECRAFT_1_20_5, true), createMapping(0x2D, ProtocolVersion.MINECRAFT_1_21_2, true), - createMapping(0x2C, ProtocolVersion.MINECRAFT_1_21_5, true) + createMapping(0x2C, ProtocolVersion.MINECRAFT_1_21_5, true), + createMapping(0x31, ProtocolVersion.MINECRAFT_1_21_9, true) ); register(LIMBO_STATE_REGISTRY, PacketDirection.CLIENTBOUND, - PlayerAbilitiesPacket.class, PlayerAbilitiesPacket::new, + PlayerAbilitiesPacket.class, null, createMapping(0x39, ProtocolVersion.MINECRAFT_1_7_2, true), createMapping(0x2B, ProtocolVersion.MINECRAFT_1_9, true), createMapping(0x2C, ProtocolVersion.MINECRAFT_1_12_1, true), @@ -295,10 +318,11 @@ public static void init() throws Throwable { createMapping(0x36, ProtocolVersion.MINECRAFT_1_20_2, true), createMapping(0x38, ProtocolVersion.MINECRAFT_1_20_5, true), createMapping(0x3A, ProtocolVersion.MINECRAFT_1_21_2, true), - createMapping(0x39, ProtocolVersion.MINECRAFT_1_21_5, true) + createMapping(0x39, ProtocolVersion.MINECRAFT_1_21_5, true), + createMapping(0x3E, ProtocolVersion.MINECRAFT_1_21_9, true) ); register(LIMBO_STATE_REGISTRY, PacketDirection.CLIENTBOUND, - PositionRotationPacket.class, PositionRotationPacket::new, + PlayerPositionPacket.class, null, createMapping(0x08, ProtocolVersion.MINECRAFT_1_7_2, true), createMapping(0x2E, ProtocolVersion.MINECRAFT_1_9, true), createMapping(0x2F, ProtocolVersion.MINECRAFT_1_12_1, true), @@ -315,10 +339,11 @@ public static void init() throws Throwable { createMapping(0x3E, ProtocolVersion.MINECRAFT_1_20_2, true), createMapping(0x40, ProtocolVersion.MINECRAFT_1_20_5, true), createMapping(0x42, ProtocolVersion.MINECRAFT_1_21_2, true), - createMapping(0x41, ProtocolVersion.MINECRAFT_1_21_5, true) + createMapping(0x41, ProtocolVersion.MINECRAFT_1_21_5, true), + createMapping(0x46, ProtocolVersion.MINECRAFT_1_21_9, true) ); register(LIMBO_STATE_REGISTRY, PacketDirection.CLIENTBOUND, - SetExperiencePacket.class, SetExperiencePacket::new, + SetExperiencePacket.class, null, createMapping(0x1F, ProtocolVersion.MINECRAFT_1_7_2, true), createMapping(0x3D, ProtocolVersion.MINECRAFT_1_9, true), createMapping(0x3F, ProtocolVersion.MINECRAFT_1_12, true), @@ -334,10 +359,11 @@ public static void init() throws Throwable { createMapping(0x5A, ProtocolVersion.MINECRAFT_1_20_3, true), createMapping(0x5C, ProtocolVersion.MINECRAFT_1_20_5, true), createMapping(0x61, ProtocolVersion.MINECRAFT_1_21_2, true), - createMapping(0x60, ProtocolVersion.MINECRAFT_1_21_5, true) + createMapping(0x60, ProtocolVersion.MINECRAFT_1_21_5, true), + createMapping(0x65, ProtocolVersion.MINECRAFT_1_21_9, true) ); register(LIMBO_STATE_REGISTRY, PacketDirection.CLIENTBOUND, - SetSlotPacket.class, SetSlotPacket::new, + SetSlotPacket.class, null, createMapping(0x2F, ProtocolVersion.MINECRAFT_1_7_2, true), createMapping(0x16, ProtocolVersion.MINECRAFT_1_9, true), createMapping(0x17, ProtocolVersion.MINECRAFT_1_13, true), @@ -353,7 +379,7 @@ public static void init() throws Throwable { createMapping(0x14, ProtocolVersion.MINECRAFT_1_21_5, true) ); register(LIMBO_STATE_REGISTRY, PacketDirection.CLIENTBOUND, - TimeUpdatePacket.class, TimeUpdatePacket::new, + SetTimePacket.class, null, createMapping(0x03, ProtocolVersion.MINECRAFT_1_7_2, true), createMapping(0x44, ProtocolVersion.MINECRAFT_1_9, true), createMapping(0x46, ProtocolVersion.MINECRAFT_1_12, true), @@ -371,10 +397,11 @@ public static void init() throws Throwable { createMapping(0x62, ProtocolVersion.MINECRAFT_1_20_3, true), createMapping(0x64, ProtocolVersion.MINECRAFT_1_20_5, true), createMapping(0x6B, ProtocolVersion.MINECRAFT_1_21_2, true), - createMapping(0x6A, ProtocolVersion.MINECRAFT_1_21_5, true) + createMapping(0x6A, ProtocolVersion.MINECRAFT_1_21_5, true), + createMapping(0x6F, ProtocolVersion.MINECRAFT_1_21_9, true) ); register(LIMBO_STATE_REGISTRY, PacketDirection.CLIENTBOUND, - UpdateViewPositionPacket.class, UpdateViewPositionPacket::new, // ViewCentre, ChunkRenderDistanceCenter + SetChunkCacheCenterPacket.class, null, // Also known as ViewCentre, ChunkRenderDistanceCenter createMapping(0x40, ProtocolVersion.MINECRAFT_1_14, true), createMapping(0x41, ProtocolVersion.MINECRAFT_1_15, true), createMapping(0x40, ProtocolVersion.MINECRAFT_1_16, true), @@ -387,10 +414,11 @@ public static void init() throws Throwable { createMapping(0x52, ProtocolVersion.MINECRAFT_1_20_3, true), createMapping(0x54, ProtocolVersion.MINECRAFT_1_20_5, true), createMapping(0x58, ProtocolVersion.MINECRAFT_1_21_2, true), - createMapping(0x57, ProtocolVersion.MINECRAFT_1_21_5, true) + createMapping(0x57, ProtocolVersion.MINECRAFT_1_21_5, true), + createMapping(0x5C, ProtocolVersion.MINECRAFT_1_21_9, true) ); register(LIMBO_STATE_REGISTRY, PacketDirection.CLIENTBOUND, - UpdateTagsPacket.class, UpdateTagsPacket::new, + UpdateTagsPacket.class, null, createMapping(0x55, ProtocolVersion.MINECRAFT_1_13, true), createMapping(0x5B, ProtocolVersion.MINECRAFT_1_14, true), createMapping(0x5C, ProtocolVersion.MINECRAFT_1_15, true), @@ -404,7 +432,8 @@ public static void init() throws Throwable { createMapping(0x70, ProtocolVersion.MINECRAFT_1_20_2, true), createMapping(0x74, ProtocolVersion.MINECRAFT_1_20_3, true), createMapping(0x78, ProtocolVersion.MINECRAFT_1_20_5, true), - createMapping(0x7F, ProtocolVersion.MINECRAFT_1_21_2, true) + createMapping(0x7F, ProtocolVersion.MINECRAFT_1_21_2, true), + createMapping(0x84, ProtocolVersion.MINECRAFT_1_21_9, true) ); register(LIMBO_STATE_REGISTRY, PacketDirection.SERVERBOUND, @@ -488,68 +517,75 @@ public static void init() throws Throwable { createMapping(0x20, ProtocolVersion.MINECRAFT_1_21_6, false) ); register(LIMBO_STATE_REGISTRY, PacketDirection.SERVERBOUND, - TeleportConfirmPacket.class, TeleportConfirmPacket::new, + AcceptTeleportationPacket.class, AcceptTeleportationPacket::new, createMapping(0x00, ProtocolVersion.MINECRAFT_1_9, false) ); register(PLAY_SERVERBOUND_REGISTRY, - PlayerChatSessionPacket.class, PlayerChatSessionPacket::new, + ChatSessionUpdatePacket.class, ChatSessionUpdatePacket::new, createMapping(0x20, ProtocolVersion.MINECRAFT_1_19_3, false), createMapping(0x06, ProtocolVersion.MINECRAFT_1_19_4, false), createMapping(0x07, ProtocolVersion.MINECRAFT_1_20_5, false), createMapping(0x08, ProtocolVersion.MINECRAFT_1_21_2, false), createMapping(0x09, ProtocolVersion.MINECRAFT_1_21_6, false) ); + + final boolean enableSetEntityDataRewrite = !true; // TODO to config + register(PLAY_CLIENTBOUND_REGISTRY, + SetEntityDataPacket.class, enableSetEntityDataRewrite ? null : SetEntityDataPacket::new, + createMapping(0x1C, ProtocolVersion.MINECRAFT_1_7_2, enableSetEntityDataRewrite), + createMapping(0x39, ProtocolVersion.MINECRAFT_1_9, enableSetEntityDataRewrite), + createMapping(0x3B, ProtocolVersion.MINECRAFT_1_12, enableSetEntityDataRewrite), + createMapping(0x3C, ProtocolVersion.MINECRAFT_1_12_1, enableSetEntityDataRewrite), + createMapping(0x3F, ProtocolVersion.MINECRAFT_1_13, enableSetEntityDataRewrite), + createMapping(0x43, ProtocolVersion.MINECRAFT_1_14, enableSetEntityDataRewrite), + createMapping(0x44, ProtocolVersion.MINECRAFT_1_15, enableSetEntityDataRewrite), + createMapping(0x4D, ProtocolVersion.MINECRAFT_1_17, enableSetEntityDataRewrite), + createMapping(0x50, ProtocolVersion.MINECRAFT_1_19_1, enableSetEntityDataRewrite), + createMapping(0x4E, ProtocolVersion.MINECRAFT_1_19_3, enableSetEntityDataRewrite), + createMapping(0x52, ProtocolVersion.MINECRAFT_1_19_4, enableSetEntityDataRewrite), + createMapping(0x54, ProtocolVersion.MINECRAFT_1_20_2, enableSetEntityDataRewrite), + createMapping(0x56, ProtocolVersion.MINECRAFT_1_20_3, enableSetEntityDataRewrite), + createMapping(0x58, ProtocolVersion.MINECRAFT_1_20_5, enableSetEntityDataRewrite), + createMapping(0x5D, ProtocolVersion.MINECRAFT_1_21_2, enableSetEntityDataRewrite), + createMapping(0x5C, ProtocolVersion.MINECRAFT_1_21_5, enableSetEntityDataRewrite), + createMapping(0x61, ProtocolVersion.MINECRAFT_1_21_9, enableSetEntityDataRewrite) + ); } public static StateRegistry createLocalStateRegistry() { try { - StateRegistry stateRegistry = (StateRegistry) UNSAFE.allocateInstance(StateRegistry.class); + StateRegistry stateRegistry = (StateRegistry) Reflection.UNSAFE.allocateInstance(StateRegistry.class); overlayRegistry(stateRegistry, "clientbound", LIMBO_CLIENTBOUND_REGISTRY); overlayRegistry(stateRegistry, "serverbound", LIMBO_SERVERBOUND_REGISTRY); return stateRegistry; - } catch (Throwable e) { - throw new ReflectionException(e); + } catch (Throwable t) { + throw new ReflectionException(t); } } - public static void register(StateRegistry stateRegistry, - PacketDirection direction, Class packetClass, Supplier packetSupplier, PacketMapping[] mappings) { + public static void register(StateRegistry stateRegistry, PacketDirection direction, Class packetClass, Supplier packetSupplier, PacketMapping[] mappings) { register(stateRegistry, direction, packetClass, packetSupplier, Arrays.stream(mappings).map(mapping -> { try { - return createMapping(mapping.getID(), mapping.getProtocolVersion(), mapping.getLastValidProtocolVersion(), mapping.isEncodeOnly()); - } catch (Throwable e) { - throw new ReflectionException(e); + return createMapping(mapping.getId(), mapping.getProtocolVersion(), mapping.getLastValidProtocolVersion(), mapping.isEncodeOnly()); + } catch (Throwable t) { + throw new ReflectionException(t); } }).toArray(StateRegistry.PacketMapping[]::new)); } - public static void register(StateRegistry stateRegistry, - PacketDirection direction, Class packetClass, Supplier packetSupplier, StateRegistry.PacketMapping... mappings) { - MethodHandle registryGetter; - switch (direction) { - case CLIENTBOUND: { - registryGetter = CLIENTBOUND_REGISTRY_GETTER; - break; - } - case SERVERBOUND: { - registryGetter = SERVERBOUND_REGISTRY_GETTER; - break; - } - default: { - throw new IllegalStateException("Unexpected value: " + direction); - } - } - + public static void register(StateRegistry stateRegistry, PacketDirection direction, Class packetClass, Supplier packetSupplier, StateRegistry.PacketMapping... mappings) { try { - register((StateRegistry.PacketRegistry) registryGetter.invokeExact(stateRegistry), packetClass, packetSupplier, mappings); - } catch (Throwable e) { - throw new ReflectionException(e); + register((StateRegistry.PacketRegistry) (switch (direction) { + case CLIENTBOUND -> CLIENTBOUND_REGISTRY_GETTER; + case SERVERBOUND -> SERVERBOUND_REGISTRY_GETTER; + }).invokeExact(stateRegistry), packetClass, packetSupplier, mappings); + } catch (Throwable t) { + throw new ReflectionException(t); } } - public static void register(StateRegistry.PacketRegistry registry, - Class packetClass, Supplier packetSupplier, StateRegistry.PacketMapping... mappings) { + public static void register(StateRegistry.PacketRegistry registry, Class packetClass, Supplier packetSupplier, StateRegistry.PacketMapping... mappings) { try { var versions = (Map) VERSIONS_GETTER.invokeExact(registry); List> overlayMaps = versions.values().stream().flatMap(protocolRegistry -> { @@ -564,16 +600,15 @@ public static void register(StateRegistry.PacketRegistry registry, } else { return Stream.empty(); } - } catch (Throwable e) { - throw new ReflectionException(e); + } catch (Throwable t) { + throw new ReflectionException(t); } }).toList(); - overlayMaps.forEach(overlayMap -> overlayMap.setOverride(true)); REGISTER_METHOD.invokeExact(registry, packetClass, packetSupplier, mappings); overlayMaps.forEach(overlayMap -> overlayMap.setOverride(false)); - } catch (Throwable e) { - throw new ReflectionException(e); + } catch (Throwable t) { + throw new ReflectionException(t); } } @@ -581,8 +616,7 @@ private static StateRegistry.PacketMapping createMapping(int id, ProtocolVersion return createMapping(id, version, null, encodeOnly); } - private static StateRegistry.PacketMapping createMapping(int id, ProtocolVersion version, ProtocolVersion lastValidProtocolVersion, boolean encodeOnly) - throws Throwable { + private static StateRegistry.PacketMapping createMapping(int id, ProtocolVersion version, ProtocolVersion lastValidProtocolVersion, boolean encodeOnly) throws Throwable { return (StateRegistry.PacketMapping) PACKET_MAPPING_CONSTRUCTOR.invokeExact(id, version, lastValidProtocolVersion, encodeOnly); } diff --git a/plugin/src/main/java/net/elytrium/limboapi/protocol/codec/ByteBufCodecs.java b/plugin/src/main/java/net/elytrium/limboapi/protocol/codec/ByteBufCodecs.java new file mode 100644 index 00000000..dbd92bb0 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/protocol/codec/ByteBufCodecs.java @@ -0,0 +1,484 @@ +package net.elytrium.limboapi.protocol.codec; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufInputStream; +import io.netty.buffer.ByteBufOutputStream; +import io.netty.buffer.ByteBufUtil; +import io.netty.handler.codec.CorruptedFrameException; +import io.netty.handler.codec.DecoderException; +import io.netty.handler.codec.EncoderException; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.lang.reflect.Field; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.function.Function; +import java.util.function.IntFunction; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.BinaryTagType; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.nbt.EndBinaryTag; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.NullMarked; + +public interface ByteBufCodecs { + + StreamCodec BOOL = new StreamCodec<>() { + + @Override + public Boolean decode(ByteBuf buf, ProtocolVersion version) { + return buf.readBoolean(); + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, Boolean value) { + buf.writeBoolean(value); + } + }; + StreamCodec BYTE = new StreamCodec<>() { + + @Override + public Byte decode(ByteBuf buf, ProtocolVersion version) { + return buf.readByte(); + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, Byte value) { + buf.writeByte(value); + } + }; + StreamCodec SHORT = new StreamCodec<>() { + + @Override + public Short decode(ByteBuf buf, ProtocolVersion version) { + return buf.readShort(); + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, Short value) { + buf.writeShort(value); + } + }; + StreamCodec INT = new StreamCodec<>() { + + @Override + public Integer decode(ByteBuf buf, ProtocolVersion version) { + return buf.readInt(); + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, Integer value) { + buf.writeInt(value); + } + }; + StreamCodec> INT_COLLECTION = ByteBufCodecs.collection(ByteBufCodecs.INT); + StreamCodec VAR_INT = new StreamCodec<>() { + + @Override + public Integer decode(ByteBuf buf, ProtocolVersion version) { + byte readByte = buf.readByte(); + if ((readByte & 0x80) != 0x80) { + return (int) readByte; + } + + int result = readByte & 0x7F; + int bitsRead = 7; + do { + readByte = buf.readByte(); + result |= (readByte & 0x7F) << bitsRead; + bitsRead += 7; + if (bitsRead > 5 * 7) { + throw new CorruptedFrameException("VarInt too big"); + } + } while ((readByte & 0x80) == 0x80); + return result; + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, Integer value) { + ProtocolUtils.writeVarInt(buf, value); + } + }; + StreamCodec VAR_LONG = new StreamCodec<>() { + + @Override + public Long decode(ByteBuf buf, ProtocolVersion version) { + byte readByte = buf.readByte(); + if ((readByte & 0x80) != 0x80) { + return (long) readByte; + } + + long result = readByte & 0x7F; + int bitsRead = 7; + do { + readByte = buf.readByte(); + result |= (readByte & 0x7FL) << bitsRead; + bitsRead += 7; + if (bitsRead > 10 * 7) { + throw new CorruptedFrameException("VarLong too big"); + } + } while ((readByte & 0x80) == 0x80); + return result; + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, Long valueObj) { + long value = valueObj; + while ((value & ~0x7F) != 0L) { + buf.writeByte((int) (value & 0x7FL) | 0x80); + value >>>= 7; + } + + buf.writeByte((int) value); + } + }; + StreamCodec FLOAT = new StreamCodec<>() { + + @Override + public Float decode(ByteBuf buf, ProtocolVersion version) { + return buf.readFloat(); + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, Float value) { + buf.writeFloat(value); + } + }; + StreamCodec DOUBLE = new StreamCodec<>() { + + @Override + public Double decode(ByteBuf buf, ProtocolVersion version) { + return buf.readDouble(); + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, Double value) { + buf.writeDouble(value); + } + }; + + StreamCodec UUID = new StreamCodec<>() { + + @Override + public UUID decode(ByteBuf buf, ProtocolVersion version) { + return ProtocolUtils.readUuid(buf); + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, UUID value) { + ProtocolUtils.writeUuid(buf, value); + } + }; + StreamCodec<@Nullable UUID> OPTIONAL_UUID = ByteBufCodecs.optional(ByteBufCodecs.UUID); + + StreamCodec CONTAINER_ID = new StreamCodec<>() { + + @Override + public Integer decode(ByteBuf buf, ProtocolVersion version) { + return version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2) ? ProtocolUtils.readVarInt(buf) : buf.readUnsignedByte(); + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, Integer value) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) { + ProtocolUtils.writeVarInt(buf, value); + } else { + buf.writeByte(value & 0xFF); + } + } + }; + + StreamCodec STRING_UTF8 = ByteBufCodecs.stringUtf8(Short.MAX_VALUE); + StreamCodec<@Nullable String> OPTIONAL_STRING_UTF8 = ByteBufCodecs.optional(ByteBufCodecs.STRING_UTF8); + + StreamCodec STRING_UTF8_1024 = ByteBufCodecs.stringUtf8(1024); + StreamCodec<@Nullable String> OPTIONAL_STRING_UTF8_1024 = ByteBufCodecs.optional(ByteBufCodecs.STRING_UTF8_1024); + + StreamCodec PLAYER_NAME = ByteBufCodecs.stringUtf8(16); + StreamCodec<@Nullable String> OPTIONAL_PLAYER_NAME = ByteBufCodecs.optional(ByteBufCodecs.PLAYER_NAME); + + StreamCodec<@Nullable BinaryTag> OPTIONAL_TAG = new StreamCodec<>() { + + // + private static final BinaryTagType[] BINARY_TAG_TYPES; + + static { + try { + Field field = ProtocolUtils.class.getDeclaredField("BINARY_TAG_TYPES"); + field.setAccessible(true); + BINARY_TAG_TYPES = (BinaryTagType[]) field.get(null); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + // + + // + @Nullable + @Override + public BinaryTag decode(ByteBuf buf, ProtocolVersion version) { + if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_7_6)) { + short length = buf.readShort(); + if (length < 0) { + return null; + } else { + try (DataInputStream inputStream = new DataInputStream(new BufferedInputStream(new GZIPInputStream(new ByteBufInputStream(buf, length))))) { + BinaryTagType type = BINARY_TAG_TYPES[inputStream.readByte()]; + inputStream.skipBytes(inputStream.readUnsignedShort()); + return type.read(inputStream); + } catch (IOException e) { + throw new DecoderException(e); + } + } + } else { + byte type = buf.readByte(); + if (type == 0) { + return null; + } else { + if (version.lessThan(ProtocolVersion.MINECRAFT_1_20_2)) { + buf.skipBytes(buf.readUnsignedShort()); + } + + try { + return BINARY_TAG_TYPES[type].read(new ByteBufInputStream(buf)); + } catch (IOException e) { + throw new DecoderException("Unable to parse BinaryTag", e); + } + } + } + } + // + + // + @Override + @SuppressWarnings("unchecked") + public void encode(ByteBuf buf, ProtocolVersion version, BinaryTag value) { + if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_7_6)) { + if (value == null || value instanceof EndBinaryTag) { + buf.writeShort(-1); + } else { + buf.writeShort(0); + int preIndex = buf.writerIndex(); + try (DataOutputStream output = new DataOutputStream(new BufferedOutputStream(new GZIPOutputStream(new ByteBufOutputStream(buf))))) { + var type = (BinaryTagType) value.type(); + output.writeByte(type.id()); + output.writeShort(0); // writeUTF("") + type.write(value, output); + } catch (IOException e) { + throw new RuntimeException(e); + } + int postIndex = buf.writerIndex(); + buf.writerIndex(preIndex - Short.BYTES); + buf.writeShort(postIndex - preIndex); + buf.writerIndex(postIndex); + } + } else if (value == null || value instanceof EndBinaryTag) { + buf.writeByte(0); + } else { + ProtocolUtils.writeBinaryTag(buf, version, value); + } + } + // + }; + StreamCodec TAG = ByteBufCodecs.OPTIONAL_TAG.map(tag -> { + if (tag == null) { + throw new DecoderException("Expected non-null binary tag"); + } else { + return tag; + } + }, tag -> { + if (tag == null || tag instanceof EndBinaryTag) { + throw new EncoderException("Expected non-null binary tag"); + } else { + return tag; + } + }); + StreamCodec<@Nullable CompoundBinaryTag> OPTIONAL_COMPOUND_TAG = ByteBufCodecs.OPTIONAL_TAG.map(tag -> { + if (tag == null || tag instanceof CompoundBinaryTag) { + return (CompoundBinaryTag) tag; + } else { + throw new DecoderException("Not a compound tag: " + tag); + } + }, Function.identity()); + StreamCodec COMPOUND_TAG = ByteBufCodecs.TAG.map(tag -> { + if (tag instanceof CompoundBinaryTag compound) { + return compound; + } else { + throw new EncoderException("Not a compound tag: " + tag); + } + }, Function.identity()); + + StreamCodec> STRING_2_STRING_MAP = ByteBufCodecs.map(Object2ObjectOpenHashMap::new, ByteBufCodecs.STRING_UTF8, ByteBufCodecs.STRING_UTF8); + + static StreamCodec stringUtf8(int capacity) { + return new StreamCodec<>() { + + @Override + public String decode(ByteBuf buf, ProtocolVersion version) { + int length = ProtocolUtils.readVarInt(buf); + int utf8MaxBytes = ByteBufUtil.utf8MaxBytes(capacity); + if (length > utf8MaxBytes) { + throw new DecoderException("The received encoded string buffer length is longer than maximum allowed (" + length + " > " + utf8MaxBytes + ")"); + } else if (length < 0) { + throw new DecoderException("The received encoded string buffer length is less than zero! Weird string!"); + } else { + int readableBytes = buf.readableBytes(); + if (length > readableBytes) { + throw new DecoderException("Not enough bytes in buffer, expected " + length + ", but got " + readableBytes); + } else { + String result = buf.readString(length, StandardCharsets.UTF_8); + if (result.length() > capacity) { + throw new DecoderException("The received string length is longer than maximum allowed (" + result.length() + " > " + capacity + ")"); + } else { + return result; + } + } + } + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, String value) { + // https://github.com/astei/krypton/blob/v0.2.8/src/main/java/me/steinborn/krypton/mixin/shared/network/microopt/StringEncodingMixin.java#L20 + if (value.length() > capacity) { + throw new EncoderException("String too big (was " + value.length() + " characters, max " + capacity + ")"); + } else { + int utf8Bytes = ByteBufUtil.utf8Bytes(value); + int utf8MaxBytes = ByteBufUtil.utf8MaxBytes(capacity); + if (utf8Bytes > utf8MaxBytes) { + throw new EncoderException("String too big (was " + utf8Bytes + " bytes encoded, max " + utf8MaxBytes + ")"); + } else { + ProtocolUtils.writeVarInt(buf, utf8Bytes); + buf.writeCharSequence(value, StandardCharsets.UTF_8); + } + } + } + }; + } + + static StreamCodec<@Nullable V> optional(StreamCodec<@Nullable V> codec) { + return new StreamCodec<>() { + + @Nullable + @Override + public V decode(ByteBuf buf, ProtocolVersion version) { + return buf.readBoolean() ? codec.decode(buf, version) : null; + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, @Nullable V value) { + if (value == null) { + buf.writeBoolean(false); + } else { + buf.writeBoolean(true); + codec.encode(buf, version, value); + } + } + }; + } + + static StreamCodec> list(StreamCodec codec) { + return ByteBufCodecs.collection(ArrayList::new, codec, Integer.MAX_VALUE); + } + + static StreamCodec> list(StreamCodec codec, int max) { + return ByteBufCodecs.collection(ArrayList::new, codec, max); + } + + static StreamCodec> collection(StreamCodec codec) { + return ByteBufCodecs.collection(ArrayList::new, codec, Integer.MAX_VALUE); + } + + static StreamCodec> collection(StreamCodec codec, int max) { + return ByteBufCodecs.collection(ArrayList::new, codec, max); + } + + static > StreamCodec collection(IntFunction constructor, StreamCodec codec) { + return ByteBufCodecs.collection(constructor, codec, Integer.MAX_VALUE); + } + + static > StreamCodec collection(IntFunction constructor, StreamCodec codec, int max) { + return new StreamCodec<>() { + + @Override + public C decode(ByteBuf buf, ProtocolVersion version) { + int count = ByteBufCodecs.readCount(buf, max); + C result = constructor.apply(count); + for (int i = 0; i < count; ++i) { + result.add(codec.decode(buf, version)); + } + + return result; + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, C value) { + int size = value.size(); + ByteBufCodecs.writeCount(buf, size, max); + if (size > 0) { + value.forEach(object -> codec.encode(buf, version, object)); + } + } + }; + } + + static > StreamCodec map(IntFunction constructor, StreamCodec keyCodec, StreamCodec valueCodec) { + return ByteBufCodecs.map(constructor, keyCodec, valueCodec, Integer.MAX_VALUE); + } + + static > StreamCodec map(IntFunction constructor, StreamCodec keyCodec, StreamCodec valueCodec, int max) { + return new StreamCodec<>() { + + @Override + public M decode(ByteBuf buf, ProtocolVersion version) { + int count = ByteBufCodecs.readCount(buf, max); + M map = constructor.apply(count); + for (int i = 0; i < count; ++i) { + map.put(keyCodec.decode(buf, version), valueCodec.decode(buf, version)); + } + + return map; + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, M map) { + int size = map.size(); + ByteBufCodecs.writeCount(buf, size, max); + if (size > 0) { + map.forEach((key, value) -> { + keyCodec.encode(buf, version, key); + valueCodec.encode(buf, version, value); + }); + } + } + }; + } + + static int readCount(ByteBuf buf, int max) { + int count = ProtocolUtils.readVarInt(buf); + if (count > max) { + throw new DecoderException(count + " elements exceeded max size of: " + max); + } else { + return count; + } + } + + static void writeCount(ByteBuf buf, int count, int max) { + if (count > max) { + throw new EncoderException(count + " elements exceeded max size of: " + max); + } else { + ProtocolUtils.writeVarInt(buf, count); + } + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/protocol/codec/StreamCodec.java b/plugin/src/main/java/net/elytrium/limboapi/protocol/codec/StreamCodec.java new file mode 100644 index 00000000..ead5633a --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/protocol/codec/StreamCodec.java @@ -0,0 +1,560 @@ +package net.elytrium.limboapi.protocol.codec; + +import com.velocitypowered.api.network.ProtocolVersion; +import io.netty.buffer.ByteBuf; +import java.util.Objects; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.UnaryOperator; +import net.elytrium.limboapi.utils.functions.Function10; +import net.elytrium.limboapi.utils.functions.Function11; +import net.elytrium.limboapi.utils.functions.Function3; +import net.elytrium.limboapi.utils.functions.Function4; +import net.elytrium.limboapi.utils.functions.Function5; +import net.elytrium.limboapi.utils.functions.Function6; +import net.elytrium.limboapi.utils.functions.Function7; +import net.elytrium.limboapi.utils.functions.Function8; +import net.elytrium.limboapi.utils.functions.Function9; +import org.checkerframework.checker.nullness.qual.Nullable; + +@SuppressWarnings("DuplicatedCode") +public interface StreamCodec extends StreamDecoder, StreamEncoder { + + default StreamCodec map(Function decodeMap, Function encodeMap) { + return new StreamCodec<>() { + + @Override + public O decode(ByteBuf buf, ProtocolVersion version) { + return decodeMap.apply(StreamCodec.this.decode(buf, version)); + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, O value) { + StreamCodec.this.encode(buf, version, encodeMap.apply(value)); + } + }; + } + + default StreamCodec map(BiFunction decodeMap, BiFunction encodeMap) { + return new StreamCodec<>() { + + @Override + public O decode(ByteBuf buf, ProtocolVersion version) { + return decodeMap.apply(StreamCodec.this.decode(buf, version), version); + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, O value) { + StreamCodec.this.encode(buf, version, encodeMap.apply(value, version)); + } + }; + } + + static StreamCodec unit(V unit) { + return new StreamCodec<>() { + + @Override + public V decode(ByteBuf buf, ProtocolVersion version) { + return unit; + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, V value) { + if (!Objects.equals(value, unit)) { + throw new IllegalStateException("Can't encode '" + value + "', expected '" + unit + "'"); + } + } + }; + } + + static StreamCodec eq(ProtocolVersion eq, StreamCodec codec) { + return StreamCodec.eq(eq, codec, null); + } + + static StreamCodec eq(ProtocolVersion eq, StreamCodec codec, @Nullable V fallback) { + return new StreamCodec<>() { + + @Nullable + @Override + public V decode(ByteBuf buf, ProtocolVersion version) { + return version == eq ? codec.decode(buf, version) : fallback; + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, V value) { + if (version == eq) { + codec.encode(buf, version, value); + } + } + }; + } + + static StreamCodec ne(ProtocolVersion ne, StreamCodec codec) { + return StreamCodec.ne(ne, codec, null); + } + + static StreamCodec ne(ProtocolVersion ne, StreamCodec codec, @Nullable V fallback) { + return new StreamCodec<>() { + + @Nullable + @Override + public V decode(ByteBuf buf, ProtocolVersion version) { + return version != ne ? codec.decode(buf, version) : fallback; + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, V value) { + if (version != ne) { + codec.encode(buf, version, value); + } + } + }; + } + + static StreamCodec gt(ProtocolVersion gt, StreamCodec codec) { + return StreamCodec.gt(gt, codec, null); + } + + static StreamCodec gt(ProtocolVersion gt, StreamCodec codec, @Nullable V fallback) { + return new StreamCodec<>() { + + @Nullable + @Override + public V decode(ByteBuf buf, ProtocolVersion version) { + return version.greaterThan(gt) ? codec.decode(buf, version) : fallback; + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, V value) { + if (version.greaterThan(gt)) { + codec.encode(buf, version, value); + } + } + }; + } + + static StreamCodec ge(ProtocolVersion ge, StreamCodec codec) { + return StreamCodec.ge(ge, codec, null); + } + + static StreamCodec ge(ProtocolVersion ge, StreamCodec codec, @Nullable V fallback) { + return new StreamCodec<>() { + + @Nullable + @Override + public V decode(ByteBuf buf, ProtocolVersion version) { + return version.noLessThan(ge) ? codec.decode(buf, version) : fallback; + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, V value) { + if (version.noLessThan(ge)) { + codec.encode(buf, version, value); + } + } + }; + } + + static StreamCodec lt(ProtocolVersion lt, StreamCodec codec) { + return StreamCodec.lt(lt, codec, (V) null); + } + + static StreamCodec lt(ProtocolVersion lt, StreamCodec codec, @Nullable V fallback) { + return new StreamCodec<>() { + + @Nullable + @Override + public V decode(ByteBuf buf, ProtocolVersion version) { + return version.lessThan(lt) ? codec.decode(buf, version) : fallback; + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, V value) { + if (version.lessThan(lt)) { + codec.encode(buf, version, value); + } + } + }; + } + + static StreamCodec lt(ProtocolVersion lt, StreamCodec codec, StreamCodec otherwise) { + return new StreamCodec<>() { + + @Override + public V decode(ByteBuf buf, ProtocolVersion version) { + return (version.lessThan(lt) ? codec : otherwise).decode(buf, version); + } + + @Override + @SuppressWarnings("unchecked") + public void encode(ByteBuf buf, ProtocolVersion version, V value) { + ((StreamCodec) (version.lessThan(lt) ? codec : otherwise)).encode(buf, version, value); + } + }; + } + + static StreamCodec le(ProtocolVersion le, StreamCodec codec) { + return StreamCodec.le(le, codec, null); + } + + static StreamCodec le(ProtocolVersion le, StreamCodec codec, @Nullable V fallback) { + return new StreamCodec<>() { + + @Nullable + @Override + public V decode(ByteBuf buf, ProtocolVersion version) { + return version.noGreaterThan(le) ? codec.decode(buf, version) : fallback; + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, V value) { + if (version.noGreaterThan(le)) { + codec.encode(buf, version, value); + } + } + }; + } + + static StreamCodec composite( + StreamCodec codec1, Function getter1, + Function constructor + ) { + return new StreamCodec<>() { + + @Override + public V decode(ByteBuf buf, ProtocolVersion version) { + return constructor.apply(codec1.decode(buf, version)); + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, V value) { + codec1.encode(buf, version, getter1.apply(value)); + } + }; + } + + static StreamCodec composite( + StreamCodec codec1, Function getter1, + StreamCodec codec2, Function getter2, + BiFunction constructor + ) { + return new StreamCodec<>() { + + @Override + public V decode(ByteBuf buf, ProtocolVersion version) { + return constructor.apply(codec1.decode(buf, version), codec2.decode(buf, version)); + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, V value) { + codec1.encode(buf, version, getter1.apply(value)); + codec2.encode(buf, version, getter2.apply(value)); + } + }; + } + + static StreamCodec composite( + StreamCodec codec1, Function getter1, + StreamCodec codec2, Function getter2, + StreamCodec codec3, Function getter3, + Function3 constructor + ) { + return new StreamCodec<>() { + + @Override + public V decode(ByteBuf buf, ProtocolVersion version) { + return constructor.apply(codec1.decode(buf, version), codec2.decode(buf, version), codec3.decode(buf, version)); + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, V value) { + codec1.encode(buf, version, getter1.apply(value)); + codec2.encode(buf, version, getter2.apply(value)); + codec3.encode(buf, version, getter3.apply(value)); + } + }; + } + + static StreamCodec composite( + StreamCodec codec1, Function getter1, + StreamCodec codec2, Function getter2, + StreamCodec codec3, Function getter3, + StreamCodec codec4, Function getter4, + Function4 constructor + ) { + return new StreamCodec<>() { + + @Override + public V decode(ByteBuf buf, ProtocolVersion version) { + return constructor.apply(codec1.decode(buf, version), codec2.decode(buf, version), codec3.decode(buf, version), codec4.decode(buf, version)); + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, V value) { + codec1.encode(buf, version, getter1.apply(value)); + codec2.encode(buf, version, getter2.apply(value)); + codec3.encode(buf, version, getter3.apply(value)); + codec4.encode(buf, version, getter4.apply(value)); + } + }; + } + + static StreamCodec composite( + StreamCodec codec1, Function getter1, + StreamCodec codec2, Function getter2, + StreamCodec codec3, Function getter3, + StreamCodec codec4, Function getter4, + StreamCodec codec5, Function getter5, + Function5 constructor + ) { + return new StreamCodec<>() { + + @Override + public V decode(ByteBuf buf, ProtocolVersion version) { + return constructor.apply(codec1.decode(buf, version), codec2.decode(buf, version), codec3.decode(buf, version), codec4.decode(buf, version), codec5.decode(buf, version)); + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, V value) { + codec1.encode(buf, version, getter1.apply(value)); + codec2.encode(buf, version, getter2.apply(value)); + codec3.encode(buf, version, getter3.apply(value)); + codec4.encode(buf, version, getter4.apply(value)); + codec5.encode(buf, version, getter5.apply(value)); + } + }; + } + + static StreamCodec composite( + StreamCodec codec1, Function getter1, + StreamCodec codec2, Function getter2, + StreamCodec codec3, Function getter3, + StreamCodec codec4, Function getter4, + StreamCodec codec5, Function getter5, + StreamCodec codec6, Function getter6, + Function6 constructor + ) { + return new StreamCodec<>() { + + @Override + public V decode(ByteBuf buf, ProtocolVersion version) { + return constructor.apply( + codec1.decode(buf, version), codec2.decode(buf, version), codec3.decode(buf, version), + codec4.decode(buf, version), codec5.decode(buf, version), codec6.decode(buf, version) + ); + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, V value) { + codec1.encode(buf, version, getter1.apply(value)); + codec2.encode(buf, version, getter2.apply(value)); + codec3.encode(buf, version, getter3.apply(value)); + codec4.encode(buf, version, getter4.apply(value)); + codec5.encode(buf, version, getter5.apply(value)); + codec6.encode(buf, version, getter6.apply(value)); + } + }; + } + + static StreamCodec composite( + StreamCodec codec1, Function getter1, + StreamCodec codec2, Function getter2, + StreamCodec codec3, Function getter3, + StreamCodec codec4, Function getter4, + StreamCodec codec5, Function getter5, + StreamCodec codec6, Function getter6, + StreamCodec codec7, Function getter7, + Function7 constructor + ) { + return new StreamCodec<>() { + + @Override + public V decode(ByteBuf buf, ProtocolVersion version) { + return constructor.apply( + codec1.decode(buf, version), codec2.decode(buf, version), codec3.decode(buf, version), + codec4.decode(buf, version), codec5.decode(buf, version), codec6.decode(buf, version), codec7.decode(buf, version) + ); + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, V value) { + codec1.encode(buf, version, getter1.apply(value)); + codec2.encode(buf, version, getter2.apply(value)); + codec3.encode(buf, version, getter3.apply(value)); + codec4.encode(buf, version, getter4.apply(value)); + codec5.encode(buf, version, getter5.apply(value)); + codec6.encode(buf, version, getter6.apply(value)); + codec7.encode(buf, version, getter7.apply(value)); + } + }; + } + + static StreamCodec composite( + StreamCodec codec1, Function getter1, + StreamCodec codec2, Function getter2, + StreamCodec codec3, Function getter3, + StreamCodec codec4, Function getter4, + StreamCodec codec5, Function getter5, + StreamCodec codec6, Function getter6, + StreamCodec codec7, Function getter7, + StreamCodec codec8, Function getter8, + Function8 constructor + ) { + return new StreamCodec<>() { + + @Override + public V decode(ByteBuf buf, ProtocolVersion version) { + return constructor.apply( + codec1.decode(buf, version), codec2.decode(buf, version), codec3.decode(buf, version), codec4.decode(buf, version), + codec5.decode(buf, version), codec6.decode(buf, version), codec7.decode(buf, version), codec8.decode(buf, version) + ); + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, V value) { + codec1.encode(buf, version, getter1.apply(value)); + codec2.encode(buf, version, getter2.apply(value)); + codec3.encode(buf, version, getter3.apply(value)); + codec4.encode(buf, version, getter4.apply(value)); + codec5.encode(buf, version, getter5.apply(value)); + codec6.encode(buf, version, getter6.apply(value)); + codec7.encode(buf, version, getter7.apply(value)); + codec8.encode(buf, version, getter8.apply(value)); + } + }; + } + + static StreamCodec composite( + StreamCodec codec1, Function getter1, + StreamCodec codec2, Function getter2, + StreamCodec codec3, Function getter3, + StreamCodec codec4, Function getter4, + StreamCodec codec5, Function getter5, + StreamCodec codec6, Function getter6, + StreamCodec codec7, Function getter7, + StreamCodec codec8, Function getter8, + StreamCodec codec9, Function getter9, + Function9 constructor + ) { + return new StreamCodec<>() { + + @Override + public V decode(ByteBuf buf, ProtocolVersion version) { + return constructor.apply( + codec1.decode(buf, version), codec2.decode(buf, version), codec3.decode(buf, version), codec4.decode(buf, version), + codec5.decode(buf, version), codec6.decode(buf, version), codec7.decode(buf, version), codec8.decode(buf, version), codec9.decode(buf, version) + ); + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, V value) { + codec1.encode(buf, version, getter1.apply(value)); + codec2.encode(buf, version, getter2.apply(value)); + codec3.encode(buf, version, getter3.apply(value)); + codec4.encode(buf, version, getter4.apply(value)); + codec5.encode(buf, version, getter5.apply(value)); + codec6.encode(buf, version, getter6.apply(value)); + codec7.encode(buf, version, getter7.apply(value)); + codec8.encode(buf, version, getter8.apply(value)); + codec9.encode(buf, version, getter9.apply(value)); + } + }; + } + + static StreamCodec composite( + StreamCodec codec1, Function getter1, + StreamCodec codec2, Function getter2, + StreamCodec codec3, Function getter3, + StreamCodec codec4, Function getter4, + StreamCodec codec5, Function getter5, + StreamCodec codec6, Function getter6, + StreamCodec codec7, Function getter7, + StreamCodec codec8, Function getter8, + StreamCodec codec9, Function getter9, + StreamCodec codec10, Function getter10, + Function10 constructor + ) { + return new StreamCodec<>() { + + @Override + public V decode(ByteBuf buf, ProtocolVersion version) { + return constructor.apply( + codec1.decode(buf, version), codec2.decode(buf, version), codec3.decode(buf, version), codec4.decode(buf, version), codec5.decode(buf, version), + codec6.decode(buf, version), codec7.decode(buf, version), codec8.decode(buf, version), codec9.decode(buf, version), codec10.decode(buf, version) + ); + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, V value) { + codec1.encode(buf, version, getter1.apply(value)); + codec2.encode(buf, version, getter2.apply(value)); + codec3.encode(buf, version, getter3.apply(value)); + codec4.encode(buf, version, getter4.apply(value)); + codec5.encode(buf, version, getter5.apply(value)); + codec6.encode(buf, version, getter6.apply(value)); + codec7.encode(buf, version, getter7.apply(value)); + codec8.encode(buf, version, getter8.apply(value)); + codec9.encode(buf, version, getter9.apply(value)); + codec10.encode(buf, version, getter10.apply(value)); + } + }; + } + + static StreamCodec composite( + StreamCodec codec1, Function getter1, + StreamCodec codec2, Function getter2, + StreamCodec codec3, Function getter3, + StreamCodec codec4, Function getter4, + StreamCodec codec5, Function getter5, + StreamCodec codec6, Function getter6, + StreamCodec codec7, Function getter7, + StreamCodec codec8, Function getter8, + StreamCodec codec9, Function getter9, + StreamCodec codec10, Function getter10, + StreamCodec codec11, Function getter11, + Function11 constructor + ) { + return new StreamCodec<>() { + + @Override + public V decode(ByteBuf buf, ProtocolVersion version) { + return constructor.apply( + codec1.decode(buf, version), codec2.decode(buf, version), codec3.decode(buf, version), codec4.decode(buf, version), codec5.decode(buf, version), + codec6.decode(buf, version), codec7.decode(buf, version), codec8.decode(buf, version), codec9.decode(buf, version), codec10.decode(buf, version), codec11.decode(buf, version) + ); + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, V value) { + codec1.encode(buf, version, getter1.apply(value)); + codec2.encode(buf, version, getter2.apply(value)); + codec3.encode(buf, version, getter3.apply(value)); + codec4.encode(buf, version, getter4.apply(value)); + codec5.encode(buf, version, getter5.apply(value)); + codec6.encode(buf, version, getter6.apply(value)); + codec7.encode(buf, version, getter7.apply(value)); + codec8.encode(buf, version, getter8.apply(value)); + codec9.encode(buf, version, getter9.apply(value)); + codec10.encode(buf, version, getter10.apply(value)); + codec11.encode(buf, version, getter11.apply(value)); + } + }; + } + + static StreamCodec recursive(UnaryOperator> initializer) { + return new StreamCodec<>() { + + private final StreamCodec inner = initializer.apply(this); + + @Override + public T decode(ByteBuf buf, ProtocolVersion version) { + return this.inner.decode(buf, version); + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, T value) { + this.inner.encode(buf, version, value); + } + }; + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/protocol/codec/StreamDecoder.java b/plugin/src/main/java/net/elytrium/limboapi/protocol/codec/StreamDecoder.java new file mode 100644 index 00000000..8ad32449 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/protocol/codec/StreamDecoder.java @@ -0,0 +1,10 @@ +package net.elytrium.limboapi.protocol.codec; + +import com.velocitypowered.api.network.ProtocolVersion; +import io.netty.buffer.ByteBuf; + +@FunctionalInterface +public interface StreamDecoder { + + T decode(ByteBuf buf, ProtocolVersion version); +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/protocol/codec/StreamEncoder.java b/plugin/src/main/java/net/elytrium/limboapi/protocol/codec/StreamEncoder.java new file mode 100644 index 00000000..34957f0a --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/protocol/codec/StreamEncoder.java @@ -0,0 +1,10 @@ +package net.elytrium.limboapi.protocol.codec; + +import com.velocitypowered.api.network.ProtocolVersion; +import io.netty.buffer.ByteBuf; + +@FunctionalInterface +public interface StreamEncoder { + + void encode(ByteBuf buf, ProtocolVersion version, T value); +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/protocol/data/BiomeStorage118.java b/plugin/src/main/java/net/elytrium/limboapi/protocol/data/BiomeStorage118.java index 39b361cf..0c29a7a7 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/protocol/data/BiomeStorage118.java +++ b/plugin/src/main/java/net/elytrium/limboapi/protocol/data/BiomeStorage118.java @@ -24,30 +24,27 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import net.elytrium.limboapi.api.chunk.VirtualBiome; -import net.elytrium.limboapi.api.chunk.util.CompactStorage; -import net.elytrium.limboapi.material.Biome; +import net.elytrium.limboapi.api.world.chunk.biome.VirtualBiome; +import net.elytrium.limboapi.api.world.chunk.util.CompactStorage; import net.elytrium.limboapi.mcprotocollib.BitStorage116; +import net.elytrium.limboapi.protocol.util.LimboProtocolUtils; import net.elytrium.limboapi.server.world.chunk.SimpleChunk; import org.checkerframework.checker.nullness.qual.NonNull; public class BiomeStorage118 { + private static final int MIN_BITS_PER_ENTRY = 1; + private static final int MAX_BITS_PER_ENTRY = 3; + private final ProtocolVersion version; - private List palette = new ArrayList<>(); - private Map rawToBiome = new HashMap<>(); + private List palette = new ArrayList<>(4); + private Map rawToBiome = new HashMap<>(4); private CompactStorage storage; public BiomeStorage118(ProtocolVersion version) { this.version = version; - - for (Biome biome : Biome.values()) { - this.palette.add(biome); - this.rawToBiome.put(biome.getID(), biome); - } - - this.storage = new BitStorage116(3, SimpleChunk.MAX_BIOMES_PER_SECTION); + this.storage = new BitStorage116(BiomeStorage118.MIN_BITS_PER_ENTRY, SimpleChunk.MAX_BIOMES_PER_SECTION); } private BiomeStorage118(ProtocolVersion version, List palette, Map rawToBiome, CompactStorage storage) { @@ -58,12 +55,12 @@ private BiomeStorage118(ProtocolVersion version, List palette, Map } public void set(int posX, int posY, int posZ, @NonNull VirtualBiome biome) { - int id = this.getIndex(biome); + int id = this.getId(biome); this.storage.set(index(posX, posY, posZ), id); } public void set(int index, @NonNull VirtualBiome biome) { - int id = this.getIndex(biome); + int id = this.getId(biome); this.storage.set(index, id); } @@ -74,20 +71,14 @@ public VirtualBiome get(int posX, int posY, int posZ) { private VirtualBiome get(int index) { int id = this.storage.get(index); - if (this.storage.getBitsPerEntry() > 8) { - return this.rawToBiome.get(id); - } else { - return this.palette.get(id); - } + return this.storage.getBitsPerEntry() > BiomeStorage118.MAX_BITS_PER_ENTRY ? this.rawToBiome.get(id) : this.palette.get(id); } public void write(ByteBuf buf, ProtocolVersion version) { - buf.writeByte(this.storage.getBitsPerEntry()); - if (this.storage.getBitsPerEntry() <= 8) { - ProtocolUtils.writeVarInt(buf, this.palette.size()); - for (VirtualBiome biome : this.palette) { - ProtocolUtils.writeVarInt(buf, biome.getID()); - } + int bitsPerEntry = this.storage.getBitsPerEntry(); + buf.writeByte(bitsPerEntry); + if (bitsPerEntry != BiomeStorage118.MAX_BITS_PER_ENTRY) { + LimboProtocolUtils.writeCollection(buf, this.palette, biome -> ProtocolUtils.writeVarInt(buf, biome.getId())); } this.storage.write(buf, version); @@ -95,10 +86,10 @@ public void write(ByteBuf buf, ProtocolVersion version) { public int getDataLength(ProtocolVersion version) { int length = 1; - if (this.storage.getBitsPerEntry() <= 8) { + if (this.storage.getBitsPerEntry() != BiomeStorage118.MAX_BITS_PER_ENTRY) { length += ProtocolUtils.varIntBytes(this.palette.size()); for (VirtualBiome biome : this.palette) { - length += ProtocolUtils.varIntBytes(biome.getID()); + length += ProtocolUtils.varIntBytes(biome.getId()); } } @@ -109,9 +100,9 @@ public BiomeStorage118 copy() { return new BiomeStorage118(this.version, new ArrayList<>(this.palette), new HashMap<>(this.rawToBiome), this.storage.copy()); } - private int getIndex(VirtualBiome biome) { - if (this.storage.getBitsPerEntry() > 8) { - int raw = biome.getID(); + private int getId(VirtualBiome biome) { + if (this.storage.getBitsPerEntry() > BiomeStorage118.MAX_BITS_PER_ENTRY) { + int raw = biome.getId(); this.rawToBiome.put(raw, biome); return raw; } else { @@ -119,7 +110,7 @@ private int getIndex(VirtualBiome biome) { if (id == -1) { if (this.palette.size() >= (1 << this.storage.getBitsPerEntry())) { this.resize(this.storage.getBitsPerEntry() + 1); - return this.getIndex(biome); + return this.getId(biome); } this.palette.add(biome); @@ -131,10 +122,9 @@ private int getIndex(VirtualBiome biome) { } private void resize(int newSize) { - newSize = StorageUtils.fixBitsPerEntry(this.version, newSize); CompactStorage newStorage = new BitStorage116(newSize, SimpleChunk.MAX_BIOMES_PER_SECTION); for (int i = 0; i < SimpleChunk.MAX_BIOMES_PER_SECTION; ++i) { - newStorage.set(i, newSize > 8 ? this.palette.get(this.storage.get(i)).getID() : this.storage.get(i)); + newStorage.set(i, newSize > BiomeStorage118.MAX_BITS_PER_ENTRY ? this.palette.get(this.storage.get(i)).getId() : this.storage.get(i)); } this.storage = newStorage; diff --git a/plugin/src/main/java/net/elytrium/limboapi/protocol/data/BlockStorage17.java b/plugin/src/main/java/net/elytrium/limboapi/protocol/data/BlockStorage17.java index 94140a17..7318aafa 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/protocol/data/BlockStorage17.java +++ b/plugin/src/main/java/net/elytrium/limboapi/protocol/data/BlockStorage17.java @@ -21,9 +21,9 @@ import com.velocitypowered.api.network.ProtocolVersion; import io.netty.buffer.ByteBuf; import java.util.Arrays; -import net.elytrium.limboapi.api.chunk.VirtualBlock; -import net.elytrium.limboapi.api.chunk.data.BlockStorage; -import net.elytrium.limboapi.api.mcprotocollib.NibbleArray3D; +import net.elytrium.limboapi.api.world.chunk.block.VirtualBlock; +import net.elytrium.limboapi.api.world.chunk.data.BlockStorage; +import net.elytrium.limboapi.api.world.chunk.util.NibbleArray3D; import net.elytrium.limboapi.server.world.SimpleBlock; import net.elytrium.limboapi.server.world.chunk.SimpleChunk; import org.checkerframework.checker.nullness.qual.NonNull; @@ -45,11 +45,11 @@ public void write(Object byteBufObject, ProtocolVersion version, int pass) { Preconditions.checkArgument(byteBufObject instanceof ByteBuf); ByteBuf buf = (ByteBuf) byteBufObject; if (pass == 0) { - if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) < 0) { + if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_7_6)) { byte[] raw = new byte[this.blocks.length]; for (int i = 0; i < this.blocks.length; ++i) { VirtualBlock block = this.blocks[i]; - raw[i] = (byte) (block == null ? 0 : block.getBlockStateID(ProtocolVersion.MINECRAFT_1_7_2) >> 4); + raw[i] = (byte) (block == null ? 0 : block.blockStateId(ProtocolVersion.MINECRAFT_1_7_2) >> 4); } buf.writeBytes(raw); @@ -57,18 +57,18 @@ public void write(Object byteBufObject, ProtocolVersion version, int pass) { short[] raw = new short[this.blocks.length]; for (int i = 0; i < this.blocks.length; ++i) { VirtualBlock block = this.blocks[i]; - raw[i] = (short) (block == null ? 0 : block.getBlockStateID(ProtocolVersion.MINECRAFT_1_8)); + raw[i] = (short) (block == null ? 0 : block.blockStateId(ProtocolVersion.MINECRAFT_1_8)); } - for (short s : raw) { - buf.writeShortLE(s); + for (short value : raw) { + buf.writeShortLE(value); } } - } else if (pass == 1 && version.compareTo(ProtocolVersion.MINECRAFT_1_8) < 0) { + } else if (pass == 1 && version.noGreaterThan(ProtocolVersion.MINECRAFT_1_7_6)) { NibbleArray3D metadata = new NibbleArray3D(SimpleChunk.MAX_BLOCKS_PER_SECTION); for (int i = 0; i < this.blocks.length; ++i) { VirtualBlock block = this.blocks[i]; - metadata.set(i, block == null ? 0 : block.getBlockStateID(ProtocolVersion.MINECRAFT_1_7_2) & 0xFFFF); + metadata.set(i, block == null ? 0 : (byte) (block.blockStateId(ProtocolVersion.MINECRAFT_1_7_2) & 0xFFFF)); } buf.writeBytes(metadata.getData()); @@ -89,7 +89,7 @@ public VirtualBlock get(int posX, int posY, int posZ) { @Override public int getDataLength(ProtocolVersion version) { - return version.compareTo(ProtocolVersion.MINECRAFT_1_8) < 0 ? this.blocks.length + (SimpleChunk.MAX_BLOCKS_PER_SECTION >> 1) : this.blocks.length * 2; + return version.noGreaterThan(ProtocolVersion.MINECRAFT_1_7_6) ? this.blocks.length + (SimpleChunk.MAX_BLOCKS_PER_SECTION >> 1) : this.blocks.length * Short.BYTES; } @Override diff --git a/plugin/src/main/java/net/elytrium/limboapi/protocol/data/BlockStorage19.java b/plugin/src/main/java/net/elytrium/limboapi/protocol/data/BlockStorage19.java index c42816dc..6173148b 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/protocol/data/BlockStorage19.java +++ b/plugin/src/main/java/net/elytrium/limboapi/protocol/data/BlockStorage19.java @@ -25,17 +25,21 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import net.elytrium.limboapi.api.chunk.VirtualBlock; -import net.elytrium.limboapi.api.chunk.data.BlockStorage; -import net.elytrium.limboapi.api.chunk.util.CompactStorage; +import net.elytrium.limboapi.api.world.chunk.block.VirtualBlock; +import net.elytrium.limboapi.api.world.chunk.data.BlockStorage; +import net.elytrium.limboapi.api.world.chunk.util.CompactStorage; import net.elytrium.limboapi.mcprotocollib.BitStorage116; import net.elytrium.limboapi.mcprotocollib.BitStorage19; +import net.elytrium.limboapi.protocol.util.LimboProtocolUtils; import net.elytrium.limboapi.server.world.SimpleBlock; import net.elytrium.limboapi.server.world.chunk.SimpleChunk; import org.checkerframework.checker.nullness.qual.NonNull; public class BlockStorage19 implements BlockStorage { + private static final int MIN_BITS_PER_ENTRY = 4; + private static final int MAX_BITS_PER_ENTRY = 8; + private final ProtocolVersion version; private final List palette; private final Map rawToBlock; @@ -48,9 +52,9 @@ public BlockStorage19(ProtocolVersion version) { this.rawToBlock = new HashMap<>(); this.palette.add(SimpleBlock.AIR); - this.rawToBlock.put(SimpleBlock.AIR.getBlockStateID(version), SimpleBlock.AIR); + this.rawToBlock.put(SimpleBlock.AIR.blockStateId(version), SimpleBlock.AIR); - this.storage = this.createStorage(4); + this.storage = this.createStorage(BlockStorage19.MIN_BITS_PER_ENTRY); } private BlockStorage19(ProtocolVersion version, List palette, Map rawToBlock, CompactStorage storage) { @@ -65,15 +69,12 @@ public void write(Object byteBufObject, ProtocolVersion version, int pass) { Preconditions.checkArgument(byteBufObject instanceof ByteBuf); ByteBuf buf = (ByteBuf) byteBufObject; buf.writeByte(this.storage.getBitsPerEntry()); - if (this.storage.getBitsPerEntry() > 8) { - if (this.version.compareTo(ProtocolVersion.MINECRAFT_1_13) < 0) { + if (this.storage.getBitsPerEntry() > BlockStorage19.MAX_BITS_PER_ENTRY) { + if (this.version.noGreaterThan(ProtocolVersion.MINECRAFT_1_12_2)) { ProtocolUtils.writeVarInt(buf, 0); } } else { - ProtocolUtils.writeVarInt(buf, this.palette.size()); - for (VirtualBlock state : this.palette) { - ProtocolUtils.writeVarInt(buf, state.getBlockStateID(this.version)); - } + LimboProtocolUtils.writeCollection(buf, this.palette, state -> ProtocolUtils.writeVarInt(buf, state.blockStateId(this.version))); } this.storage.write(buf, version); @@ -81,32 +82,33 @@ public void write(Object byteBufObject, ProtocolVersion version, int pass) { @Override public void set(int posX, int posY, int posZ, @NonNull VirtualBlock block) { - int id = this.getIndex(block); + int id = this.getId(block); this.storage.set(BlockStorage.index(posX, posY, posZ), id); } - private int getIndex(VirtualBlock block) { - if (this.storage.getBitsPerEntry() > 8) { - short raw = block.getBlockStateID(this.version); + private int getId(VirtualBlock block) { + int bitsPerEntry = this.storage.getBitsPerEntry(); + if (bitsPerEntry > BlockStorage19.MAX_BITS_PER_ENTRY) { + short raw = block.blockStateId(this.version); this.rawToBlock.put(raw, block); return raw; } else { int id = this.palette.indexOf(block); if (id == -1) { - if (this.palette.size() >= (1 << this.storage.getBitsPerEntry())) { - int bitsPerEntry = StorageUtils.fixBitsPerEntry(this.version, this.storage.getBitsPerEntry() + 1); + int size = this.palette.size(); + if (size >= (1 << bitsPerEntry)) { + ++bitsPerEntry; CompactStorage newStorage = this.createStorage(bitsPerEntry); for (int i = 0; i < SimpleChunk.MAX_BLOCKS_PER_SECTION; ++i) { - newStorage.set(i, bitsPerEntry > 8 ? this.palette.get(this.storage.get(i)).getBlockStateID(this.version) : this.storage.get(i)); + newStorage.set(i, bitsPerEntry > BlockStorage19.MAX_BITS_PER_ENTRY ? this.palette.get(this.storage.get(i)).blockStateId(this.version) : this.storage.get(i)); } this.storage = newStorage; - - return this.getIndex(block); + return this.getId(block); } this.palette.add(block); - id = this.palette.size() - 1; + id = size; } return id; @@ -114,7 +116,7 @@ private int getIndex(VirtualBlock block) { } private CompactStorage createStorage(int bitsPerEntry) { - return this.version.compareTo(ProtocolVersion.MINECRAFT_1_16) < 0 + return this.version.lessThan(ProtocolVersion.MINECRAFT_1_16) ? new BitStorage19(bitsPerEntry, SimpleChunk.MAX_BLOCKS_PER_SECTION) : new BitStorage116(bitsPerEntry, SimpleChunk.MAX_BLOCKS_PER_SECTION); } @@ -123,24 +125,20 @@ private CompactStorage createStorage(int bitsPerEntry) { @Override public VirtualBlock get(int posX, int posY, int posZ) { int id = this.storage.get(BlockStorage.index(posX, posY, posZ)); - if (this.storage.getBitsPerEntry() > 8) { - return this.rawToBlock.get((short) id); - } else { - return this.palette.get(id); - } + return this.storage.getBitsPerEntry() > BlockStorage19.MAX_BITS_PER_ENTRY ? this.rawToBlock.get((short) id) : this.palette.get(id); } @Override public int getDataLength(ProtocolVersion version) { int length = 1; - if (this.storage.getBitsPerEntry() > 8) { - if (this.version.compareTo(ProtocolVersion.MINECRAFT_1_13) < 0) { + if (this.storage.getBitsPerEntry() > BlockStorage19.MAX_BITS_PER_ENTRY) { + if (this.version.noGreaterThan(ProtocolVersion.MINECRAFT_1_12_2)) { length += 1; } } else { length += ProtocolUtils.varIntBytes(this.palette.size()); for (VirtualBlock state : this.palette) { - length += ProtocolUtils.varIntBytes(state.getBlockStateID(this.version)); + length += ProtocolUtils.varIntBytes(state.blockStateId(this.version)); } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/protocol/util/NetworkSection.java b/plugin/src/main/java/net/elytrium/limboapi/protocol/data/NetworkSection.java similarity index 59% rename from plugin/src/main/java/net/elytrium/limboapi/protocol/util/NetworkSection.java rename to plugin/src/main/java/net/elytrium/limboapi/protocol/data/NetworkSection.java index 350e607f..5b9f5a46 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/protocol/util/NetworkSection.java +++ b/plugin/src/main/java/net/elytrium/limboapi/protocol/data/NetworkSection.java @@ -15,33 +15,29 @@ * along with this program. If not, see . */ -package net.elytrium.limboapi.protocol.util; +package net.elytrium.limboapi.protocol.data; import com.velocitypowered.api.network.ProtocolVersion; import io.netty.buffer.ByteBuf; import java.util.EnumMap; -import java.util.Map; -import net.elytrium.limboapi.api.chunk.VirtualBiome; -import net.elytrium.limboapi.api.chunk.VirtualBlock; -import net.elytrium.limboapi.api.chunk.data.BlockSection; -import net.elytrium.limboapi.api.chunk.data.BlockStorage; -import net.elytrium.limboapi.api.mcprotocollib.NibbleArray3D; -import net.elytrium.limboapi.protocol.data.BiomeStorage118; -import net.elytrium.limboapi.protocol.data.BlockStorage17; -import net.elytrium.limboapi.protocol.data.BlockStorage19; +import net.elytrium.limboapi.api.world.chunk.biome.VirtualBiome; +import net.elytrium.limboapi.api.world.chunk.block.VirtualBlock; +import net.elytrium.limboapi.api.world.chunk.data.BlockSection; +import net.elytrium.limboapi.api.world.chunk.data.BlockStorage; +import net.elytrium.limboapi.api.world.chunk.util.NibbleArray3D; import net.elytrium.limboapi.server.world.chunk.SimpleChunk; public class NetworkSection { - private final Map storages = new EnumMap<>(ProtocolVersion.class); - private final Map biomeStorages = new EnumMap<>(ProtocolVersion.class); + private final EnumMap storages = new EnumMap<>(ProtocolVersion.class); + private final EnumMap biomeStorages = new EnumMap<>(ProtocolVersion.class); private final NibbleArray3D blockLight; private final NibbleArray3D skyLight; private final BlockSection section; private final VirtualBiome[] biomes; private final int index; - private int blockCount = -1; + private int nonEmptyBlockCount = -1; public NetworkSection(int index, BlockSection section, NibbleArray3D blockLight, NibbleArray3D skyLight, VirtualBiome[] biomes) { this.index = index; @@ -53,83 +49,70 @@ public NetworkSection(int index, BlockSection section, NibbleArray3D blockLight, public int getDataLength(ProtocolVersion version) { int dataLength = this.ensureStorageCreated(version).getDataLength(version); - if (version.compareTo(ProtocolVersion.MINECRAFT_1_14) < 0) { + if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_13_2)) { dataLength += this.blockLight.getData().length; if (this.skyLight != null) { dataLength += this.skyLight.getData().length; } } - if (version.compareTo(ProtocolVersion.MINECRAFT_1_14) >= 0) { + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_14)) { dataLength += 2; // Block count short. } - if (version.compareTo(ProtocolVersion.MINECRAFT_1_17_1) > 0) { + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_18)) { dataLength += this.ensure118BiomeCreated(version).getDataLength(version); } return dataLength; } - public void writeData(ByteBuf buf, int pass, ProtocolVersion version) { - if (version.compareTo(ProtocolVersion.MINECRAFT_1_9) < 0) { - BlockStorage storage = this.ensureStorageCreated(version); - this.write17Data(buf, storage, version, pass); - } else if (pass == 0) { - BlockStorage storage = this.ensureStorageCreated(version); - if (version.compareTo(ProtocolVersion.MINECRAFT_1_14) < 0) { - this.write19Data(buf, storage, version, pass); - } else { - this.write114Data(buf, storage, version, pass); - - if (version.compareTo(ProtocolVersion.MINECRAFT_1_17_1) > 0) { - this.write118Biomes(buf, version); - } + public void writeData(ByteBuf buf, ProtocolVersion version) { + BlockStorage storage = this.ensureStorageCreated(version); + if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_13_2)) { + this.write19Data(buf, storage, version); + } else { + this.write114Data(buf, storage, version); + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_18)) { + this.write118Biomes(buf, version); } } } + public void write17Data(ByteBuf buf, ProtocolVersion version, int pass) { + if (pass == 0 || pass == 1) { + this.ensureStorageCreated(version).write(buf, version, pass); + } else if (pass == 2) { + buf.writeBytes(this.blockLight.getData()); + } else if (pass == 3 && this.skyLight != null) { + buf.writeBytes(this.skyLight.getData()); + } + } + private BlockStorage ensureStorageCreated(ProtocolVersion version) { BlockStorage storage = this.storages.get(version); if (storage == null) { synchronized (this.storages) { - BlockStorage blockStorage = this.createStorage(version); - this.fillBlocks(blockStorage); - this.storages.put(version, blockStorage); - storage = blockStorage; + storage = version.noGreaterThan(ProtocolVersion.MINECRAFT_1_8) ? new BlockStorage17() : new BlockStorage19(version); + this.fillBlocks(storage); + this.storages.put(version, storage); } } return storage; } - private BlockStorage createStorage(ProtocolVersion version) { - if (version.compareTo(ProtocolVersion.MINECRAFT_1_9) < 0) { - return new BlockStorage17(); - } else { - return new BlockStorage19(version); - } - } - - private void write17Data(ByteBuf buf, BlockStorage storage, ProtocolVersion version, int pass) { - if (pass == 0 || pass == 1) { - storage.write(buf, version, pass); - } else if (pass == 2) { - buf.writeBytes(this.blockLight.getData()); - } else if (pass == 3 && this.skyLight != null) { - buf.writeBytes(this.skyLight.getData()); - } - } - - private void write19Data(ByteBuf buf, BlockStorage storage, ProtocolVersion version, int pass) { - storage.write(buf, version, pass); + private void write19Data(ByteBuf buf, BlockStorage storage, ProtocolVersion version) { + storage.write(buf, version, 0); buf.writeBytes(this.blockLight.getData()); if (this.skyLight != null) { buf.writeBytes(this.skyLight.getData()); } } - private void write114Data(ByteBuf buf, BlockStorage storage, ProtocolVersion version, int pass) { - buf.writeShort(this.blockCount); - storage.write(buf, version, pass); + private void write114Data(ByteBuf buf, BlockStorage storage, ProtocolVersion version) { + buf.writeShort(this.nonEmptyBlockCount); + storage.write(buf, version, 0); } private void write118Biomes(ByteBuf buf, ProtocolVersion version) { @@ -154,21 +137,21 @@ private BiomeStorage118 ensure118BiomeCreated(ProtocolVersion version) { } private void fillBlocks(BlockStorage storage) { - int blockCount = 0; + int nonEmptyBlockCount = 0; for (int posX = 0; posX < 16; ++posX) { for (int posY = 0; posY < 16; ++posY) { for (int posZ = 0; posZ < 16; ++posZ) { VirtualBlock block = this.section.getBlockAt(posX, posY, posZ); - if (!block.isAir()) { - ++blockCount; + if (!block.air()) { + ++nonEmptyBlockCount; storage.set(posX, posY, posZ, block); } } } } - if (this.blockCount == -1) { - this.blockCount = blockCount; + if (this.nonEmptyBlockCount == -1) { + this.nonEmptyBlockCount = nonEmptyBlockCount; } } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/PacketFactoryImpl.java b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/PacketFactoryImpl.java index c161d8db..1658fc32 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/PacketFactoryImpl.java +++ b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/PacketFactoryImpl.java @@ -18,46 +18,96 @@ package net.elytrium.limboapi.protocol.packets; import com.velocitypowered.api.network.ProtocolVersion; +import java.util.Collections; import java.util.List; import java.util.Map; -import net.elytrium.limboapi.api.chunk.Dimension; -import net.elytrium.limboapi.api.chunk.data.ChunkSnapshot; -import net.elytrium.limboapi.api.material.VirtualItem; -import net.elytrium.limboapi.api.material.WorldVersion; -import net.elytrium.limboapi.api.protocol.item.ItemComponentMap; -import net.elytrium.limboapi.api.protocol.packets.PacketFactory; -import net.elytrium.limboapi.api.protocol.packets.data.MapData; -import net.elytrium.limboapi.protocol.packets.s2c.ChangeGameStatePacket; +import net.elytrium.limboapi.api.protocol.data.EntityData; +import net.elytrium.limboapi.api.world.chunk.Dimension; +import net.elytrium.limboapi.api.world.chunk.blockentity.BlockEntityVersion; +import net.elytrium.limboapi.api.world.chunk.blockentity.VirtualBlockEntity; +import net.elytrium.limboapi.api.world.chunk.ChunkSnapshot; +import net.elytrium.limboapi.api.world.item.VirtualItem; +import net.elytrium.limboapi.api.world.WorldVersion; +import net.elytrium.limboapi.api.protocol.PreparedPacket; +import net.elytrium.limboapi.api.world.item.datacomponent.DataComponentMap; +import net.elytrium.limboapi.api.protocol.PacketFactory; +import net.elytrium.limboapi.api.protocol.data.MapData; +import net.elytrium.limboapi.protocol.packets.s2c.BlockEntityDataPacket; import net.elytrium.limboapi.protocol.packets.s2c.ChunkDataPacket; import net.elytrium.limboapi.protocol.packets.s2c.ChunkUnloadPacket; import net.elytrium.limboapi.protocol.packets.s2c.DefaultSpawnPositionPacket; +import net.elytrium.limboapi.protocol.packets.s2c.GameEventPacket; +import net.elytrium.limboapi.protocol.packets.s2c.LightUpdatePacket; import net.elytrium.limboapi.protocol.packets.s2c.MapDataPacket; import net.elytrium.limboapi.protocol.packets.s2c.PlayerAbilitiesPacket; -import net.elytrium.limboapi.protocol.packets.s2c.PositionRotationPacket; +import net.elytrium.limboapi.protocol.packets.s2c.PlayerPositionPacket; +import net.elytrium.limboapi.protocol.packets.s2c.SetChunkCacheCenterPacket; +import net.elytrium.limboapi.protocol.packets.s2c.SetEntityDataPacket; import net.elytrium.limboapi.protocol.packets.s2c.SetExperiencePacket; import net.elytrium.limboapi.protocol.packets.s2c.SetSlotPacket; -import net.elytrium.limboapi.protocol.packets.s2c.TimeUpdatePacket; +import net.elytrium.limboapi.protocol.packets.s2c.SetTimePacket; +import net.elytrium.limboapi.protocol.packets.s2c.UpdateSignPacket; import net.elytrium.limboapi.protocol.packets.s2c.UpdateTagsPacket; -import net.elytrium.limboapi.protocol.packets.s2c.UpdateViewPositionPacket; import net.elytrium.limboapi.server.world.SimpleTagManager; import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.text.Component; +import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; public class PacketFactoryImpl implements PacketFactory { @Override - public Object createChangeGameStatePacket(int reason, float value) { - return new ChangeGameStatePacket(reason, value); + public Object createGameEventPacket(int event, float param) { + return new GameEventPacket(event, param); } + /** + * {@inheritDoc} + */ @Override - public Object createChunkDataPacket(ChunkSnapshot chunkSnapshot, boolean legacySkyLight, int maxSections) { - return new ChunkDataPacket(chunkSnapshot, legacySkyLight, maxSections); + public void prepareCompleteChunkDataPacket(ProtocolVersion minPrepareVersion, ProtocolVersion maxPrepareVersion, PreparedPacket packet, ChunkSnapshot chunkSnapshot, boolean hasSkyLight, int maxSections) { + var flowerPots = minPrepareVersion.noGreaterThan(ProtocolVersion.MINECRAFT_1_12_2) ? ChunkDataPacket.getAdditionalFlowerPots(chunkSnapshot) : null; + packet.prepare(new ChunkDataPacket(chunkSnapshot, hasSkyLight, maxSections, flowerPots == null ? Collections.emptyList() : flowerPots)); + packet.prepare( + chunkSnapshot.blockEntityEntriesStream(ProtocolVersion.MINIMUM_VERSION) + .filter(entry -> entry.getBlockEntity().isSupportedOn(BlockEntityVersion.LEGACY)) + .map(entry -> entry.getBlockEntity().getId(BlockEntityVersion.LEGACY) == 9 ? this.createUpdateSignPacket(entry) : this.createBlockEntityDataPacket(entry)) + .toArray(), + ProtocolVersion.MINIMUM_VERSION, ProtocolVersion.MINECRAFT_1_9_2 + ); + if (flowerPots != null) { + for (VirtualBlockEntity.Entry entry : flowerPots) { + packet.prepare(this.createBlockEntityDataPacket(entry), ProtocolVersion.MINIMUM_VERSION, ProtocolVersion.MINECRAFT_1_9_2); + } + } + if (maxPrepareVersion.noLessThan(ProtocolVersion.MINECRAFT_1_14) && minPrepareVersion.noGreaterThan(ProtocolVersion.MINECRAFT_1_17_1)) { + packet.prepare(this.createLightUpdatePacket(chunkSnapshot, hasSkyLight), ProtocolVersion.MINECRAFT_1_14, ProtocolVersion.MINECRAFT_1_17_1); + } } @Override - public Object createChunkDataPacket(ChunkSnapshot chunkSnapshot, Dimension dimension) { - return new ChunkDataPacket(chunkSnapshot, dimension.hasLegacySkyLight(), dimension.getMaxSections()); + public Object createChunkDataPacket(ChunkSnapshot chunkSnapshot, boolean hasSkyLight, int maxSections) { + return new ChunkDataPacket(chunkSnapshot, hasSkyLight, maxSections, null); + } + + @Override + public Object createLightUpdatePacket(ChunkSnapshot chunkSnapshot, boolean hasSkyLight) { + return new LightUpdatePacket(chunkSnapshot, hasSkyLight); + } + + @Override + public Object createBlockEntityDataPacket(VirtualBlockEntity.Entry entry) { + return new BlockEntityDataPacket(entry); + } + + @Override + public Object createUpdateSignPacket(int posX, int posY, int posZ, @NonNull Component @NonNull [] lines) { + return new UpdateSignPacket(posX, posY, posZ, lines); + } + + @Override + public Object createUpdateSignPacket(VirtualBlockEntity.Entry entry) { + return new UpdateSignPacket(entry); } @Override @@ -67,53 +117,87 @@ public Object createChunkUnloadPacket(int posX, int posZ) { @Override public Object createDefaultSpawnPositionPacket(int posX, int posY, int posZ, float angle) { - return new DefaultSpawnPositionPacket(posX, posY, posZ, angle); + return new DefaultSpawnPositionPacket(Dimension.OVERWORLD.getKey(), posX, posY, posZ, angle, 0.0F); + } + + @Override + public Object createDefaultSpawnPositionPacket(String dimension, int posX, int posY, int posZ, float yaw, float pitch) { + return new DefaultSpawnPositionPacket(dimension, posX, posY, posZ, yaw, pitch); + } + + @Override + public Object createMapDataPacket(int mapId, byte scale, MapData mapData) { + return new MapDataPacket(mapId, scale, mapData); + } + + @Override + public Object createEntityDataPacket(int id, EntityData data) { + return new SetEntityDataPacket(id, data); + } + + @Override + public Object createPlayerAbilitiesPacket(int abilities, float flyingSpeed, float walkingSpeed) { + return new PlayerAbilitiesPacket((byte) abilities, flyingSpeed, walkingSpeed); + } + + @Override + public Object createPlayerAbilitiesPacket(byte abilities, float flyingSpeed, float walkingSpeed) { + return new PlayerAbilitiesPacket(abilities, flyingSpeed, walkingSpeed); + } + + @Override + public Object createPlayerPositionPacket(double posX, double posY, double posZ, float yaw, float pitch, boolean onGround, int teleportId, boolean dismountVehicle) { + return new PlayerPositionPacket(posX, posY, posZ, yaw, pitch, onGround, teleportId, dismountVehicle); + } + + @Override + public Object createSetExperiencePacket(float experienceProgress, int experienceLevel, int totalExperience) { + return new SetExperiencePacket(experienceProgress, experienceLevel, totalExperience); } @Override - public Object createMapDataPacket(int mapID, byte scale, MapData mapData) { - return new MapDataPacket(mapID, scale, mapData); + public Object createSetSlotPacket(int containerId, int slot, VirtualItem item, int count) { + return new SetSlotPacket(containerId, slot, item, count); } @Override - public Object createPlayerAbilitiesPacket(int flags, float flySpeed, float walkSpeed) { - return new PlayerAbilitiesPacket((byte) flags, flySpeed, walkSpeed); + public Object createSetSlotPacket(int containerId, int slot, VirtualItem item, int count, @Nullable CompoundBinaryTag nbt) { + return new SetSlotPacket(containerId, slot, item, count, nbt); } @Override - public Object createPlayerAbilitiesPacket(byte flags, float flySpeed, float walkSpeed) { - return new PlayerAbilitiesPacket(flags, flySpeed, walkSpeed); + public Object createSetSlotPacket(int containerId, int slot, VirtualItem item, int count, @Nullable DataComponentMap map) { + return new SetSlotPacket(containerId, slot, item, count, map); } @Override - public Object createPositionRotationPacket(double posX, double posY, double posZ, float yaw, float pitch, - boolean onGround, int teleportID, boolean dismountVehicle) { - return new PositionRotationPacket(posX, posY, posZ, yaw, pitch, onGround, teleportID, dismountVehicle); + public Object createSetSlotPacket(int containerId, int slot, VirtualItem item, int count, short data) { + return new SetSlotPacket(containerId, slot, item, count, data); } @Override - public Object createSetExperiencePacket(float expBar, int level, int totalExp) { - return new SetExperiencePacket(expBar, level, totalExp); + public Object createSetSlotPacket(int containerId, int slot, VirtualItem item, int count, short data, @Nullable CompoundBinaryTag nbt) { + return new SetSlotPacket(containerId, slot, item, count, data, nbt); } @Override - public Object createSetSlotPacket(int windowID, int slot, VirtualItem item, int count, int data, @Nullable CompoundBinaryTag nbt) { - return new SetSlotPacket(windowID, slot, item, count, data, nbt, null); + public Object createSetSlotPacket(int containerId, int slot, VirtualItem item, int count, short data, @Nullable DataComponentMap map) { + return new SetSlotPacket(containerId, slot, item, count, data, map); } @Override - public Object createSetSlotPacket(int windowID, int slot, VirtualItem item, int count, int data, @Nullable ItemComponentMap map) { - return new SetSlotPacket(windowID, slot, item, count, data, null, map); + public Object createSetSlotPacket(int containerId, int slot, VirtualItem item, int count, short data, @Nullable CompoundBinaryTag nbt, @Nullable DataComponentMap map) { + return new SetSlotPacket(containerId, slot, item, count, data, nbt, map); } @Override - public Object createTimeUpdatePacket(long worldAge, long timeOfDay) { - return new TimeUpdatePacket(worldAge, timeOfDay); + public Object createSetTimePacket(long gameTime, long dayTime) { + return new SetTimePacket(gameTime, dayTime); } @Override - public Object createUpdateViewPositionPacket(int posX, int posZ) { - return new UpdateViewPositionPacket(posX, posZ); + public Object createSetChunkCacheCenter(int posX, int posZ) { + return new SetChunkCacheCenterPacket(posX, posZ); } @Override @@ -127,7 +211,7 @@ public Object createUpdateTagsPacket(ProtocolVersion version) { } @Override - public Object createUpdateTagsPacket(Map>> tags) { + public Object createUpdateTagsPacket(Map> tags) { return new UpdateTagsPacket(tags); } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/c2s/TeleportConfirmPacket.java b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/c2s/AcceptTeleportationPacket.java similarity index 80% rename from plugin/src/main/java/net/elytrium/limboapi/protocol/packets/c2s/TeleportConfirmPacket.java rename to plugin/src/main/java/net/elytrium/limboapi/protocol/packets/c2s/AcceptTeleportationPacket.java index 3ec13d5e..57b55844 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/c2s/TeleportConfirmPacket.java +++ b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/c2s/AcceptTeleportationPacket.java @@ -24,13 +24,13 @@ import io.netty.buffer.ByteBuf; import net.elytrium.limboapi.server.LimboSessionHandlerImpl; -public class TeleportConfirmPacket implements MinecraftPacket { +public class AcceptTeleportationPacket implements MinecraftPacket { - private int teleportID; + private int id; @Override public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { - this.teleportID = ProtocolUtils.readVarInt(buf); + this.id = ProtocolUtils.readVarInt(buf); } @Override @@ -40,11 +40,7 @@ public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersi @Override public boolean handle(MinecraftSessionHandler handler) { - if (handler instanceof LimboSessionHandlerImpl) { - return ((LimboSessionHandlerImpl) handler).handle(this); - } else { - return true; - } + return !(handler instanceof LimboSessionHandlerImpl limbo) || limbo.handle(this); } @Override @@ -57,14 +53,14 @@ public int decodeExpectedMinLength(ByteBuf buf, ProtocolUtils.Direction directio return 1; } + public int getId() { + return this.id; + } + @Override public String toString() { - return "TeleportConfirm{" - + "teleportID=" + this.teleportID + return "AcceptTeleportationPacket{" + + "teleportId=" + this.id + "}"; } - - public int getTeleportID() { - return this.teleportID; - } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/c2s/ChatSessionUpdatePacket.java b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/c2s/ChatSessionUpdatePacket.java new file mode 100644 index 00000000..ea2c07d8 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/c2s/ChatSessionUpdatePacket.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2021 - 2025 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limboapi.protocol.packets.c2s; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.api.proxy.crypto.IdentifiedKey; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler; +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import java.util.UUID; +import net.elytrium.limboapi.Settings; + +public class ChatSessionUpdatePacket implements MinecraftPacket { + + private UUID sessionId; + private IdentifiedKey profilePublicKey; + + @Override + public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + this.sessionId = ProtocolUtils.readUuid(buf); + this.profilePublicKey = ProtocolUtils.readPlayerKey(protocolVersion, buf); + } + + @Override + public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + ProtocolUtils.writeUuid(buf, this.sessionId); + ProtocolUtils.writePlayerKey(buf, this.profilePublicKey); + } + + @Override + public boolean handle(MinecraftSessionHandler minecraftSessionHandler) { + // LimboAPI hook - skip server-side signature verification if enabled + return minecraftSessionHandler instanceof ClientPlaySessionHandler && Settings.IMP.MAIN.FORCE_DISABLE_MODERN_CHAT_SIGNING; + } + + public UUID getSessionId() { + return this.sessionId; + } + + public IdentifiedKey getProfilePublicKey() { + return this.profilePublicKey; + } + + @Override + public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { + return this.encodeSizeHint(direction, version); + } + + @Override + public int decodeExpectedMinLength(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { + return this.encodeSizeHint(direction, version); + } + + @Override + public int encodeSizeHint(ProtocolUtils.Direction direction, ProtocolVersion version) { + return Long.BYTES * 2 + Long.BYTES/*expiry*/ + 2/*key size*/ + 294/*key*/ + 2/*sign size*/ + 512/*sign*/; + } + + @Override + public String toString() { + return "PlayerChatSessionPacket{" + + "holderId=" + this.sessionId + + ", playerKey=" + this.profilePublicKey + + "}"; + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/c2s/MoveOnGroundOnlyPacket.java b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/c2s/MoveOnGroundOnlyPacket.java index 1e05f742..189c08e8 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/c2s/MoveOnGroundOnlyPacket.java +++ b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/c2s/MoveOnGroundOnlyPacket.java @@ -35,8 +35,8 @@ public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersi this.onGround = buf.readBoolean(); } else { int flags = buf.readUnsignedByte(); - this.onGround = (flags & 1) != 0; - this.collideHorizontally = (flags & 2) != 0; + this.onGround = (flags & 0b01) != 0; + this.collideHorizontally = (flags & 0b10) != 0; } } @@ -47,11 +47,7 @@ public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersi @Override public boolean handle(MinecraftSessionHandler handler) { - if (handler instanceof LimboSessionHandlerImpl) { - return ((LimboSessionHandlerImpl) handler).handle(this); - } else { - return true; - } + return !(handler instanceof LimboSessionHandlerImpl limbo) || limbo.handle(this); } @Override @@ -64,14 +60,6 @@ public int decodeExpectedMinLength(ByteBuf buf, ProtocolUtils.Direction directio return 1; } - @Override - public String toString() { - return "Player{" - + "onGround=" + this.onGround - + ", collideHorizontally=" + this.collideHorizontally - + "}"; - } - public boolean isOnGround() { return this.onGround; } @@ -79,4 +67,12 @@ public boolean isOnGround() { public boolean isCollideHorizontally() { return this.collideHorizontally; } + + @Override + public String toString() { + return "MoveOnGroundOnlyPacket{" + + "onGround=" + this.onGround + + ", collideHorizontally=" + this.collideHorizontally + + "}"; + } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/c2s/MovePacket.java b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/c2s/MovePacket.java index 8272b611..a9678e43 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/c2s/MovePacket.java +++ b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/c2s/MovePacket.java @@ -21,7 +21,6 @@ import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolUtils; -import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; import io.netty.buffer.ByteBuf; import net.elytrium.limboapi.server.LimboSessionHandlerImpl; @@ -36,60 +35,42 @@ public class MovePacket implements MinecraftPacket { private boolean collideHorizontally; @Override - public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { + public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { this.posX = buf.readDouble(); - if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_8) < 0) { - buf.skipBytes(8); - } this.posY = buf.readDouble(); + if (protocolVersion.noGreaterThan(ProtocolVersion.MINECRAFT_1_7_6)) { + buf.skipBytes(Double.BYTES); // eyes pos + } this.posZ = buf.readDouble(); this.yaw = buf.readFloat(); this.pitch = buf.readFloat(); - if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_21_2)) { this.onGround = buf.readBoolean(); } else { int flags = buf.readUnsignedByte(); - this.onGround = (flags & 1) != 0; - this.collideHorizontally = (flags & 2) != 0; + this.onGround = (flags & 0b01) != 0; + this.collideHorizontally = (flags & 0b10) != 0; } } @Override - public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { + public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { throw new IllegalStateException(); } @Override public boolean handle(MinecraftSessionHandler handler) { - if (handler instanceof LimboSessionHandlerImpl) { - return ((LimboSessionHandlerImpl) handler).handle(this); - } else { - return true; - } + return !(handler instanceof LimboSessionHandlerImpl limbo) || limbo.handle(this); } @Override public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - return version.compareTo(ProtocolVersion.MINECRAFT_1_8) < 0 ? 41 : 33; + return version.noGreaterThan(ProtocolVersion.MINECRAFT_1_7_6) ? Double.BYTES * 4 + Float.BYTES * 2 + 1 : Double.BYTES * 3 + Float.BYTES * 2 + 1; } @Override public int decodeExpectedMinLength(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - return 33; - } - - @Override - public String toString() { - return "PlayerPositionAndLook{" - + "posX=" + this.posX - + ", posY=" + this.posY - + ", posZ=" + this.posZ - + ", yaw=" + this.yaw - + ", pitch=" + this.pitch - + ", onGround=" + this.onGround - + ", collideHorizontally=" + this.collideHorizontally - + "}"; + return this.decodeExpectedMaxLength(buf, direction, version); } public double getX() { @@ -119,4 +100,17 @@ public boolean isOnGround() { public boolean isCollideHorizontally() { return this.collideHorizontally; } + + @Override + public String toString() { + return "MovePacket{" + + "posX=" + this.posX + + ", posY=" + this.posY + + ", posZ=" + this.posZ + + ", yaw=" + this.yaw + + ", pitch=" + this.pitch + + ", onGround=" + this.onGround + + ", collideHorizontally=" + this.collideHorizontally + + "}"; + } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/c2s/MovePositionOnlyPacket.java b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/c2s/MovePositionOnlyPacket.java index 7c64ddad..7c74ce7a 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/c2s/MovePositionOnlyPacket.java +++ b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/c2s/MovePositionOnlyPacket.java @@ -35,18 +35,17 @@ public class MovePositionOnlyPacket implements MinecraftPacket { @Override public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { this.posX = buf.readDouble(); - if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_8) < 0) { - buf.skipBytes(8); - } this.posY = buf.readDouble(); + if (protocolVersion.noGreaterThan(ProtocolVersion.MINECRAFT_1_7_6)) { + buf.skipBytes(Double.BYTES); // eyes pos + } this.posZ = buf.readDouble(); - if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_21_2)) { this.onGround = buf.readBoolean(); } else { int flags = buf.readUnsignedByte(); - this.onGround = (flags & 1) != 0; - this.collideHorizontally = (flags & 2) != 0; + this.onGround = (flags & 0b01) != 0; + this.collideHorizontally = (flags & 0b10) != 0; } } @@ -57,32 +56,17 @@ public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersi @Override public boolean handle(MinecraftSessionHandler handler) { - if (handler instanceof LimboSessionHandlerImpl) { - return ((LimboSessionHandlerImpl) handler).handle(this); - } else { - return true; - } + return !(handler instanceof LimboSessionHandlerImpl limbo) || limbo.handle(this); } @Override public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - return version.compareTo(ProtocolVersion.MINECRAFT_1_8) < 0 ? 33 : 25; + return version.noGreaterThan(ProtocolVersion.MINECRAFT_1_7_6) ? Double.BYTES * 4 + 1 : Double.BYTES * 3 + 1; } @Override public int decodeExpectedMinLength(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - return 25; - } - - @Override - public String toString() { - return "PlayerPosition{" - + "posX=" + this.posX - + ", posY=" + this.posY - + ", posZ=" + this.posZ - + ", onGround=" + this.onGround - + ", collideHorizontally=" + this.collideHorizontally - + "}"; + return this.decodeExpectedMaxLength(buf, direction, version); } public double getX() { @@ -104,4 +88,15 @@ public boolean isOnGround() { public boolean isCollideHorizontally() { return this.collideHorizontally; } + + @Override + public String toString() { + return "MovePositionOnlyPacket{" + + "posX=" + this.posX + + ", posY=" + this.posY + + ", posZ=" + this.posZ + + ", onGround=" + this.onGround + + ", collideHorizontally=" + this.collideHorizontally + + "}"; + } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/c2s/MoveRotationOnlyPacket.java b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/c2s/MoveRotationOnlyPacket.java index 58dbe6bb..5edaa4db 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/c2s/MoveRotationOnlyPacket.java +++ b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/c2s/MoveRotationOnlyPacket.java @@ -21,7 +21,6 @@ import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolUtils; -import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; import io.netty.buffer.ByteBuf; import net.elytrium.limboapi.server.LimboSessionHandlerImpl; @@ -33,51 +32,36 @@ public class MoveRotationOnlyPacket implements MinecraftPacket { private boolean collideHorizontally; @Override - public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { + public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { this.yaw = buf.readFloat(); this.pitch = buf.readFloat(); - if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_21_2)) { this.onGround = buf.readBoolean(); } else { int flags = buf.readUnsignedByte(); - this.onGround = (flags & 1) != 0; - this.collideHorizontally = (flags & 2) != 0; + this.onGround = (flags & 0b01) != 0; + this.collideHorizontally = (flags & 0b10) != 0; } } @Override - public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { + public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { throw new IllegalStateException(); } @Override public boolean handle(MinecraftSessionHandler handler) { - if (handler instanceof LimboSessionHandlerImpl) { - return ((LimboSessionHandlerImpl) handler).handle(this); - } else { - return true; - } + return !(handler instanceof LimboSessionHandlerImpl limbo) || limbo.handle(this); } @Override public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - return 9; + return Float.BYTES * 2 + 1; } @Override public int decodeExpectedMinLength(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - return 9; - } - - @Override - public String toString() { - return "PlayerLook{" - + ", yaw=" + this.yaw - + ", pitch=" + this.pitch - + ", onGround=" + this.onGround - + ", collideHorizontally=" + this.collideHorizontally - + "}"; + return this.decodeExpectedMaxLength(buf, direction, version); } public float getYaw() { @@ -95,4 +79,14 @@ public boolean isOnGround() { public boolean isCollideHorizontally() { return this.collideHorizontally; } + + @Override + public String toString() { + return "MoveRotationOnlyPacket{" + + ", yaw=" + this.yaw + + ", pitch=" + this.pitch + + ", onGround=" + this.onGround + + ", collideHorizontally=" + this.collideHorizontally + + "}"; + } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/c2s/PlayerChatSessionPacket.java b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/c2s/PlayerChatSessionPacket.java deleted file mode 100644 index a8448055..00000000 --- a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/c2s/PlayerChatSessionPacket.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package net.elytrium.limboapi.protocol.packets.c2s; - -import com.velocitypowered.api.network.ProtocolVersion; -import com.velocitypowered.api.proxy.crypto.IdentifiedKey; -import com.velocitypowered.proxy.connection.MinecraftSessionHandler; -import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler; -import com.velocitypowered.proxy.protocol.MinecraftPacket; -import com.velocitypowered.proxy.protocol.ProtocolUtils; -import io.netty.buffer.ByteBuf; -import java.util.UUID; -import net.elytrium.limboapi.Settings; - -@SuppressWarnings("unused") -public class PlayerChatSessionPacket implements MinecraftPacket { - - private UUID holderId; - private IdentifiedKey playerKey; - - @Override - public void decode(ByteBuf byteBuf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { - this.holderId = ProtocolUtils.readUuid(byteBuf); - this.playerKey = ProtocolUtils.readPlayerKey(protocolVersion, byteBuf); - } - - @Override - public void encode(ByteBuf byteBuf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { - ProtocolUtils.writeUuid(byteBuf, this.holderId); - ProtocolUtils.writePlayerKey(byteBuf, this.playerKey); - } - - @Override - public boolean handle(MinecraftSessionHandler minecraftSessionHandler) { - // LimboAPI hook - skip server-side signature verification if enabled - if (minecraftSessionHandler instanceof ClientPlaySessionHandler) { - return Settings.IMP.MAIN.FORCE_DISABLE_MODERN_CHAT_SIGNING; - } - - return false; - } - - public UUID getHolderId() { - return this.holderId; - } - - public void setHolderId(UUID holderId) { - this.holderId = holderId; - } - - public IdentifiedKey getPlayerKey() { - return this.playerKey; - } - - public void setPlayerKey(IdentifiedKey playerKey) { - this.playerKey = playerKey; - } - -} diff --git a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/BlockEntityDataPacket.java b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/BlockEntityDataPacket.java new file mode 100644 index 00000000..e00b4b6a --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/BlockEntityDataPacket.java @@ -0,0 +1,36 @@ +package net.elytrium.limboapi.protocol.packets.s2c; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import net.elytrium.limboapi.api.world.chunk.blockentity.VirtualBlockEntity; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.server.item.codec.data.BlockPosCodec; + +public record BlockEntityDataPacket(VirtualBlockEntity.Entry entry) implements MinecraftPacket { + + @Override + public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + throw new IllegalStateException(); + } + + @Override + public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + if (protocolVersion.noGreaterThan(ProtocolVersion.MINECRAFT_1_7_6)) { + buf.writeInt(this.entry.getPosX()); + buf.writeShort(this.entry.getPosY()); + buf.writeInt(this.entry.getPosZ()); + } else { + BlockPosCodec.encode(buf, protocolVersion, this.entry.getPosX(), this.entry.getPosY(), this.entry.getPosZ()); + } + ProtocolUtils.writeVarInt(buf, this.entry.getBlockEntity().getId(protocolVersion)); + ByteBufCodecs.OPTIONAL_COMPOUND_TAG.encode(buf, protocolVersion, this.entry.getNbt(protocolVersion)); + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + throw new IllegalStateException(); + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/ChunkDataPacket.java b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/ChunkDataPacket.java index c75cee60..18c72597 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/ChunkDataPacket.java +++ b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/ChunkDataPacket.java @@ -17,222 +17,241 @@ package net.elytrium.limboapi.protocol.packets.s2c; -import com.google.common.base.Preconditions; import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.natives.util.MoreByteBufUtils; +import com.velocitypowered.natives.util.Natives; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolUtils; -import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; -import java.util.BitSet; -import java.util.HashMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; -import java.util.Map; -import java.util.zip.Deflater; +import java.util.function.Consumer; +import java.util.zip.DataFormatException; import net.elytrium.limboapi.LimboAPI; -import net.elytrium.limboapi.api.chunk.VirtualBlock; -import net.elytrium.limboapi.api.chunk.VirtualBlockEntity; -import net.elytrium.limboapi.api.chunk.data.ChunkSnapshot; -import net.elytrium.limboapi.api.chunk.data.LightSection; -import net.elytrium.limboapi.api.chunk.util.CompactStorage; -import net.elytrium.limboapi.api.material.Block; -import net.elytrium.limboapi.api.protocol.packets.data.BiomeData; -import net.elytrium.limboapi.material.Biome; +import net.elytrium.limboapi.api.world.chunk.biome.VirtualBiome; +import net.elytrium.limboapi.api.world.chunk.block.VirtualBlock; +import net.elytrium.limboapi.api.world.chunk.blockentity.VirtualBlockEntity; +import net.elytrium.limboapi.api.world.chunk.data.BlockSection; +import net.elytrium.limboapi.api.world.chunk.ChunkSnapshot; +import net.elytrium.limboapi.api.world.chunk.data.LightSection; +import net.elytrium.limboapi.api.world.chunk.util.CompactStorage; +import net.elytrium.limboapi.api.world.chunk.block.Block; +import net.elytrium.limboapi.api.protocol.data.BiomeData; +import net.elytrium.limboapi.server.world.Biome; import net.elytrium.limboapi.mcprotocollib.BitStorage116; import net.elytrium.limboapi.mcprotocollib.BitStorage19; -import net.elytrium.limboapi.protocol.util.NetworkSection; -import net.kyori.adventure.nbt.BinaryTag; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.util.LimboProtocolUtils; +import net.elytrium.limboapi.protocol.data.NetworkSection; +import net.elytrium.limboapi.server.world.SimpleBlockEntity; import net.kyori.adventure.nbt.CompoundBinaryTag; -import net.kyori.adventure.nbt.LongArrayBinaryTag; +// TODO fix 1.17.x spawn with offset -1 by y (i guess the client tries to fall before chunks are loaded, and as a result, due to the ping, the player may spawn inside the blocks) public class ChunkDataPacket implements MinecraftPacket { private final ChunkSnapshot chunk; private final NetworkSection[] sections; - private final int mask; + private final int availableSections; + private final int skyLightMask; private final int maxSections; private final int nonNullSections; private final BiomeData biomeData; private final CompoundBinaryTag heightmap114; private final CompoundBinaryTag heightmap116; - private final Map heightmap1215; + private final Int2ObjectArrayMap heightmap1215; + private final boolean hasSkyLight; - public ChunkDataPacket(ChunkSnapshot chunkSnapshot, boolean hasLegacySkyLight, int maxSections) { - this.maxSections = maxSections; + private List flowerPots; + + public ChunkDataPacket(ChunkSnapshot chunk, boolean hasSkyLight, int maxSections) { + this(chunk, hasSkyLight, maxSections, null); + } + + public ChunkDataPacket(ChunkSnapshot chunk, boolean hasSkyLight, int maxSections, List flowerPots) { + this.chunk = chunk; this.sections = new NetworkSection[maxSections]; - this.chunk = chunkSnapshot; - int mask = 0; + boolean fullChunk = chunk.fullChunk(); + int availableSections = 0; + int skyLightMask = fullChunk ? 0x3FFFF : 0; int nonNullSections = 0; - for (int i = 0; i < this.chunk.getSections().length; ++i) { - if (this.chunk.getSections()[i] != null) { + BlockSection[] sections = chunk.sections(); + LightSection[] light = chunk.light(); + VirtualBiome[] biomes = chunk.biomes(); + for (int i = 0; i < sections.length; ++i) { + BlockSection section = sections[i]; + if (section != null) { + { + availableSections |= 1 << i; + if (!fullChunk && hasSkyLight) { + skyLightMask |= (1 << i); + } + } ++nonNullSections; - mask |= 1 << i; - LightSection light = this.chunk.getLight()[i]; - NetworkSection section = new NetworkSection( - i, - this.chunk.getSections()[i], - light.getBlockLight(), - hasLegacySkyLight ? light.getSkyLight() : null, - this.chunk.getBiomes() - ); - this.sections[i] = section; + LightSection lightSection = light[i]; + this.sections[i] = new NetworkSection(i, section, lightSection.getBlockLight(), hasSkyLight ? lightSection.getSkyLight() : null, biomes); } } + this.availableSections = availableSections; + this.skyLightMask = skyLightMask; + this.maxSections = maxSections; this.nonNullSections = nonNullSections; - this.mask = mask; - this.heightmap114 = this.createHeightMap(true); - this.heightmap116 = this.createHeightMap(false); - this.heightmap1215 = new HashMap<>(); - for (Map.Entry entry : this.heightmap116) { - this.heightmap1215.put(this.findHeightMapId(entry.getKey()), ((LongArrayBinaryTag) entry.getValue()).value()); - } - - this.biomeData = new BiomeData(this.chunk); - } - - private int findHeightMapId(String key) { - return switch (key) { - case "WORLD_SURFACE" -> 1; /* taken from minecraft decompiled source code */ - case "MOTION_BLOCKING" -> 4; /* taken from minecraft decompiled source code */ - default -> throw new IllegalArgumentException("Unsupported heightmap: " + key); - }; - } - - public ChunkDataPacket() { - throw new IllegalStateException(); + this.biomeData = new BiomeData(chunk); + this.heightmap114 = this.createHeightMap(null); + this.heightmap116 = this.createHeightMap(this.heightmap1215 = new Int2ObjectArrayMap<>()); + this.hasSkyLight = hasSkyLight; + this.flowerPots = flowerPots; } @Override - public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { + public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { throw new IllegalStateException(); } @Override - public void encode(ByteBuf buf, Direction direction, ProtocolVersion version) { - if (!this.chunk.isFullChunk()) { - // 1.17 supports only full chunks. - Preconditions.checkState(version.compareTo(ProtocolVersion.MINECRAFT_1_17) < 0); + public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + boolean fullChunk = this.chunk.fullChunk(); + if (!fullChunk && protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_17)) { + throw new IllegalStateException(">=1.17 supports only full chunks"); } - buf.writeInt(this.chunk.getPosX()); - buf.writeInt(this.chunk.getPosZ()); - if (version.compareTo(ProtocolVersion.MINECRAFT_1_17) >= 0) { - // 1.17 mask. - if (version.compareTo(ProtocolVersion.MINECRAFT_1_17_1) <= 0) { - long[] mask = this.create117Mask(); - ProtocolUtils.writeVarInt(buf, mask.length); - for (long l : mask) { - buf.writeLong(l); - } + buf.writeLong(((long) this.chunk.posX() & 0xFFFFFFFFL) << 32 | (long) this.chunk.posZ() & 0xFFFFFFFFL); + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_17)) { + if (protocolVersion == ProtocolVersion.MINECRAFT_1_17 || protocolVersion == ProtocolVersion.MINECRAFT_1_17_1) { + ProtocolUtils.writeVarInt(buf, 1); // length + buf.writeLong(this.availableSections); } } else { - buf.writeBoolean(this.chunk.isFullChunk()); + buf.writeBoolean(fullChunk); - if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) >= 0 && version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) < 0) { - buf.writeBoolean(true); // Ignore old data. + if (protocolVersion == ProtocolVersion.MINECRAFT_1_16 || protocolVersion == ProtocolVersion.MINECRAFT_1_16_1) { + buf.writeBoolean(true); // forgetOldData } - // Mask. - if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) > 0) { - ProtocolUtils.writeVarInt(buf, this.mask); + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_9)) { + ProtocolUtils.writeVarInt(buf, this.availableSections); } else { - // OptiFine devs have over-optimized the chunk loading by breaking loading of void-chunks. - // We are changing void-chunks length here, and OptiFine client thinks that the chunk is not void-alike. - buf.writeShort(this.mask == 0 ? 1 : this.mask); + // <=1.8 client doesn't treat void-alike chunks as loaded and do slow fall + // We are changing void-sections count here, and the client thinks that the chunk is loaded + buf.writeShort(this.availableSections == 0 ? 1 : this.availableSections); } } - // 1.14+ heightMap. - if (version.compareTo(ProtocolVersion.MINECRAFT_1_14) >= 0) { - if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) < 0) { - ProtocolUtils.writeBinaryTag(buf, version, this.heightmap114); - } else if (version.compareTo(ProtocolVersion.MINECRAFT_1_21_5) < 0) { - ProtocolUtils.writeBinaryTag(buf, version, this.heightmap116); + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_14)) { + if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_16)) { + ProtocolUtils.writeBinaryTag(buf, protocolVersion, this.heightmap114); + } else if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_21_5)) { + ProtocolUtils.writeBinaryTag(buf, protocolVersion, this.heightmap116); } else { - ProtocolUtils.writeVarInt(buf, this.heightmap1215.size()); - for (Map.Entry entry : this.heightmap1215.entrySet()) { - ProtocolUtils.writeVarInt(buf, entry.getKey()); - ProtocolUtils.writeVarInt(buf, entry.getValue().length); - for (long l : entry.getValue()) { - buf.writeLong(l); - } - } + LimboProtocolUtils.writeMap(buf, this.heightmap1215, ProtocolUtils::writeVarInt, LimboProtocolUtils::writeLongArray); } } - // 1.15 - 1.17 biomes. - if (this.chunk.isFullChunk() && version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0 && version.compareTo(ProtocolVersion.MINECRAFT_1_17_1) <= 0) { - if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0) { - ProtocolUtils.writeVarInt(buf, this.biomeData.getPost115Biomes().length); - for (int b : this.biomeData.getPost115Biomes()) { - ProtocolUtils.writeVarInt(buf, b); - } + // 1.15 - 1.17.1 biomes + if (fullChunk && protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_15) && protocolVersion.noGreaterThan(ProtocolVersion.MINECRAFT_1_17_1)) { + int[] post115Biomes = this.biomeData.getPost115Biomes(); + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_16_2)) { + LimboProtocolUtils.writeVarIntArray(buf, post115Biomes); } else { - for (int b : this.biomeData.getPost115Biomes()) { - buf.writeInt(b); + for (int value : post115Biomes) { + buf.writeInt(value); } } } - ByteBuf data = this.createChunkData(version); + ByteBuf data = this.createChunkData(protocolVersion); try { - if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) { + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_8)) { ProtocolUtils.writeVarInt(buf, data.readableBytes()); buf.writeBytes(data); - if (version.compareTo(ProtocolVersion.MINECRAFT_1_9_4) >= 0) { - List blockEntityEntries = this.chunk.getBlockEntityEntries(); - ProtocolUtils.writeVarInt(buf, blockEntityEntries.size()); - for (VirtualBlockEntity.Entry blockEntityEntry : blockEntityEntries) { - CompoundBinaryTag blockEntityNbt = blockEntityEntry.getNbt(); - if (version.compareTo(ProtocolVersion.MINECRAFT_1_18) >= 0) { - buf.writeByte(((blockEntityEntry.getPosX() & 15) << 4) | (blockEntityEntry.getPosZ() & 15)); - buf.writeShort(blockEntityEntry.getPosY()); - ProtocolUtils.writeVarInt(buf, blockEntityEntry.getID(version)); - } else { - blockEntityNbt.putString("id", blockEntityEntry.getBlockEntity().getModernID()); - blockEntityNbt.putInt("x", blockEntityEntry.getPosX()); - blockEntityNbt.putInt("y", blockEntityEntry.getPosY()); - blockEntityNbt.putInt("z", blockEntityEntry.getPosZ()); + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_9_4)) { + Consumer encoder = entry -> { + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_18)) { + buf.writeByte(((entry.getPosX() & 0x0F) << 4) | (entry.getPosZ() & 0x0F)); + buf.writeShort(entry.getPosY()); + var blockEntity = entry.getBlockEntity(); + int id = blockEntity.getId(protocolVersion); + if (id == Integer.MIN_VALUE) { + LimboAPI.getLogger().warn("Could not find block entity id '{}' for {}", blockEntity.getModernId(), protocolVersion); + ProtocolUtils.writeVarInt(buf, 0); + } else { + ProtocolUtils.writeVarInt(buf, id); + } } - ProtocolUtils.writeBinaryTag(buf, version, blockEntityNbt); - } - } - if (version.compareTo(ProtocolVersion.MINECRAFT_1_17_1) > 0) { - long[] mask = this.create117Mask(); - if (version.compareTo(ProtocolVersion.MINECRAFT_1_20) < 0) { - buf.writeBoolean(true); // Trust edges. - } - ProtocolUtils.writeVarInt(buf, mask.length); // Skylight mask. - for (long m : mask) { - buf.writeLong(m); - } - ProtocolUtils.writeVarInt(buf, mask.length); // BlockLight mask. - for (long m : mask) { - buf.writeLong(m); + ByteBufCodecs.OPTIONAL_COMPOUND_TAG.encode(buf, protocolVersion, entry.getNbt(protocolVersion)); + }; + VirtualBlockEntity.Entry[] array = this.chunk.blockEntityEntries(protocolVersion); + List flowerPots = null; + if (protocolVersion.noGreaterThan(ProtocolVersion.MINECRAFT_1_12_2)) { + flowerPots = this.flowerPots; + if (flowerPots == null) { + this.flowerPots = flowerPots = ChunkDataPacket.getAdditionalFlowerPots(this.chunk); + } } - ProtocolUtils.writeVarInt(buf, 0); // EmptySkylight mask. - ProtocolUtils.writeVarInt(buf, 0); // EmptyBlockLight mask. - ProtocolUtils.writeVarInt(buf, this.chunk.getLight().length); - for (LightSection section : this.chunk.getLight()) { - ProtocolUtils.writeByteArray(buf, section.getSkyLight().getData()); + ProtocolUtils.writeVarInt(buf, flowerPots == null ? array.length : array.length + flowerPots.size()); + for (VirtualBlockEntity.Entry entry : array) { + encoder.accept(entry); } - ProtocolUtils.writeVarInt(buf, this.chunk.getLight().length); - for (LightSection section : this.chunk.getLight()) { - ProtocolUtils.writeByteArray(buf, section.getBlockLight().getData()); + + if (flowerPots != null) { + flowerPots.forEach(encoder); } } + + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_18)) { + ChunkDataPacket.writeLight(buf, protocolVersion, this.chunk.light(), this.skyLightMask, this.availableSections); + } } else { - this.write17(buf, data); + ChunkDataPacket.write17(buf, data); } } finally { data.release(); } } + static void writeLight(ByteBuf buf, ProtocolVersion protocolVersion, LightSection[] light, int skyLightMask, int blockLightMask) { + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_16) && protocolVersion.noGreaterThan(ProtocolVersion.MINECRAFT_1_19_4)) { + buf.writeBoolean(true); // Trust edges + } + + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_17)) { + // Skylight mask + ProtocolUtils.writeVarInt(buf, 1); // length + buf.writeLong(skyLightMask); + + // BlockLight mask + ProtocolUtils.writeVarInt(buf, 1); // length + buf.writeLong(blockLightMask); + } else { + ProtocolUtils.writeVarInt(buf, skyLightMask); + ProtocolUtils.writeVarInt(buf, blockLightMask); + } + + ProtocolUtils.writeVarInt(buf, 0); // EmptySkylight mask + ProtocolUtils.writeVarInt(buf, 0); // EmptyBlockLight mask + + ChunkDataPacket.writeLight(buf, protocolVersion, light, skyLightMask, section -> ProtocolUtils.writeByteArray(buf, section.getSkyLight().getData())); + ChunkDataPacket.writeLight(buf, protocolVersion, light, blockLightMask, section -> ProtocolUtils.writeByteArray(buf, section.getBlockLight().getData())); + } + + private static void writeLight(ByteBuf buf, ProtocolVersion protocolVersion, LightSection[] light, int mask, Consumer encoder) { + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_17)) { + LimboProtocolUtils.writeArray(buf, light, encoder); + } else { + for (int i = 0, length = light.length; i < length; ++i) { + if ((mask & 1 << i) != 0) { + encoder.accept(light[i]); + } + } + } + } + private ByteBuf createChunkData(ProtocolVersion version) { int dataLength = 0; for (NetworkSection networkSection : this.sections) { @@ -240,109 +259,272 @@ private ByteBuf createChunkData(ProtocolVersion version) { dataLength += networkSection.getDataLength(version); } } - if (this.chunk.isFullChunk() && version.compareTo(ProtocolVersion.MINECRAFT_1_15) < 0) { - dataLength += (version.compareTo(ProtocolVersion.MINECRAFT_1_13) < 0 ? 256 : 256 * 4); + + boolean hasBiomeData = this.chunk.fullChunk() && version.noGreaterThan(ProtocolVersion.MINECRAFT_1_14_4); + if (hasBiomeData) { + dataLength += version.noGreaterThan(ProtocolVersion.MINECRAFT_1_12_2) ? 256 : 256 * 4; + } + + if (this.availableSections == 0 && version.noGreaterThan(ProtocolVersion.MINECRAFT_1_8)) { + dataLength += version == ProtocolVersion.MINECRAFT_1_8 + ? (this.hasSkyLight ? (4096 * Short.BYTES) + 2048 + 2048 : (4096 * Short.BYTES) + 2048) + : (this.hasSkyLight ? 4096 + 2048 + 2048 + 2048 : 4096 + 2048 + 2048); } - if (version.compareTo(ProtocolVersion.MINECRAFT_1_18) >= 0) { - int emptySectionSize = version.compareTo(ProtocolVersion.MINECRAFT_1_21_5) >= 0 ? 6 : 8; + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_18)) { + int emptySectionSize = version.lessThan(ProtocolVersion.MINECRAFT_1_21_5) ? (Short.BYTES + Byte.BYTES + 1 + 1 + Byte.BYTES + 1 + 1) : (Short.BYTES + Byte.BYTES + 1 + Byte.BYTES + 1); dataLength += (this.maxSections - this.nonNullSections) * emptySectionSize; } ByteBuf data = Unpooled.buffer(dataLength); - for (int pass = 0; pass < 4; ++pass) { + if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_8)) { + if (this.availableSections == 0) { + // Since we changed the number of available sections to 1, we need to write one additional empty section + data.writeZero(version == ProtocolVersion.MINECRAFT_1_8 + ? this.hasSkyLight ? (4096 * Short.BYTES)/*blocks*/ + 2048/*blocklight*/ + 2048/*skylight*/ : (4096 * Short.BYTES)/*blocks*/ + 2048/*blocklight*/ + : this.hasSkyLight ? 4096/*blocks*/ + 2048/*metadata*/ + 2048/*blocklight*/ + 2048/*skylight*/ : 4096/*blocks*/ + 2048/*metadata*/ + 2048/*blocklight*/); + } else { + for (int pass = 0; pass < 4; ++pass) { + for (NetworkSection section : this.sections) { + if (section != null) { + section.write17Data(data, version, pass); + } + } + } + } + } else { for (NetworkSection section : this.sections) { if (section != null) { - section.writeData(data, pass, version); - } else if (pass == 0 && version.compareTo(ProtocolVersion.MINECRAFT_1_18) >= 0) { - data.writeShort(0); // Block count = 0. - data.writeByte(0); // BlockStorage: 0 bit per entry = Single palette. - ProtocolUtils.writeVarInt(data, Block.AIR.getID()); // Only air block in the palette. - if (version.compareTo(ProtocolVersion.MINECRAFT_1_21_5) < 0) { + section.writeData(data, version); + } else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_18)) { + data.writeShort(0); // Block count = 0 + data.writeByte(0); // BlockStorage: 0 bit per entry = Single palette + ProtocolUtils.writeVarInt(data, Block.AIR.getId()); // Only air block in the palette + if (version.lessThan(ProtocolVersion.MINECRAFT_1_21_5)) { ProtocolUtils.writeVarInt(data, 0); // BlockStorage: 0 entries. } - - data.writeByte(0); // BiomeStorage: 0 bit per entry = Single palette. - ProtocolUtils.writeVarInt(data, Biome.PLAINS.getID()); // Only Plain biome in the palette. - if (version.compareTo(ProtocolVersion.MINECRAFT_1_21_5) < 0) { + data.writeByte(0); // BiomeStorage: 0 bit per entry = Single palette + ProtocolUtils.writeVarInt(data, Biome.PLAINS.getId()); // Only Plain biome in the palette + if (version.lessThan(ProtocolVersion.MINECRAFT_1_21_5)) { ProtocolUtils.writeVarInt(data, 0); // BiomeStorage: 0 entries. } } } } - if (this.chunk.isFullChunk() && version.compareTo(ProtocolVersion.MINECRAFT_1_15) < 0) { - for (byte b : this.biomeData.getPre115Biomes()) { - if (version.compareTo(ProtocolVersion.MINECRAFT_1_13) < 0) { - data.writeByte(b); + + if (hasBiomeData) { + for (int value : this.biomeData.getPre115Biomes()) { + if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_12_2)) { + data.writeByte(value); } else { - data.writeInt(b); + data.writeInt(value); } } } if (dataLength != data.readableBytes()) { - LimboAPI.getLogger().warn("Data length mismatch: " + dataLength + " != " + data.readableBytes() + ". Version: " + version); + LimboAPI.getLogger().warn("Data length mismatch: got: {}, expected: {}, version: {}", dataLength, data.readableBytes(), version); } return data; } - private CompoundBinaryTag createHeightMap(boolean pre116) { - CompactStorage surface = pre116 ? new BitStorage19(9, 256) : new BitStorage116(9, 256); - CompactStorage motionBlocking = pre116 ? new BitStorage19(9, 256) : new BitStorage116(9, 256); - + private CompoundBinaryTag createHeightMap(Int2ObjectArrayMap heightmap1215) { + CompactStorage motionBlocking = heightmap1215 == null ? new BitStorage19(9, 256) : new BitStorage116(9, 256); + CompactStorage surface = heightmap1215 == null ? new BitStorage19(9, 256) : new BitStorage116(9, 256); + // TODO better way (?) (я уже не помню что я имел ввиду) for (int posY = 0; posY < 256; ++posY) { for (int posX = 0; posX < 16; ++posX) { for (int posZ = 0; posZ < 16; ++posZ) { VirtualBlock block = this.chunk.getBlock(posX, posY, posZ); - if (!block.isAir()) { - surface.set(posX + (posZ << 4), posY + 1); - } - if (block.isMotionBlocking()) { + if (block.motionBlocking()) { motionBlocking.set(posX + (posZ << 4), posY + 1); } + + if (!block.air()) { + surface.set(posX + (posZ << 4), posY + 1); + } } } } + if (heightmap1215 != null) { + heightmap1215.put(1, motionBlocking.getData()); + heightmap1215.put(4, motionBlocking.getData()); + } + return CompoundBinaryTag.builder() .putLongArray("MOTION_BLOCKING", motionBlocking.getData()) .putLongArray("WORLD_SURFACE", surface.getData()) .build(); } - private long[] create117Mask() { - return BitSet.valueOf( - new long[] { - this.mask - } - ).toLongArray(); + @Override + public boolean handle(MinecraftSessionHandler handler) { + throw new IllegalStateException(); } - // TODO: Use velocity compressor. - private void write17(ByteBuf out, ByteBuf data) { - out.writeShort(0); // Extended bitmask. - byte[] uncompressed = new byte[data.readableBytes()]; - data.readBytes(uncompressed); - ByteBuf compressed = Unpooled.buffer(); - Deflater deflater = new Deflater(9); - try { - deflater.setInput(uncompressed); - deflater.finish(); - byte[] buffer = new byte[1024]; - while (!deflater.finished()) { - int count = deflater.deflate(buffer); - compressed.writeBytes(buffer, 0, count); + @Override + public int encodeSizeHint(ProtocolUtils.Direction direction, ProtocolVersion version) { + return 128 * 1024; // Should be enough for most cases + } + + @Override + public String toString() { + return "ChunkDataPacket{" + + "chunk=" + this.chunk + + ", sections=" + Arrays.toString(this.sections) + + ", availableSections=" + this.availableSections + + ", maxSections=" + this.maxSections + + ", nonNullSections=" + this.nonNullSections + + ", biomeData=" + this.biomeData + + ", heightmap114=" + this.heightmap114 + + ", heightmap116=" + this.heightmap116 + + "}"; + } + + private static void write17(ByteBuf out, ByteBuf data) { + out.writeShort(0); // Extended bitmask + try (var compressor = Natives.compress.get().create(6)) { + ByteBuf compatibleIn = MoreByteBufUtils.ensureCompatible(data.alloc(), compressor, data); + try { + out.writeInt(0); + int preIndex = out.writerIndex(); + compressor.deflate(compatibleIn, out); + int postIndex = out.writerIndex(); + out.writerIndex(preIndex - Integer.BYTES); + out.writeInt(postIndex - preIndex); + out.writerIndex(postIndex); + } catch (DataFormatException e) { + throw new RuntimeException(e); + } finally { + compatibleIn.release(); } - out.writeInt(compressed.readableBytes()); // Compressed size. - out.writeBytes(compressed); - } finally { - deflater.end(); - compressed.release(); } } - @Override - public boolean handle(MinecraftSessionHandler handler) { - return true; + // In <=1.12.2 flower pots are still block entities, while on higher versions it's just blockstates + public static List getAdditionalFlowerPots(ChunkSnapshot chunk) { + List flowerPots = null; + VirtualBlockEntity flowerPot = null; + BlockSection[] sections = chunk.sections(); + for (int i = 0, length = sections.length; i < length; ++i) { + BlockSection section = sections[i]; + if (section == null) { + continue; + } + + for (int posY = 0; posY < 16; ++posY) { + for (int posX = 0; posX < 16; ++posX) { + for (int posZ = 0; posZ < 16; ++posZ) { + short id = section.getBlockAt(posX, posY, posZ).blockStateId(ProtocolVersion.MINECRAFT_1_13); + if (id >= 5265 && id <= 5286) { + CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder(); + switch (id) { + case 5265 -> { + builder.putString("Item", "minecraft:air"); + builder.putInt("Data", 0); + } + case 5266 -> { + builder.putString("Item", "minecraft:sapling"); + builder.putInt("Data", 0); + } + case 5267 -> { + builder.putString("Item", "minecraft:sapling"); + builder.putInt("Data", 1); + } + case 5268 -> { + builder.putString("Item", "minecraft:sapling"); + builder.putInt("Data", 2); + } + case 5269 -> { + builder.putString("Item", "minecraft:sapling"); + builder.putInt("Data", 3); + } + case 5270 -> { + builder.putString("Item", "minecraft:sapling"); + builder.putInt("Data", 4); + } + case 5271 -> { + builder.putString("Item", "minecraft:sapling"); + builder.putInt("Data", 5); + } + case 5272 -> { + builder.putString("Item", "minecraft:tallgrass"); + builder.putInt("Data", 2); + } + case 5273 -> { + builder.putString("Item", "minecraft:yellow_flower"); + builder.putInt("Data", 0); + } + case 5274 -> { + builder.putString("Item", "minecraft:red_flower"); + builder.putInt("Data", 0); + } + case 5275 -> { + builder.putString("Item", "minecraft:red_flower"); + builder.putInt("Data", 1); + } + case 5276 -> { + builder.putString("Item", "minecraft:red_flower"); + builder.putInt("Data", 2); + } + case 5277 -> { + builder.putString("Item", "minecraft:red_flower"); + builder.putInt("Data", 3); + } + case 5278 -> { + builder.putString("Item", "minecraft:red_flower"); + builder.putInt("Data", 4); + } + case 5279 -> { + builder.putString("Item", "minecraft:red_flower"); + builder.putInt("Data", 5); + } + case 5280 -> { + builder.putString("Item", "minecraft:red_flower"); + builder.putInt("Data", 6); + } + case 5281 -> { + builder.putString("Item", "minecraft:red_flower"); + builder.putInt("Data", 7); + } + case 5282 -> { + builder.putString("Item", "minecraft:red_flower"); + builder.putInt("Data", 8); + } + case 5283 -> { + builder.putString("Item", "minecraft:red_mushroom"); + builder.putInt("Data", 0); + } + case 5284 -> { + builder.putString("Item", "minecraft:brown_mushroom"); + builder.putInt("Data", 0); + } + case 5285 -> { + builder.putString("Item", "minecraft:deadbush"); + builder.putInt("Data", 0); + } + case 5286 -> { + builder.putString("Item", "minecraft:cactus"); + builder.putInt("Data", 0); + } + } + if (flowerPots == null) { + flowerPots = new ArrayList<>(); + flowerPot = SimpleBlockEntity.fromModernId("minecraft:flower_pot"); + if (flowerPot == null) { + throw new NullPointerException(); + } + } + + flowerPots.add(flowerPot.createEntry(null, (chunk.posX() << 4) + posX, posY + (i << 4), (chunk.posZ() << 4) + posZ, builder.build())); + } + } + } + } + } + + return flowerPots; } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/ChunkUnloadPacket.java b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/ChunkUnloadPacket.java index a9af6b25..442bd493 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/ChunkUnloadPacket.java +++ b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/ChunkUnloadPacket.java @@ -25,11 +25,6 @@ public record ChunkUnloadPacket(int posX, int posZ) implements MinecraftPacket { - public ChunkUnloadPacket() { - this(0, 0); - throw new IllegalStateException(); - } - @Override public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { throw new IllegalStateException(); @@ -58,4 +53,15 @@ public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersi public boolean handle(MinecraftSessionHandler handler) { throw new IllegalStateException(); } + + @Override + public int encodeSizeHint(ProtocolUtils.Direction direction, ProtocolVersion version) { + if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_8)) { + return version == ProtocolVersion.MINECRAFT_1_8 + ? Long.BYTES + 1 + Short.BYTES + 1 + : Long.BYTES + 1 + Short.BYTES + Short.BYTES + Integer.BYTES; + } + + return Long.BYTES; + } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/DefaultSpawnPositionPacket.java b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/DefaultSpawnPositionPacket.java index 21c574ea..484b19ec 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/DefaultSpawnPositionPacket.java +++ b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/DefaultSpawnPositionPacket.java @@ -22,24 +22,9 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; +import net.elytrium.limboapi.server.item.codec.data.BlockPosCodec; -public class DefaultSpawnPositionPacket implements MinecraftPacket { - - private final int posX; - private final int posY; - private final int posZ; - private final float angle; - - public DefaultSpawnPositionPacket(int posX, int posY, int posZ, float angle) { - this.posX = posX; - this.posY = posY; - this.posZ = posZ; - this.angle = angle; - } - - public DefaultSpawnPositionPacket() { - throw new IllegalStateException(); - } +public record DefaultSpawnPositionPacket(String dimension, int posX, int posY, int posZ, float yaw, float pitch) implements MinecraftPacket { @Override public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { @@ -48,38 +33,38 @@ public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersi @Override public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { - if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_8) < 0) { - buf.writeInt(this.posX); - buf.writeInt(this.posY); - buf.writeInt(this.posZ); - } else { - long location; - if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_14) < 0) { - location = ((this.posX & 0x3FFFFFFL) << 38) | ((this.posY & 0xFFFL) << 26) | (this.posZ & 0x3FFFFFFL); - } else { - location = ((this.posX & 0x3FFFFFFL) << 38) | ((this.posZ & 0x3FFFFFFL) << 12) | (this.posY & 0xFFFL); - } - - buf.writeLong(location); - - if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_17) >= 0) { - buf.writeFloat(this.angle); - } + boolean v1_21_9 = protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_21_9); + if (v1_21_9) { + ProtocolUtils.writeString(buf, this.dimension); + } + BlockPosCodec.encode(buf, protocolVersion, this.posX, this.posY, this.posZ); + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_17)) { + buf.writeFloat(this.yaw); + } + if (v1_21_9) { + buf.writeFloat(this.pitch); } } @Override public boolean handle(MinecraftSessionHandler handler) { - return true; + throw new IllegalStateException(); } @Override - public String toString() { - return "DefaultSpawnPosition{" - + "posX=" + this.posX - + ", posY=" + this.posY - + ", posZ=" + this.posZ - + ", angle=" + this.angle - + "}"; + public int encodeSizeHint(ProtocolUtils.Direction direction, ProtocolVersion version) { + if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_7_6)) { + return Integer.BYTES * 3; + } + + if (version.lessThan(ProtocolVersion.MINECRAFT_1_17)) { + return Long.BYTES; + } + + if (version.lessThan(ProtocolVersion.MINECRAFT_1_21_9)) { + return Long.BYTES + Float.BYTES; + } + + return ProtocolUtils.stringSizeHint(this.dimension) + (Long.BYTES + Float.BYTES * 2); } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/ChangeGameStatePacket.java b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/GameEventPacket.java similarity index 73% rename from plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/ChangeGameStatePacket.java rename to plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/GameEventPacket.java index e025da35..91610c32 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/ChangeGameStatePacket.java +++ b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/GameEventPacket.java @@ -23,20 +23,8 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; -public class ChangeGameStatePacket implements MinecraftPacket { - - private final int reason; - private final float value; - - // TODO: Reasons enum or builder. - public ChangeGameStatePacket(int reason, float value) { - this.reason = reason; - this.value = value; - } - - public ChangeGameStatePacket() { - throw new IllegalStateException(); - } +// TODO: Reasons enum or builder +public record GameEventPacket(int event, float param) implements MinecraftPacket { @Override public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { @@ -45,20 +33,17 @@ public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersi @Override public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { - buf.writeByte(this.reason); - buf.writeFloat(this.value); + buf.writeByte(this.event); + buf.writeFloat(this.param); } @Override public boolean handle(MinecraftSessionHandler handler) { - return true; + throw new IllegalStateException(); } @Override - public String toString() { - return "ChangeGameState{" - + "reason=" + this.reason - + ", value=" + this.value - + "}"; + public int encodeSizeHint(ProtocolUtils.Direction direction, ProtocolVersion version) { + return Byte.BYTES + Float.BYTES; } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/LightUpdatePacket.java b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/LightUpdatePacket.java new file mode 100644 index 00000000..c3bfc90d --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/LightUpdatePacket.java @@ -0,0 +1,55 @@ +package net.elytrium.limboapi.protocol.packets.s2c; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import net.elytrium.limboapi.api.world.chunk.ChunkSnapshot; +import net.elytrium.limboapi.api.world.chunk.data.LightSection; + +public record LightUpdatePacket(int posX, int posZ, LightSection[] light, int[] masks) implements MinecraftPacket { + + public LightUpdatePacket(ChunkSnapshot chunk, boolean hasSkyLight) { + this(chunk.posX(), chunk.posZ(), chunk.light(), LightUpdatePacket.calcMasks(chunk, hasSkyLight)); + } + + // TODO get rid of this method when JEP 447 + private static int[] calcMasks(ChunkSnapshot chunk, boolean hasSkyLight) { + boolean fullChunk = chunk.fullChunk(); + int skyLightMask = fullChunk ? 0x3FFFF : 0; + int blockLightMask = 0; + var sections = chunk.sections(); + for (int i = 0; i < sections.length; ++i) { + if (sections[i] != null) { + if (!fullChunk && hasSkyLight) { + skyLightMask |= (1 << i); + } + + blockLightMask |= (1 << i); + } + } + + return new int[] { + skyLightMask, + blockLightMask + }; + } + + @Override + public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + throw new IllegalStateException(); + } + + @Override + public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + ProtocolUtils.writeVarInt(buf, this.posX); + ProtocolUtils.writeVarInt(buf, this.posZ); + ChunkDataPacket.writeLight(buf, protocolVersion, this.light, this.masks[0], this.masks[1]); + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + throw new IllegalStateException(); + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/MapDataPacket.java b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/MapDataPacket.java index da9935a6..93279093 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/MapDataPacket.java +++ b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/MapDataPacket.java @@ -22,23 +22,9 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; -import net.elytrium.limboapi.api.protocol.packets.data.MapData; +import net.elytrium.limboapi.api.protocol.data.MapData; -public class MapDataPacket implements MinecraftPacket { - - private final int mapID; - private final byte scale; - private final MapData mapData; - - public MapDataPacket(int mapID, byte scale, MapData mapData) { - this.mapID = mapID; - this.scale = scale; - this.mapData = mapData; - } - - public MapDataPacket() { - throw new IllegalStateException(); - } +public record MapDataPacket(int mapId, byte scale, MapData mapData) implements MinecraftPacket { @Override public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { @@ -46,37 +32,37 @@ public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersi } @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - ProtocolUtils.writeVarInt(buf, this.mapID); - byte[] data = this.mapData.getData(); - if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) < 0) { - buf.writeShort(data.length + 3); - buf.writeByte(0); - buf.writeByte(this.mapData.getX()); - buf.writeByte(this.mapData.getY()); - + public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + ProtocolUtils.writeVarInt(buf, this.mapId); + byte[] data = this.mapData.data(); + if (protocolVersion.noGreaterThan(ProtocolVersion.MINECRAFT_1_7_6)) { + buf.writeShort((1 + 1 + 1) + data.length); + buf.writeByte(0); // type (?) + buf.writeByte(this.mapData.posX()); + buf.writeByte(this.mapData.posY()); buf.writeBytes(data); } else { buf.writeByte(this.scale); - if (version.compareTo(ProtocolVersion.MINECRAFT_1_9) >= 0 && version.compareTo(ProtocolVersion.MINECRAFT_1_17) < 0) { - buf.writeBoolean(false); + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_9) && protocolVersion.noGreaterThan(ProtocolVersion.MINECRAFT_1_16_4)) { + buf.writeBoolean(false); // trackingPosition } - if (version.compareTo(ProtocolVersion.MINECRAFT_1_14) >= 0) { - buf.writeBoolean(false); + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_14)) { + buf.writeBoolean(false); // locked } - if (version.compareTo(ProtocolVersion.MINECRAFT_1_17) >= 0) { + // decorations (their lack, that is) + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_17)) { buf.writeBoolean(false); } else { ProtocolUtils.writeVarInt(buf, 0); } - buf.writeByte(this.mapData.getColumns()); - buf.writeByte(this.mapData.getRows()); - buf.writeByte(this.mapData.getX()); - buf.writeByte(this.mapData.getY()); + buf.writeByte(this.mapData.columns()); + buf.writeByte(this.mapData.rows()); + buf.writeByte(this.mapData.posX()); + buf.writeByte(this.mapData.posY()); ProtocolUtils.writeByteArray(buf, data); } @@ -84,15 +70,13 @@ public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersi @Override public boolean handle(MinecraftSessionHandler handler) { - return true; + throw new IllegalStateException(); } @Override - public String toString() { - return "MapDataPacket{" - + "mapID=" + this.mapID - + ", scale=" + this.scale - + ", mapData=" + this.mapData - + "}"; + public int encodeSizeHint(ProtocolUtils.Direction direction, ProtocolVersion version) { + return version.noGreaterThan(ProtocolVersion.MINECRAFT_1_7_6) + ? 5 + 2 + Byte.BYTES * 3 + MapData.MAP_DIM_SIZE + : 5 + Byte.BYTES * 8 + 5 + MapData.MAP_SIZE; } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/PlayerAbilitiesPacket.java b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/PlayerAbilitiesPacket.java index 5b842ac5..14d493fc 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/PlayerAbilitiesPacket.java +++ b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/PlayerAbilitiesPacket.java @@ -23,21 +23,7 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; -public class PlayerAbilitiesPacket implements MinecraftPacket { - - private final byte flags; - private final float walkSpeed; - private final float flySpeed; - - public PlayerAbilitiesPacket(byte flags, float flySpeed, float walkSpeed) { - this.flags = flags; - this.flySpeed = flySpeed; - this.walkSpeed = walkSpeed; - } - - public PlayerAbilitiesPacket() { - throw new IllegalStateException(); - } +public record PlayerAbilitiesPacket(byte abilities, float flyingSpeed, float walkingSpeed) implements MinecraftPacket { @Override public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { @@ -46,22 +32,18 @@ public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersi @Override public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { - buf.writeByte(this.flags); - buf.writeFloat(this.flySpeed); - buf.writeFloat(this.walkSpeed); + buf.writeByte(this.abilities); + buf.writeFloat(this.flyingSpeed); + buf.writeFloat(this.walkingSpeed); } @Override public boolean handle(MinecraftSessionHandler handler) { - return true; + throw new IllegalStateException(); } @Override - public String toString() { - return "PlayerAbilities{" - + "flags=" + this.flags - + ", flySpeed=" + this.flySpeed - + ", walkSpeed=" + this.walkSpeed - + "}"; + public int encodeSizeHint(ProtocolUtils.Direction direction, ProtocolVersion version) { + return Byte.BYTES + Float.BYTES * 2; } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/PlayerPositionPacket.java b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/PlayerPositionPacket.java new file mode 100644 index 00000000..22b59129 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/PlayerPositionPacket.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2021 - 2024 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limboapi.protocol.packets.s2c; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; + +public record PlayerPositionPacket(double posX, double posY, double posZ, float yaw, float pitch, boolean onGround, int teleportId, boolean dismountVehicle) implements MinecraftPacket { + + @Override + public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + throw new IllegalStateException(); + } + + @Override + public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + final boolean v1_21_2 = protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_21_2); + final boolean v1_7_x = !v1_21_2 && protocolVersion.noGreaterThan(ProtocolVersion.MINECRAFT_1_7_6); + + if (v1_21_2) { + ProtocolUtils.writeVarInt(buf, this.teleportId); + } + + buf.writeDouble(this.posX); + buf.writeDouble(v1_7_x ? this.posY + 1.62F/*in 1.7.x posY means eyes position*/ : this.posY); + buf.writeDouble(this.posZ); + if (v1_21_2) { + // deltaMovement + buf.writeDouble(0); + buf.writeDouble(0); + buf.writeDouble(0); + } + buf.writeFloat(this.yaw); + buf.writeFloat(this.pitch); + + if (v1_21_2) { + buf.writeInt(0x00); // relatives + } else if (v1_7_x) { + buf.writeBoolean(this.onGround); + } else { + buf.writeByte(0x00); // relativeArguments + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_9)) { + ProtocolUtils.writeVarInt(buf, this.teleportId); + } + + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_17) && protocolVersion.noGreaterThan(ProtocolVersion.MINECRAFT_1_19_3)) { + buf.writeBoolean(this.dismountVehicle); + } + } + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + throw new IllegalStateException(); + } + + @Override + public int encodeSizeHint(ProtocolUtils.Direction direction, ProtocolVersion version) { + return 5 + Double.BYTES * 3 + Double.BYTES * 3 + Float.BYTES * 2 + Integer.BYTES; // The worst case scenario + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/PositionRotationPacket.java b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/PositionRotationPacket.java deleted file mode 100644 index a91a5f03..00000000 --- a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/PositionRotationPacket.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package net.elytrium.limboapi.protocol.packets.s2c; - -import com.velocitypowered.api.network.ProtocolVersion; -import com.velocitypowered.proxy.connection.MinecraftSessionHandler; -import com.velocitypowered.proxy.protocol.MinecraftPacket; -import com.velocitypowered.proxy.protocol.ProtocolUtils; -import io.netty.buffer.ByteBuf; - -public class PositionRotationPacket implements MinecraftPacket { - - private final double posX; - private final double posY; - private final double posZ; - private final float yaw; - private final float pitch; - private final boolean onGround; - private final int teleportID; - private final boolean dismountVehicle; - - @Deprecated(forRemoval = true) - public PositionRotationPacket(double posX, double posY, double posZ, float yaw, float pitch, int teleportID, boolean onGround, boolean dismountVehicle) { - this(posX, posY, posZ, yaw, pitch, onGround, teleportID, dismountVehicle); - } - - public PositionRotationPacket(double posX, double posY, double posZ, float yaw, float pitch, boolean onGround, int teleportID, boolean dismountVehicle) { - this.posX = posX; - this.posY = posY; - this.posZ = posZ; - this.yaw = yaw; - this.pitch = pitch; - this.onGround = onGround; - this.teleportID = teleportID; - this.dismountVehicle = dismountVehicle; - } - - public PositionRotationPacket() { - throw new IllegalStateException(); - } - - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { - throw new IllegalStateException(); - } - - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { - if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) { - this.encodeModern(buf, protocolVersion); - } else { - this.encodeLegacy(buf, protocolVersion); - } - } - - public void encodeModern(ByteBuf buf, ProtocolVersion protocolVersion) { - ProtocolUtils.writeVarInt(buf, this.teleportID); - - buf.writeDouble(this.posX); - buf.writeDouble(this.posY); - buf.writeDouble(this.posZ); - - // velocity - buf.writeDouble(0); - buf.writeDouble(0); - buf.writeDouble(0); - - buf.writeFloat(this.yaw); - buf.writeFloat(this.pitch); - - buf.writeInt(0); // not relative - } - - public void encodeLegacy(ByteBuf buf, ProtocolVersion protocolVersion) { - buf.writeDouble(this.posX); - buf.writeDouble(this.posY); - buf.writeDouble(this.posZ); - buf.writeFloat(this.yaw); - buf.writeFloat(this.pitch); - - if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_8) < 0) { - buf.writeBoolean(this.onGround); - } else { - buf.writeByte(0x00); // Flags. - - if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_9) >= 0) { - ProtocolUtils.writeVarInt(buf, this.teleportID); - } - - if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_17) >= 0 && protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_19_3) <= 0) { - buf.writeBoolean(this.dismountVehicle); - } - } - } - - @Override - public boolean handle(MinecraftSessionHandler handler) { - return true; - } - - @Override - public String toString() { - return "PlayerPositionAndLook{" - + "posX=" + this.posX - + ", posY=" + this.posY - + ", posZ=" + this.posZ - + ", yaw=" + this.yaw - + ", pitch=" + this.pitch - + ", teleportID=" + this.teleportID - + ", onGround=" + this.onGround - + ", dismountVehicle=" + this.dismountVehicle - + "}"; - } -} diff --git a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/UpdateViewPositionPacket.java b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/SetChunkCacheCenterPacket.java similarity index 78% rename from plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/UpdateViewPositionPacket.java rename to plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/SetChunkCacheCenterPacket.java index fa971ff6..fa0e86f1 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/UpdateViewPositionPacket.java +++ b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/SetChunkCacheCenterPacket.java @@ -23,19 +23,7 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; -public class UpdateViewPositionPacket implements MinecraftPacket { - - private final int posX; - private final int posZ; - - public UpdateViewPositionPacket(int posX, int posZ) { - this.posX = posX; - this.posZ = posZ; - } - - public UpdateViewPositionPacket() { - throw new IllegalStateException(); - } +public record SetChunkCacheCenterPacket(int posX, int posZ) implements MinecraftPacket { @Override public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { @@ -50,14 +38,11 @@ public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersi @Override public boolean handle(MinecraftSessionHandler handler) { - return true; + throw new IllegalStateException(); } @Override - public String toString() { - return "UpdateViewPosition{" - + "posX=" + this.posX - + ", posZ=" + this.posZ - + "}"; + public int encodeSizeHint(ProtocolUtils.Direction direction, ProtocolVersion version) { + return 5 + 5; } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/SetEntityDataPacket.java b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/SetEntityDataPacket.java new file mode 100644 index 00000000..22f48fa5 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/SetEntityDataPacket.java @@ -0,0 +1,670 @@ +/* + * Copyright (C) 2021 - 2024 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limboapi.protocol.packets.s2c; + +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.handler.codec.DecoderException; +import io.netty.util.ReferenceCounted; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntObjectPair; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Base64; +import java.util.EnumMap; +import java.util.Map; +import java.util.OptionalInt; +import java.util.UUID; +import java.util.function.Function; +import net.elytrium.limboapi.LimboAPI; +import net.elytrium.limboapi.api.protocol.data.BlockPos; +import net.elytrium.limboapi.api.protocol.data.EntityData; +import net.elytrium.limboapi.api.protocol.data.GlobalPos; +import net.elytrium.limboapi.api.protocol.data.ItemStack; +import net.elytrium.limboapi.api.protocol.data.ComponentHolder; +import net.elytrium.limboapi.api.world.item.datacomponent.type.ResolvableProfile; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.Holder; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import net.elytrium.limboapi.protocol.codec.StreamDecoder; +import net.elytrium.limboapi.server.item.codec.PaintingVariantCodec; +import net.elytrium.limboapi.server.item.codec.ResolvableProfileCodec; +import net.elytrium.limboapi.server.item.codec.data.BlockPosCodec; +import net.elytrium.limboapi.server.item.codec.data.BlockStateCodec; +import net.elytrium.limboapi.server.item.codec.data.ComponentHolderCodec; +import net.elytrium.limboapi.server.item.codec.data.GlobalPosCodec; +import net.elytrium.limboapi.server.item.codec.data.HolderSetCodec; +import net.elytrium.limboapi.server.item.codec.data.ItemStackCodec; +import net.elytrium.limboapi.server.item.codec.data.OptionalBlockStateCodec; +import net.elytrium.limboapi.server.item.codec.data.ParticleCodec; +import net.elytrium.limboapi.server.item.codec.data.QuaternionCodec; +import net.elytrium.limboapi.server.item.codec.data.RotationsCodec; +import net.elytrium.limboapi.server.item.codec.data.Vector3Codec; +import net.elytrium.limboapi.server.item.codec.data.VillagerDataCodec; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.nbt.EndBinaryTag; + +/** + * See OptionalUUID codec + */ +public class SetEntityDataPacket implements MinecraftPacket, ReferenceCounted { // TODO check for uuid in nbts + + public static final int EOF_MARKER_LEGACY = Byte.MAX_VALUE; + public static final int EOF_MARKER = -Byte.MIN_VALUE + Byte.MAX_VALUE; + + private int id; + private EntityData data; + + private ByteBuf fallbackBuf; // TODO remove after proper tests + + public SetEntityDataPacket(int id, EntityData data) { + this.id = id; + this.data = data; + } + + public SetEntityDataPacket() { + + } + + @Override + public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + int readerIndex = buf.readerIndex(); + try { + var registry = RegistryBuilder.CODECS.get(protocolVersion).getKey(); + boolean v1_7_x = protocolVersion.noGreaterThan(ProtocolVersion.MINECRAFT_1_7_6); + if (v1_7_x || protocolVersion == ProtocolVersion.MINECRAFT_1_8) { + this.id = v1_7_x ? buf.readInt() : ProtocolUtils.readVarInt(buf); + byte packedId; + while ((packedId = buf.readByte()) != SetEntityDataPacket.EOF_MARKER_LEGACY) { + if (this.data == null) { + this.data = new EntityData(); + } + + short id = (short) (packedId & 0b00011111); + int type = (packedId & 0b11100000) >> 5; + var decoder = registry.get(type); + if (decoder == null) { + throw new IllegalArgumentException("Don't know how to decode type " + type + " (id: " + id + ", entityId: " + this.id + ")"); + } + this.data.put(id, decoder.decode(buf, protocolVersion)); + } + } else { + this.id = ProtocolUtils.readVarInt(buf); + short id; + while ((id = buf.readUnsignedByte()) != SetEntityDataPacket.EOF_MARKER) { + if (this.data == null) { + this.data = new EntityData(); + } + + int type = ProtocolUtils.readVarInt(buf); + var decoder = registry.get(type); + if (decoder == null) { + throw new IllegalArgumentException("Don't know how to decode type " + type + " (id: " + id + ", entityId: " + this.id + ")"); + } + this.data.put(id, decoder.decode(buf, protocolVersion)); + } + } + } catch (Throwable t) { + this.fallbackBuf = buf.retainedDuplicate().readerIndex(readerIndex); + LimboAPI.getLogger().error("Failed to read SetEntityDataPacket (direction={}, version={}, data=\"{}\")", direction, protocolVersion, Base64.getEncoder().encodeToString(ByteBufUtil.getBytes(this.fallbackBuf)), t); + buf.readerIndex(buf.writerIndex()); + } + } + + @Override + @SuppressWarnings("unchecked") + public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + if (this.fallbackBuf != null) { + buf.writeBytes(this.fallbackBuf); + return; + } + + var registry = RegistryBuilder.CODECS.get(protocolVersion).getValue(); + boolean v1_7_x = protocolVersion.noGreaterThan(ProtocolVersion.MINECRAFT_1_7_6); + if (v1_7_x || protocolVersion == ProtocolVersion.MINECRAFT_1_8) { + if (v1_7_x) { + buf.writeInt(this.id); + } else { + ProtocolUtils.writeVarInt(buf, this.id); + } + if (this.data != null) { + this.data.short2ObjectEntrySet().fastForEach(entry -> { + Object value = entry.getValue(); + var pair = registry.get(value.getClass()); + if (pair == null) { + throw new IllegalArgumentException("Don't know how to encode " + value + " (" + value.getClass() + ") for " + protocolVersion); + } + buf.writeByte((pair.leftInt() << 5 | entry.getShortKey() & 0b00011111) & 0xFF); + ((StreamCodec) pair.right()).encode(buf, protocolVersion, value); + }); + } + + buf.writeByte(SetEntityDataPacket.EOF_MARKER_LEGACY); + } else { + ProtocolUtils.writeVarInt(buf, this.id); + if (this.data != null) { + this.data.short2ObjectEntrySet().fastForEach(entry -> { + buf.writeByte(entry.getShortKey()); + Object value = entry.getValue(); + var pair = registry.get(value.getClass()); + if (pair == null) { + throw new IllegalArgumentException("Don't know how to encode " + value + " (" + value.getClass() + ") for " + protocolVersion); + } + ProtocolUtils.writeVarInt(buf, pair.leftInt()); + ((StreamCodec) pair.right()).encode(buf, protocolVersion, value); + }); + } + + buf.writeByte(SetEntityDataPacket.EOF_MARKER); + } + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + this.retain(); + return false; // forward it to the player + } + + @Override + public int refCnt() { + return this.fallbackBuf == null ? -1 : this.fallbackBuf.refCnt(); + } + + @Override + public ReferenceCounted retain() { + if (this.fallbackBuf != null) { + this.fallbackBuf.retain(); + } + + return this; + } + + @Override + public ReferenceCounted retain(int increment) { + if (this.fallbackBuf != null) { + this.fallbackBuf.retain(increment); + } + + return this; + } + + @Override + public ReferenceCounted touch() { + if (this.fallbackBuf != null) { + this.fallbackBuf.touch(); + } + + return this; + } + + @Override + public ReferenceCounted touch(Object hint) { + if (this.fallbackBuf != null) { + this.fallbackBuf.touch(hint); + } + + return this; + } + + @Override + public boolean release() { + return this.fallbackBuf != null && this.fallbackBuf.release(); + } + + @Override + public boolean release(int decrement) { + return this.fallbackBuf != null && this.fallbackBuf.release(decrement); + } + + static { + final Function long2Int = Long::intValue; + final Function optionalInt2Int = OptionalInt::orElseThrow; + try (var builder = new RegistryBuilder<>(ProtocolVersion.MINECRAFT_1_7_2)) { + builder.register(Byte.class, ByteBufCodecs.BYTE).map(Boolean.class, value -> (byte) (value ? 1 : 0)); + builder.register(Short.class, ByteBufCodecs.SHORT); + builder.register(Integer.class, ByteBufCodecs.INT).map(Long.class, long2Int).map(OptionalInt.class, optionalInt2Int); + builder.register(Float.class, ByteBufCodecs.FLOAT); + builder.register(String.class, ByteBufCodecs.STRING_UTF8) + .map(EntityData.OptionalUUID.class, optional -> LimboAPI.getClientUniqueId(optional.orElseThrow()).toString()) + .map(ComponentHolder.class, component -> component.getJson(ProtocolVersion.MINECRAFT_1_7_2)) + .map(EntityData.OptionalComponentHolder.class, optional -> optional.orElseThrow().getJson(ProtocolVersion.MINECRAFT_1_7_2)); + builder.register(ItemStack.class, ItemStackCodec.OPTIONAL_CODEC); + builder.register(BlockPos.class, BlockPosCodec.CODEC).map(EntityData.OptionalBlockPos.class, EntityData.OptionalBlockPos::orElseThrow); + } + try (var builder = new RegistryBuilder<>(ProtocolVersion.MINECRAFT_1_8).inheritAllFrom(ProtocolVersion.MINECRAFT_1_7_2)) { + builder.register(EntityData.Rotations.class, RotationsCodec.CODEC); + } + try (var builder = new RegistryBuilder<>(ProtocolVersion.MINECRAFT_1_9)) { + builder.inherit(Byte.class); + builder.register(Integer.class, ByteBufCodecs.VAR_INT).map(Short.class, Short::intValue).map(Long.class, long2Int).map(OptionalInt.class, optionalInt2Int); + builder.inherit(Float.class); + builder.inherit(String.class); + builder.register(ComponentHolder.class, ComponentHolderCodec.CODEC).map(EntityData.OptionalComponentHolder.class, EntityData.OptionalComponentHolder::orElseThrow); + builder.inherit(ItemStack.class); + builder.register(Boolean.class, ByteBufCodecs.BOOL); + builder.inherit(EntityData.Rotations.class); + builder.inherit(BlockPos.class); + builder.register(EntityData.OptionalBlockPos.class, BlockPosCodec.OPTIONAL_CODEC.map(EntityData.OptionalBlockPos::of, EntityData.OptionalBlockPos::blockPos)); + builder.register(EntityData.Direction.class, ByteBufCodecs.VAR_INT.map(id -> EntityData.Direction.VALUES[id], EntityData.Direction::ordinal)); + builder.register(EntityData.OptionalUUID.class, ByteBufCodecs.OPTIONAL_UUID.map(EntityData.OptionalUUID::of, optional -> { + UUID uuid = optional.uuid(); + return uuid == null ? null : LimboAPI.getClientUniqueId(uuid); + })); + builder.register(EntityData.OptionalBlockState.class, OptionalBlockStateCodec.CODEC).map(EntityData.BlockState.class, BlockStateCodec.CODEC); + } + Class CompoundBinaryTagImpl = CompoundBinaryTag.empty().getClass(); + Class EndBinaryTagImpl = EndBinaryTag.endBinaryTag().getClass(); + try (var builder = new RegistryBuilder<>(ProtocolVersion.MINECRAFT_1_12).inheritAllFrom(ProtocolVersion.MINECRAFT_1_9)) { + builder.register(CompoundBinaryTag.class, ByteBufCodecs.OPTIONAL_COMPOUND_TAG.map((tag, version) -> { + if (tag == null) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) { + throw new DecoderException("Expected non-null compound tag"); + } + + return EndBinaryTag.endBinaryTag(); + } + + return tag; + }, (tag, version) -> (CompoundBinaryTag) tag)).alias(CompoundBinaryTagImpl).map(EndBinaryTag.class, ByteBufCodecs.OPTIONAL_TAG.map((tag, version) -> tag, (tag, version) -> { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) { + throw new DecoderException("Expected non-null compound tag"); + } + + return tag; + })).alias(EndBinaryTagImpl); + } + try (var builder = new RegistryBuilder<>(ProtocolVersion.MINECRAFT_1_13)) { + builder.inherit(Byte.class); + builder.inherit(Integer.class).inheritMap(Short.class).inheritMap(Long.class).inheritMap(OptionalInt.class); + builder.inherit(Float.class); + builder.inherit(String.class); + builder.inherit(ComponentHolder.class); + builder.register(EntityData.OptionalComponentHolder.class, + ComponentHolderCodec.OPTIONAL_CODEC.map(EntityData.OptionalComponentHolder::of, EntityData.OptionalComponentHolder::component) + ); + builder.inherit(ItemStack.class); + builder.inherit(Boolean.class); + builder.inherit(EntityData.Rotations.class); + builder.inherit(BlockPos.class); + builder.inherit(EntityData.OptionalBlockPos.class); + builder.inherit(EntityData.Direction.class); + builder.inherit(EntityData.OptionalUUID.class); + builder.inherit(EntityData.OptionalBlockState.class).inheritMap(EntityData.BlockState.class); + builder.inherit(CompoundBinaryTag.class).alias(CompoundBinaryTagImpl).inheritMap(EndBinaryTag.class).alias(EndBinaryTagImpl); + builder.register(EntityData.Particle.class, ParticleCodec.CODEC); + } + try (var builder = new RegistryBuilder<>(ProtocolVersion.MINECRAFT_1_14)) { + builder.inherit(Byte.class); + builder.inherit(Integer.class).inheritMap(Short.class).inheritMap(Long.class); + builder.inherit(Float.class); + builder.inherit(String.class); + builder.inherit(ComponentHolder.class); + builder.inherit(EntityData.OptionalComponentHolder.class); + builder.inherit(ItemStack.class); + builder.inherit(Boolean.class); + builder.inherit(EntityData.Rotations.class); + builder.inherit(BlockPos.class); + builder.inherit(EntityData.OptionalBlockPos.class); + builder.inherit(EntityData.Direction.class); + builder.inherit(EntityData.OptionalUUID.class); + builder.inherit(EntityData.OptionalBlockState.class).inheritMap(EntityData.BlockState.class); + builder.inherit(CompoundBinaryTag.class).alias(CompoundBinaryTagImpl).inheritMap(EndBinaryTag.class).alias(EndBinaryTagImpl); + builder.inherit(EntityData.Particle.class); + builder.register(EntityData.VillagerData.class, VillagerDataCodec.CODEC); + builder.register(OptionalInt.class, ByteBufCodecs.VAR_INT.map(result -> result == 0 ? OptionalInt.empty() : OptionalInt.of(result - 1), value -> value.orElse(-1) + 1)); + builder.register(EntityData.Pose.class, ByteBufCodecs.VAR_INT.map(EntityData.Pose::fromProtocolId, EntityData.Pose::getProtocolId)); + } + try (var builder = new RegistryBuilder<>(ProtocolVersion.MINECRAFT_1_19).inheritAllFrom(ProtocolVersion.MINECRAFT_1_14)) { + builder.register(EntityData.CatVariant.class, ByteBufCodecs.VAR_INT.map(EntityData.CatVariant::new, EntityData.CatVariant::id)); + builder.register(EntityData.FrogVariant.class, ByteBufCodecs.VAR_INT.map(EntityData.FrogVariant::new, EntityData.FrogVariant::id)); + builder.register(EntityData.OptionalGlobalPos.class, GlobalPosCodec.OPTIONAL_CODEC.map(EntityData.OptionalGlobalPos::new, EntityData.OptionalGlobalPos::globalPos)) + .map(GlobalPos.class, GlobalPosCodec.OPTIONAL_CODEC); + builder.register(EntityData.PaintingVariantHolder.class, + ByteBufCodecs.VAR_INT.map(id -> new EntityData.PaintingVariantHolder(Holder.ref(id)), holder -> holder.painting() instanceof Holder.Reference reference ? reference.id() : 0) + ); + } + try (var builder = new RegistryBuilder<>(ProtocolVersion.MINECRAFT_1_19_3)) { + builder.inherit(Byte.class); + builder.inherit(Integer.class).inheritMap(Short.class); + builder.register(Long.class, ByteBufCodecs.VAR_LONG); + builder.inherit(Float.class); + builder.inherit(String.class); + builder.inherit(ComponentHolder.class); + builder.inherit(EntityData.OptionalComponentHolder.class); + builder.inherit(ItemStack.class); + builder.inherit(Boolean.class); + builder.inherit(EntityData.Rotations.class); + builder.inherit(BlockPos.class); + builder.inherit(EntityData.OptionalBlockPos.class); + builder.inherit(EntityData.Direction.class); + builder.inherit(EntityData.OptionalUUID.class); + builder.inherit(EntityData.OptionalBlockState.class).inheritMap(EntityData.BlockState.class); + builder.inherit(CompoundBinaryTag.class).alias(CompoundBinaryTagImpl).inheritMap(EndBinaryTag.class).alias(EndBinaryTagImpl); + builder.inherit(EntityData.Particle.class); + builder.inherit(EntityData.VillagerData.class); + builder.inherit(OptionalInt.class); + builder.inherit(EntityData.Pose.class); + builder.inherit(EntityData.CatVariant.class); + builder.inherit(EntityData.FrogVariant.class); + builder.inherit(EntityData.OptionalGlobalPos.class).inheritMap(GlobalPos.class); + builder.inherit(EntityData.PaintingVariantHolder.class); + } + try (var builder = new RegistryBuilder<>(ProtocolVersion.MINECRAFT_1_19_4)) { + builder.inherit(Byte.class); + builder.inherit(Integer.class).inheritMap(Short.class); + builder.inherit(Long.class); + builder.inherit(Float.class); + builder.inherit(String.class); + builder.inherit(ComponentHolder.class); + builder.inherit(EntityData.OptionalComponentHolder.class); + builder.inherit(ItemStack.class); + builder.inherit(Boolean.class); + builder.inherit(EntityData.Rotations.class); + builder.inherit(BlockPos.class); + builder.inherit(EntityData.OptionalBlockPos.class); + builder.inherit(EntityData.Direction.class); + builder.inherit(EntityData.OptionalUUID.class); + builder.inherit(EntityData.BlockState.class); // <-- The reason why inheritAllFrom not used + builder.inherit(EntityData.OptionalBlockState.class); + builder.inherit(CompoundBinaryTag.class).alias(CompoundBinaryTagImpl).inheritMap(EndBinaryTag.class).alias(EndBinaryTagImpl); + builder.inherit(EntityData.Particle.class); + builder.inherit(EntityData.VillagerData.class); + builder.inherit(OptionalInt.class); + builder.inherit(EntityData.Pose.class); + builder.inherit(EntityData.CatVariant.class); + builder.inherit(EntityData.FrogVariant.class); + builder.inherit(EntityData.OptionalGlobalPos.class).inheritMap(GlobalPos.class); + builder.inherit(EntityData.PaintingVariantHolder.class); + builder.register(EntityData.SnifferState.class, ByteBufCodecs.VAR_INT.map(id -> EntityData.SnifferState.VALUES[id], EntityData.SnifferState::ordinal)); + builder.register(EntityData.Vector3.class, Vector3Codec.CODEC); + builder.register(EntityData.Quaternion.class, QuaternionCodec.CODEC); + } + StreamCodec wolfVariantCodec = ByteBufCodecs.VAR_INT.map(EntityData.WolfVariant.Reference::new, + variant -> variant instanceof EntityData.WolfVariant.Reference reference ? reference.id() : 0 + ); + try (var builder = new RegistryBuilder<>(ProtocolVersion.MINECRAFT_1_20_5)) { + builder.inherit(Byte.class); + builder.inherit(Integer.class).inheritMap(Short.class); + builder.inherit(Long.class); + builder.inherit(Float.class); + builder.inherit(String.class); + builder.inherit(ComponentHolder.class); + builder.inherit(EntityData.OptionalComponentHolder.class); + builder.inherit(ItemStack.class); + builder.inherit(Boolean.class); + builder.inherit(EntityData.Rotations.class); + builder.inherit(BlockPos.class); + builder.inherit(EntityData.OptionalBlockPos.class); + builder.inherit(EntityData.Direction.class); + builder.inherit(EntityData.OptionalUUID.class); + builder.inherit(EntityData.BlockState.class); + builder.inherit(EntityData.OptionalBlockState.class); + builder.inherit(CompoundBinaryTag.class).alias(CompoundBinaryTagImpl).inheritMap(EndBinaryTag.class).alias(EndBinaryTagImpl); + builder.inherit(EntityData.Particle.class); + builder.register(EntityData.Particles.class, ParticleCodec.COLLECTION_CODEC); + builder.inherit(EntityData.VillagerData.class); + builder.inherit(OptionalInt.class); + builder.inherit(EntityData.Pose.class); + builder.inherit(EntityData.CatVariant.class); + builder.register(EntityData.WolfVariant.class, wolfVariantCodec).map(EntityData.WolfVariant.Reference.class, wolfVariantCodec).map(EntityData.WolfVariant.Direct.class, wolfVariantCodec); + builder.inherit(EntityData.FrogVariant.class); + builder.inherit(EntityData.OptionalGlobalPos.class).inheritMap(GlobalPos.class); + builder.inherit(EntityData.PaintingVariantHolder.class); + builder.inherit(EntityData.SnifferState.class); + builder.register(EntityData.ArmadilloState.class, ByteBufCodecs.VAR_INT.map(id -> EntityData.ArmadilloState.VALUES[id], EntityData.ArmadilloState::ordinal)); + builder.inherit(EntityData.Vector3.class); + builder.inherit(EntityData.Quaternion.class); + } + try (var builder = new RegistryBuilder<>(ProtocolVersion.MINECRAFT_1_21)) { + builder.inherit(Byte.class); + builder.inherit(Integer.class).inheritMap(Short.class); + builder.inherit(Long.class); + builder.inherit(Float.class); + builder.inherit(String.class); + builder.inherit(ComponentHolder.class); + builder.inherit(EntityData.OptionalComponentHolder.class); + builder.inherit(ItemStack.class); + builder.inherit(Boolean.class); + builder.inherit(EntityData.Rotations.class); + builder.inherit(BlockPos.class); + builder.inherit(EntityData.OptionalBlockPos.class); + builder.inherit(EntityData.Direction.class); + builder.inherit(EntityData.OptionalUUID.class); + builder.inherit(EntityData.BlockState.class); + builder.inherit(EntityData.OptionalBlockState.class); + builder.inherit(CompoundBinaryTag.class).alias(CompoundBinaryTagImpl).inheritMap(EndBinaryTag.class).alias(EndBinaryTagImpl); + builder.inherit(EntityData.Particle.class); + builder.inherit(EntityData.Particles.class); + builder.inherit(EntityData.VillagerData.class); + builder.inherit(OptionalInt.class); + builder.inherit(EntityData.Pose.class); + builder.inherit(EntityData.CatVariant.class); + StreamCodec wolfVariantHolder = new StreamCodec<>() { + + private static final StreamCodec DIRECT_CODEC = StreamCodec.composite( + ByteBufCodecs.STRING_UTF8, EntityData.WolfVariant.Direct::wildTexture, + ByteBufCodecs.STRING_UTF8, EntityData.WolfVariant.Direct::tameTexture, + ByteBufCodecs.STRING_UTF8, EntityData.WolfVariant.Direct::angryTexture, + HolderSetCodec.CODEC, EntityData.WolfVariant.Direct::biomes, + EntityData.WolfVariant.Direct::new + ); + + @Override + public EntityData.WolfVariant decode(ByteBuf buf, ProtocolVersion version) { + int i = ProtocolUtils.readVarInt(buf); + return i == 0 ? DIRECT_CODEC.decode(buf, version) : EntityData.WolfVariant.of(i - 1); + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, EntityData.WolfVariant value) { + if (value instanceof EntityData.WolfVariant.Reference reference) { + ProtocolUtils.writeVarInt(buf, reference.id() + 1); + } else if (value instanceof EntityData.WolfVariant.Direct direct) { + ProtocolUtils.writeVarInt(buf, 0); + DIRECT_CODEC.encode(buf, version, direct); + } else { + throw new IllegalArgumentException(value.getClass().getName()); + } + } + }; + builder.register(EntityData.WolfVariant.class, wolfVariantHolder).map(EntityData.WolfVariant.Reference.class, wolfVariantHolder).map(EntityData.WolfVariant.Direct.class, wolfVariantHolder); + builder.inherit(EntityData.FrogVariant.class); + builder.inherit(EntityData.OptionalGlobalPos.class).inheritMap(GlobalPos.class); + builder.register(EntityData.PaintingVariantHolder.class, StreamCodec.composite(PaintingVariantCodec.HOLDER_CODEC, EntityData.PaintingVariantHolder::painting, EntityData.PaintingVariantHolder::new)); + builder.inherit(EntityData.SnifferState.class); + builder.inherit(EntityData.ArmadilloState.class); + builder.inherit(EntityData.Vector3.class); + builder.inherit(EntityData.Quaternion.class); + } + try (var builder = new RegistryBuilder<>(ProtocolVersion.MINECRAFT_1_21_5)) { + builder.inherit(Byte.class); + builder.inherit(Integer.class).inheritMap(Short.class); + builder.inherit(Long.class); + builder.inherit(Float.class); + builder.inherit(String.class); + builder.inherit(ComponentHolder.class); + builder.inherit(EntityData.OptionalComponentHolder.class); + builder.inherit(ItemStack.class); + builder.inherit(Boolean.class); + builder.inherit(EntityData.Rotations.class); + builder.inherit(BlockPos.class); + builder.inherit(EntityData.OptionalBlockPos.class); + builder.inherit(EntityData.Direction.class); + builder.inherit(EntityData.OptionalUUID.class); + builder.inherit(EntityData.BlockState.class); + builder.inherit(EntityData.OptionalBlockState.class); + builder.inherit(CompoundBinaryTag.class).alias(CompoundBinaryTagImpl).inheritMap(EndBinaryTag.class).alias(EndBinaryTagImpl); + builder.inherit(EntityData.Particle.class); + builder.inherit(EntityData.Particles.class); + builder.inherit(EntityData.VillagerData.class); + builder.inherit(OptionalInt.class); + builder.inherit(EntityData.Pose.class); + builder.inherit(EntityData.CatVariant.class); + builder.register(EntityData.CowVariant.class, ByteBufCodecs.VAR_INT.map(EntityData.CowVariant::new, EntityData.CowVariant::id)); + builder.register(EntityData.WolfVariant.class, wolfVariantCodec).map(EntityData.WolfVariant.Reference.class, wolfVariantCodec).map(EntityData.WolfVariant.Direct.class, wolfVariantCodec); + builder.register(EntityData.WolfSoundVariant.class, ByteBufCodecs.VAR_INT.map(EntityData.WolfSoundVariant::new, EntityData.WolfSoundVariant::id)); + builder.inherit(EntityData.FrogVariant.class); + builder.register(EntityData.PigVariant.class, ByteBufCodecs.VAR_INT.map(EntityData.PigVariant::new, EntityData.PigVariant::id)); + builder.register(EntityData.ChickenVariant.class, ByteBufCodecs.VAR_INT.map(EntityData.ChickenVariant::new, EntityData.ChickenVariant::id)); + builder.inherit(EntityData.OptionalGlobalPos.class).inheritMap(GlobalPos.class); + builder.inherit(EntityData.PaintingVariantHolder.class); + builder.inherit(EntityData.SnifferState.class); + builder.inherit(EntityData.ArmadilloState.class); + builder.inherit(EntityData.Vector3.class); + builder.inherit(EntityData.Quaternion.class); + } + try (var builder = new RegistryBuilder<>(ProtocolVersion.MINECRAFT_1_21_9)) { + builder.inherit(Byte.class); + builder.inherit(Integer.class).inheritMap(Short.class); + builder.inherit(Long.class); + builder.inherit(Float.class); + builder.inherit(String.class); + builder.inherit(ComponentHolder.class); + builder.inherit(EntityData.OptionalComponentHolder.class); + builder.inherit(ItemStack.class); + builder.inherit(Boolean.class); + builder.inherit(EntityData.Rotations.class); + builder.inherit(BlockPos.class); + builder.inherit(EntityData.OptionalBlockPos.class); + builder.inherit(EntityData.Direction.class); + builder.inherit(EntityData.OptionalUUID.class); + builder.inherit(EntityData.BlockState.class); + builder.inherit(EntityData.OptionalBlockState.class); + builder.inherit(EntityData.Particle.class); + builder.inherit(EntityData.Particles.class); + builder.inherit(EntityData.VillagerData.class); + builder.inherit(OptionalInt.class); + builder.inherit(EntityData.Pose.class); + builder.inherit(EntityData.CatVariant.class); + builder.inherit(EntityData.CowVariant.class); + builder.inherit(EntityData.WolfVariant.class).inheritMap(EntityData.WolfVariant.Reference.class).inheritMap(EntityData.WolfVariant.Direct.class); + builder.inherit(EntityData.WolfSoundVariant.class); + builder.inherit(EntityData.FrogVariant.class); + builder.inherit(EntityData.PigVariant.class); + builder.inherit(EntityData.ChickenVariant.class); + builder.inherit(EntityData.OptionalGlobalPos.class).inheritMap(GlobalPos.class); + builder.inherit(EntityData.PaintingVariantHolder.class); + builder.inherit(EntityData.SnifferState.class); + builder.inherit(EntityData.ArmadilloState.class); + builder.register(EntityData.CopperGolemState.class, ByteBufCodecs.VAR_INT.map(id -> EntityData.CopperGolemState.VALUES[id], EntityData.CopperGolemState::ordinal)); + builder.register(EntityData.WeatheringCopperState.class, ByteBufCodecs.VAR_INT.map(id -> EntityData.WeatheringCopperState.VALUES[id], EntityData.WeatheringCopperState::ordinal)); + builder.inherit(EntityData.Vector3.class); + builder.inherit(EntityData.Quaternion.class); + builder.register(ResolvableProfile.class, ResolvableProfileCodec.CODEC); + } + } + + private static class RegistryBuilder implements AutoCloseable { + + private static final EnumMap>, Object2ObjectOpenHashMap, IntObjectPair>>>> CODECS = new EnumMap<>(ProtocolVersion.class); + private static final ProtocolVersion[] VERSIONS = ProtocolVersion.values(); + + private final Int2ObjectOpenHashMap> id2Codec = new Int2ObjectOpenHashMap<>(); + private final Object2ObjectOpenHashMap, IntObjectPair>> type2Codec = new Object2ObjectOpenHashMap<>(); + private final ProtocolVersion version; + + private int index = -1; + + private IntObjectPair> currentPair; + + private RegistryBuilder(ProtocolVersion version) { + this.version = version; + } + + private RegistryBuilder inheritAllFrom(ProtocolVersion version) { + var entry = RegistryBuilder.CODECS.get(version); + var key = entry.getKey(); + this.id2Codec.putAll(key); + this.type2Codec.putAll(entry.getValue()); + this.index = key.size() - 1; + return this; + } + + @CanIgnoreReturnValue + @SuppressWarnings({"unchecked", "rawtypes"}) + private RegistryBuilder inherit(Class clazz) { + var pair = this.codecFromPreviousVersion(clazz); + StreamCodec codec = pair.right(); + if (this.index + 1 == pair.firstInt()) { + this.id2Codec.put(++this.index, codec); + this.type2Codec.put(clazz, this.currentPair = pair); + return this; + } + + return this.register(clazz, (StreamCodec) codec); + } + + @CanIgnoreReturnValue + @SuppressWarnings("unchecked") + private RegistryBuilder register(Class clazz, StreamCodec codec) { + this.id2Codec.put(++this.index, codec); + this.type2Codec.put(clazz, this.currentPair = IntObjectPair.of(this.index, codec)); + return (RegistryBuilder) this; + } + + @CanIgnoreReturnValue + private RegistryBuilder inheritMap(Class clazz) { + var pair = this.codecFromPreviousVersion(clazz); + if (pair.firstInt() == this.index) { + this.type2Codec.put(clazz, this.currentPair = pair); + } else { + this.map(clazz, pair.right()); + } + + return this; + } + + @CanIgnoreReturnValue + private RegistryBuilder alias(Class clazz) { + this.type2Codec.put(clazz, this.currentPair); + return this; + } + + @CanIgnoreReturnValue + @SuppressWarnings({"unchecked", "rawtypes"}) + private RegistryBuilder map(Class clazz, Function mapper) { + this.type2Codec.put(clazz, IntObjectPair.of(this.index, this.currentPair.right().map(Function.identity(), (Function) mapper))); + return this; + } + + @CanIgnoreReturnValue + private RegistryBuilder map(Class clazz, StreamCodec codec) { + this.type2Codec.put(clazz, IntObjectPair.of(this.index, codec)); + return this; + } + + private IntObjectPair> codecFromPreviousVersion(Class clazz) { + return RegistryBuilder.CODECS.get(RegistryBuilder.VERSIONS[this.version.ordinal() - 1]).getValue().get(clazz); + } + + @Override + public void close() { + this.id2Codec.trim(); + this.type2Codec.trim(); + var entry = Map.entry(this.id2Codec, this.type2Codec); + ProtocolVersion[] versions = RegistryBuilder.VERSIONS; + for (int i = this.version.ordinal(); i < versions.length; ++i) { + RegistryBuilder.CODECS.put(versions[i], entry); + } + } + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/SetExperiencePacket.java b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/SetExperiencePacket.java index 7a46b352..cb14b313 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/SetExperiencePacket.java +++ b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/SetExperiencePacket.java @@ -23,21 +23,7 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; -public class SetExperiencePacket implements MinecraftPacket { - - private final float expBar; - private final int level; - private final int totalExp; - - public SetExperiencePacket(float expBar, int level, int totalExp) { - this.expBar = expBar; - this.level = level; - this.totalExp = totalExp; - } - - public SetExperiencePacket() { - throw new IllegalStateException(); - } +public record SetExperiencePacket(float experienceProgress, int experienceLevel, int totalExperience) implements MinecraftPacket { @Override public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { @@ -46,27 +32,23 @@ public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersi @Override public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { - buf.writeFloat(this.expBar); - if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_8) < 0) { - buf.writeShort(this.level); - buf.writeShort(this.totalExp); + buf.writeFloat(this.experienceProgress); + if (protocolVersion.noGreaterThan(ProtocolVersion.MINECRAFT_1_7_6)) { + buf.writeShort(this.experienceLevel); + buf.writeShort(this.totalExperience); } else { - ProtocolUtils.writeVarInt(buf, this.level); - ProtocolUtils.writeVarInt(buf, this.totalExp); + ProtocolUtils.writeVarInt(buf, this.experienceLevel); + ProtocolUtils.writeVarInt(buf, this.totalExperience); } } @Override public boolean handle(MinecraftSessionHandler handler) { - return true; + throw new IllegalStateException(); } @Override - public String toString() { - return "SetExperience{" - + "expBar=" + this.expBar - + ", level=" + this.level - + ", totalExp=" + this.totalExp - + "}"; + public int encodeSizeHint(ProtocolUtils.Direction direction, ProtocolVersion version) { + return Float.BYTES + 5 + 5; } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/SetSlotPacket.java b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/SetSlotPacket.java index 43356324..55d26b6d 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/SetSlotPacket.java +++ b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/SetSlotPacket.java @@ -22,129 +22,91 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; -import net.elytrium.limboapi.api.material.VirtualItem; -import net.elytrium.limboapi.api.protocol.item.ItemComponentMap; -import net.elytrium.limboapi.utils.ProtocolTools; +import net.elytrium.limboapi.api.world.item.VirtualItem; +import net.elytrium.limboapi.api.world.item.datacomponent.DataComponentMap; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.util.LimboProtocolUtils; import net.kyori.adventure.nbt.CompoundBinaryTag; import org.checkerframework.checker.nullness.qual.Nullable; -public class SetSlotPacket implements MinecraftPacket { - - private final int windowID; - private final int slot; - private final VirtualItem item; - private final int count; - private final int data; - @Nullable - private final CompoundBinaryTag nbt; - @Nullable - private final ItemComponentMap map; - - public SetSlotPacket(int windowID, int slot, VirtualItem item, int count, int data, - @Nullable CompoundBinaryTag nbt, @Nullable ItemComponentMap map) { - this.windowID = windowID; - this.slot = slot; - this.item = item; - this.count = count; - this.data = data; - this.nbt = nbt; - this.map = map; +public record SetSlotPacket(int containerId, int slot, VirtualItem item, int count, short data, @Nullable CompoundBinaryTag nbt, @Nullable DataComponentMap map) implements MinecraftPacket { + + public SetSlotPacket(int containerId, int slot, VirtualItem item, int count) { + this(containerId, slot, item, count, (short) 0, null, null); } - public SetSlotPacket() { - throw new IllegalStateException(); + public SetSlotPacket(int containerId, int slot, VirtualItem item, int count, @Nullable CompoundBinaryTag nbt) { + this(containerId, slot, item, count, (short) 0, nbt, null); } - @Override - public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { - throw new IllegalStateException(); + public SetSlotPacket(int containerId, int slot, VirtualItem item, int count, @Nullable DataComponentMap map) { + this(containerId, slot, item, count, (short) 0, null, map); } - @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { - if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_20_5) >= 0) { - this.encodeModern(buf, direction, protocolVersion); - } else { - this.encodeLegacy(buf, direction, protocolVersion); - } + public SetSlotPacket(int containerId, int slot, VirtualItem item, int count, short data) { + this(containerId, slot, item, count, data, null, null); } - public void encodeModern(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { - ProtocolTools.writeContainerId(buf, protocolVersion, this.windowID); - ProtocolUtils.writeVarInt(buf, 0); - buf.writeShort(this.slot); + public SetSlotPacket(int containerId, int slot, VirtualItem item, int count, short data, @Nullable CompoundBinaryTag nbt) { + this(containerId, slot, item, count, data, nbt, null); + } - int id = this.item.getID(protocolVersion); - if (id == 0) { - ProtocolUtils.writeVarInt(buf, 0); - } else { - ProtocolUtils.writeVarInt(buf, this.count); - ProtocolUtils.writeVarInt(buf, id); + public SetSlotPacket(int containerId, int slot, VirtualItem item, int count, short data, @Nullable DataComponentMap map) { + this(containerId, slot, item, count, data, null, map); + } - if (this.map != null) { - this.map.write(protocolVersion, buf); - } else { - ProtocolUtils.writeVarInt(buf, 0); - ProtocolUtils.writeVarInt(buf, 0); - } - } + @Override + public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + throw new IllegalStateException(); } - public void encodeLegacy(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { - buf.writeByte(this.windowID); + @Override + public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + ByteBufCodecs.CONTAINER_ID.encode(buf, protocolVersion, this.containerId); - if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_17_1) >= 0) { - ProtocolUtils.writeVarInt(buf, 0); // State ID. + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_17_1)) { + ProtocolUtils.writeVarInt(buf, 0); // State id } buf.writeShort(this.slot); - int id = this.item.getID(protocolVersion); - boolean present = id > 0; - if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_13_2) >= 0) { - buf.writeBoolean(present); - } - - if (!present && protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_13_2) < 0) { - buf.writeShort(-1); - } - - if (present) { - if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_13_2) < 0) { - buf.writeShort(id); - } else { + int id; + if (this.count > 0 && (id = this.item.itemId(protocolVersion)) > 0) { + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) { + ProtocolUtils.writeVarInt(buf, this.count); ProtocolUtils.writeVarInt(buf, id); - } - buf.writeByte(this.count); - if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_13) < 0) { - buf.writeShort(this.data); - } - - if (this.nbt == null) { - if (protocolVersion.compareTo(ProtocolVersion.MINECRAFT_1_8) < 0) { - buf.writeShort(-1); + if (this.map == null) { + ProtocolUtils.writeVarInt(buf, 0); // Added + ProtocolUtils.writeVarInt(buf, 0); // Removed } else { - buf.writeByte(0); + this.map.write(buf, protocolVersion); } } else { - ProtocolUtils.writeBinaryTag(buf, protocolVersion, this.nbt); + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_13_2)) { + buf.writeBoolean(true); + ProtocolUtils.writeVarInt(buf, id); + } else { + buf.writeShort(id); + } + + buf.writeByte(this.count); + if (protocolVersion.noGreaterThan(ProtocolVersion.MINECRAFT_1_12_2)) { + buf.writeShort(this.data); + } + + ByteBufCodecs.OPTIONAL_COMPOUND_TAG.encode(buf, protocolVersion, this.nbt); } + } else if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) { + ProtocolUtils.writeVarInt(buf, 0); + } else if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_13_2)) { + buf.writeBoolean(false); + } else { + buf.writeShort(-1); } } @Override public boolean handle(MinecraftSessionHandler handler) { - return true; - } - - @Override - public String toString() { - return "SetSlot{" - + "windowID=" + this.windowID - + ", slot=" + this.slot - + ", count=" + this.count - + ", data=" + this.data - + ", nbt=" + this.nbt - + ")"; + throw new IllegalStateException(); } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/TimeUpdatePacket.java b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/SetTimePacket.java similarity index 72% rename from plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/TimeUpdatePacket.java rename to plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/SetTimePacket.java index 020285a0..89941ae6 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/TimeUpdatePacket.java +++ b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/SetTimePacket.java @@ -23,19 +23,7 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; -public class TimeUpdatePacket implements MinecraftPacket { - - private final long worldAge; - private final long timeOfDay; - - public TimeUpdatePacket(long worldAge, long timeOfDay) { - this.worldAge = worldAge; - this.timeOfDay = timeOfDay; - } - - public TimeUpdatePacket() { - throw new IllegalStateException(); - } +public record SetTimePacket(long gameTime, long dayTime) implements MinecraftPacket { @Override public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { @@ -44,24 +32,20 @@ public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersi @Override public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { - buf.writeLong(this.worldAge); - buf.writeLong(this.timeOfDay); - + buf.writeLong(this.gameTime); + buf.writeLong(this.dayTime); if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) { - buf.writeBoolean(false); // no ticking + buf.writeBoolean(false); // tickDayTime } } @Override public boolean handle(MinecraftSessionHandler handler) { - return true; + throw new IllegalStateException(); } @Override - public String toString() { - return "TimeUpdatePacket{" - + "worldAge=" + this.worldAge - + ", timeOfDay=" + this.timeOfDay - + "}"; + public int encodeSizeHint(ProtocolUtils.Direction direction, ProtocolVersion version) { + return Long.BYTES * 2 + 1; } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/UpdateSignPacket.java b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/UpdateSignPacket.java new file mode 100644 index 00000000..758226d7 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/UpdateSignPacket.java @@ -0,0 +1,107 @@ +package net.elytrium.limboapi.protocol.packets.s2c; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import java.util.regex.Pattern; +import net.elytrium.limboapi.api.world.chunk.blockentity.VirtualBlockEntity; +import net.elytrium.limboapi.server.item.codec.data.BlockPosCodec; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; + +public record UpdateSignPacket(int posX, int posY, int posZ, Component[] lines) implements MinecraftPacket { + + public UpdateSignPacket(VirtualBlockEntity.Entry entry) { + this(entry.getPosX(), entry.getPosY(), entry.getPosZ(), UpdateSignPacket.extractLines(entry.getNbt(ProtocolVersion.MINECRAFT_1_9_4))); + } + + // TODO get rid of this method when JEP 447 + private static Component[] extractLines(CompoundBinaryTag nbt) { + Component[] lines = new Component[4]; + if (nbt == null) { + return lines; + } + + var serializer = ProtocolUtils.getJsonChatSerializer(ProtocolVersion.MINECRAFT_1_9_4); + for (int i = 0; i < 4; ++i) { + lines[i] = serializer.deserialize(nbt.getString("Text" + (i + 1))); + } + + return lines; + } + + @Override + public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + throw new IllegalStateException(); + } + + @Override + public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + boolean v1_7 = protocolVersion.noGreaterThan(ProtocolVersion.MINECRAFT_1_7_6); + if (v1_7) { + buf.writeInt(this.posX); + buf.writeShort(this.posY); + buf.writeInt(this.posZ); + } else { + BlockPosCodec.encode(buf, protocolVersion, this.posX, this.posY, this.posZ); + } + var serializer = protocolVersion.noGreaterThan(ProtocolVersion.MINECRAFT_1_8) ? LegacyComponentSerializer.legacySection() : ProtocolUtils.getJsonChatSerializer(protocolVersion); + for (int i = 0; i < 4; ++i) { + String line = serializer.serialize(this.lines[i]); + if (v1_7) { + // https://github.com/ViaVersion/ViaRewind/blob/4.0.3/common/src/main/java/com/viaversion/viarewind/protocol/v1_8to1_7_6_10/rewriter/WorldPacketRewriter1_8.java#L175 + if (line.startsWith("§f")) { + line = line.substring(2); + } + + line = LegacyUtil.removeUnusedColor(line); + if (line.length() > 15) { + line = PlainTextComponentSerializer.plainText().serialize(this.lines[i]); + if (line.length() > 15) { + line = line.substring(0, 15); + } + } + } + + ProtocolUtils.writeString(buf, line); + } + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + throw new IllegalStateException(); + } + + // https://github.com/ViaVersion/ViaRewind/blob/4.0.3/common/src/main/java/com/viaversion/viarewind/utils/ChatUtil.java#L80 + private static class LegacyUtil { + + private static final Pattern UNUSED_COLOR_PATTERN = Pattern.compile("(?>(?>§[0-fk-or])*(§r|\\Z))|(?>(?>§[0-f])*(§[0-f]))"); + + private static String removeUnusedColor(String legacy) { + legacy = LegacyUtil.UNUSED_COLOR_PATTERN.matcher(legacy).replaceAll("$1$2"); + StringBuilder builder = new StringBuilder(); + char last = '0'; + for (int i = 0; i < legacy.length(); ++i) { + char current = legacy.charAt(i); + if (current != '§' || i == legacy.length() - 1) { + builder.append(current); + continue; + } + + current = legacy.charAt(++i); + if (current == last) { + continue; + } + + builder.append('§').append(current); + last = current; + } + + return builder.toString(); + } + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/UpdateTagsPacket.java b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/UpdateTagsPacket.java index 077a6278..9187ded1 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/UpdateTagsPacket.java +++ b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/UpdateTagsPacket.java @@ -22,73 +22,36 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; -import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; -import java.util.Map.Entry; +import net.elytrium.limboapi.protocol.util.LimboProtocolUtils; -public class UpdateTagsPacket implements MinecraftPacket { - - private final Map>> tags; - - public UpdateTagsPacket() { - throw new IllegalStateException(); - } - - public UpdateTagsPacket(Map>> tags) { - this.tags = tags; - } - - public Map> toVelocityTags() { - Map> newTags = new LinkedHashMap<>(); - for (Entry>> entry : this.tags.entrySet()) { - Map tagRegistry = new LinkedHashMap<>(); - - for (Entry> tagEntry : entry.getValue().entrySet()) { - tagRegistry.put(tagEntry.getKey(), - tagEntry.getValue().stream().mapToInt(Integer::intValue).toArray()); - } - - newTags.put(entry.getKey(), tagRegistry); - } - - return newTags; - } +public record UpdateTagsPacket(Map> tags) implements MinecraftPacket { @Override - public void decode(ByteBuf byteBuf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { throw new IllegalStateException(); } @Override - public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - if (version.compareTo(ProtocolVersion.MINECRAFT_1_17) >= 0) { - ProtocolUtils.writeVarInt(buf, this.tags.size()); - this.tags.forEach((tagType, tagList) -> { - ProtocolUtils.writeString(buf, tagType); - writeTagList(buf, tagList); - }); + public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_17)) { + LimboProtocolUtils.writeMap(buf, this.tags, ProtocolUtils::writeString, UpdateTagsPacket::writeTags); } else { - writeTagList(buf, this.tags.get("minecraft:block")); - writeTagList(buf, this.tags.get("minecraft:item")); - writeTagList(buf, this.tags.get("minecraft:fluid")); - if (version.compareTo(ProtocolVersion.MINECRAFT_1_14) >= 0) { - writeTagList(buf, this.tags.get("minecraft:entity_type")); + UpdateTagsPacket.writeTags(buf, this.tags.get("minecraft:block")); + UpdateTagsPacket.writeTags(buf, this.tags.get("minecraft:item")); + UpdateTagsPacket.writeTags(buf, this.tags.get("minecraft:fluid")); + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_14)) { + UpdateTagsPacket.writeTags(buf, this.tags.get("minecraft:entity_type")); } } } - private static void writeTagList(ByteBuf buf, Map> tagList) { - ProtocolUtils.writeVarInt(buf, tagList.size()); - tagList.forEach((tagId, blockList) -> { - ProtocolUtils.writeString(buf, tagId); - ProtocolUtils.writeVarInt(buf, blockList.size()); - blockList.forEach(blockId -> ProtocolUtils.writeVarInt(buf, blockId)); - }); + private static void writeTags(ByteBuf buf, Map tags) { + LimboProtocolUtils.writeMap(buf, tags, ProtocolUtils::writeString, LimboProtocolUtils::writeVarIntArray); } @Override public boolean handle(MinecraftSessionHandler handler) { - return true; + throw new IllegalStateException(); } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/protocol/util/LimboProtocolUtils.java b/plugin/src/main/java/net/elytrium/limboapi/protocol/util/LimboProtocolUtils.java new file mode 100644 index 00000000..036687c1 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/protocol/util/LimboProtocolUtils.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2021 - 2024 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limboapi.protocol.util; + +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.EncoderException; +import java.util.Collection; +import java.util.Map; +import java.util.function.Consumer; +import org.checkerframework.checker.nullness.qual.Nullable; + +public class LimboProtocolUtils { + + public static void writeCollection(ByteBuf buf, Collection collection, Consumer encoder) { + LimboProtocolUtils.writeCollection(buf, collection, Integer.MAX_VALUE, encoder); + } + + public static void writeCollection(ByteBuf buf, Collection collection, int limit, Consumer encoder) { + if (collection == null) { + ProtocolUtils.writeVarInt(buf, 0); + return; + } + + int amount = collection.size(); + if (amount == 0) { + ProtocolUtils.writeVarInt(buf, 0); + } else { + if (amount > limit) { + throw new EncoderException(amount + " elements exceeded max size of: " + limit); + } + + ProtocolUtils.writeVarInt(buf, amount); + collection.forEach(encoder); + } + } + + public static void writeArray(ByteBuf buf, T @Nullable [] array, Consumer encoder) { + LimboProtocolUtils.writeArray(buf, array, Integer.MAX_VALUE, encoder); + } + + public static void writeArray(ByteBuf buf, T @Nullable [] array, int limit, Consumer encoder) { + if (array == null) { + ProtocolUtils.writeVarInt(buf, 0); + return; + } + + int amount = array.length; + if (amount == 0) { + ProtocolUtils.writeVarInt(buf, 0); + } else { + if (amount > limit) { + throw new EncoderException(amount + " elements exceeded max size of: " + limit); + } + + ProtocolUtils.writeVarInt(buf, amount); + for (T value : array) { + encoder.accept(value); + } + } + } + + public static void writeLongArray(ByteBuf buf, long @Nullable [] array) { + LimboProtocolUtils.writeLongArray(buf, array, Integer.MAX_VALUE); + } + + public static void writeLongArray(ByteBuf buf, long @Nullable [] array, int limit) { + if (array == null) { + ProtocolUtils.writeVarInt(buf, 0); + return; + } + + int amount = array.length; + if (amount == 0) { + ProtocolUtils.writeVarInt(buf, 0); + } else { + if (amount > limit) { + throw new EncoderException(amount + " elements exceeded max size of: " + limit); + } + + ProtocolUtils.writeVarInt(buf, amount); + for (long value : array) { + buf.writeLong(value); + } + } + } + + public static void writeVarIntArray(ByteBuf buf, int @Nullable [] array) { + LimboProtocolUtils.writeVarIntArray(buf, array, Integer.MAX_VALUE); + } + + public static void writeVarIntArray(ByteBuf buf, int @Nullable [] array, int limit) { + if (array == null) { + ProtocolUtils.writeVarInt(buf, 0); + return; + } + + int amount = array.length; + if (amount == 0) { + ProtocolUtils.writeVarInt(buf, 0); + } else { + if (amount > limit) { + throw new EncoderException(amount + " elements exceeded max size of: " + limit); + } + + ProtocolUtils.writeVarInt(buf, amount); + for (int value : array) { + ProtocolUtils.writeVarInt(buf, value); + } + } + } + + public static void writeMap(ByteBuf buf, Map map, EncoderByteBufValue keyEncoder, EncoderByteBufValue valueEncoder) { + LimboProtocolUtils.writeMap(buf, map, Integer.MAX_VALUE, keyEncoder, valueEncoder); + } + + public static void writeMap(ByteBuf buf, Map map, int limit, EncoderByteBufValue keyEncoder, EncoderByteBufValue valueEncoder) { + if (map == null) { + ProtocolUtils.writeVarInt(buf, 0); + return; + } + + int amount = map.size(); + if (amount == 0) { + ProtocolUtils.writeVarInt(buf, 0); + } else { + if (amount > limit) { + throw new EncoderException(amount + " elements exceeded max size of: " + limit); + } + + ProtocolUtils.writeVarInt(buf, amount); + map.forEach((key, value) -> { + keyEncoder.write(buf, key); + valueEncoder.write(buf, value); + }); + } + } + + @FunctionalInterface + public interface EncoderByteBufValue { + + void write(ByteBuf buf, T value); + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/LimboImpl.java b/plugin/src/main/java/net/elytrium/limboapi/server/LimboImpl.java index 53ffad05..a6240506 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/server/LimboImpl.java +++ b/plugin/src/main/java/net/elytrium/limboapi/server/LimboImpl.java @@ -17,7 +17,6 @@ package net.elytrium.limboapi.server; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.mojang.brigadier.tree.RootCommandNode; import com.velocitypowered.api.command.Command; @@ -57,21 +56,18 @@ import com.velocitypowered.proxy.protocol.packet.title.GenericTitlePacket; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; -import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelOutboundHandlerAdapter; import io.netty.channel.ChannelPipeline; import io.netty.handler.timeout.ReadTimeoutHandler; -import it.unimi.dsi.fastutil.Pair; import java.io.IOException; import java.io.InputStream; import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; -import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -81,9 +77,7 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.LongAdder; -import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Supplier; import net.elytrium.commons.utils.reflection.ReflectionException; @@ -92,25 +86,25 @@ import net.elytrium.limboapi.Settings; import net.elytrium.limboapi.api.Limbo; import net.elytrium.limboapi.api.LimboSessionHandler; -import net.elytrium.limboapi.api.chunk.Dimension; -import net.elytrium.limboapi.api.chunk.VirtualChunk; -import net.elytrium.limboapi.api.chunk.VirtualWorld; -import net.elytrium.limboapi.api.command.LimboCommandMeta; -import net.elytrium.limboapi.api.player.GameMode; +import net.elytrium.limboapi.api.world.chunk.Dimension; +import net.elytrium.limboapi.api.world.chunk.VirtualChunk; +import net.elytrium.limboapi.api.world.VirtualWorld; +import net.elytrium.limboapi.api.world.player.GameMode; import net.elytrium.limboapi.api.protocol.PacketDirection; import net.elytrium.limboapi.api.protocol.PreparedPacket; -import net.elytrium.limboapi.api.protocol.packets.PacketMapping; +import net.elytrium.limboapi.api.protocol.PacketMapping; import net.elytrium.limboapi.injection.login.confirmation.LoginConfirmHandler; import net.elytrium.limboapi.injection.packet.MinecraftLimitedCompressDecoder; -import net.elytrium.limboapi.material.Biome; +import net.elytrium.limboapi.server.world.Biome; import net.elytrium.limboapi.protocol.LimboProtocol; -import net.elytrium.limboapi.protocol.packets.s2c.ChangeGameStatePacket; -import net.elytrium.limboapi.protocol.packets.s2c.ChunkDataPacket; +import net.elytrium.limboapi.protocol.packets.s2c.GameEventPacket; import net.elytrium.limboapi.protocol.packets.s2c.DefaultSpawnPositionPacket; -import net.elytrium.limboapi.protocol.packets.s2c.PositionRotationPacket; -import net.elytrium.limboapi.protocol.packets.s2c.TimeUpdatePacket; -import net.elytrium.limboapi.protocol.packets.s2c.UpdateViewPositionPacket; +import net.elytrium.limboapi.protocol.packets.s2c.PlayerPositionPacket; +import net.elytrium.limboapi.protocol.packets.s2c.SetTimePacket; +import net.elytrium.limboapi.protocol.packets.s2c.SetChunkCacheCenterPacket; +import net.elytrium.limboapi.protocol.util.LimboProtocolUtils; import net.elytrium.limboapi.server.world.SimpleTagManager; +import net.elytrium.limboapi.utils.Reflection; import net.kyori.adventure.nbt.BinaryTag; import net.kyori.adventure.nbt.BinaryTagIO; import net.kyori.adventure.nbt.BinaryTagTypes; @@ -121,42 +115,39 @@ public class LimboImpl implements Limbo { + private static final MethodHandle GRACEFUL_DISCONNECT_SETTER = Reflection.findSetter(VelocityServerConnection.class, "gracefulDisconnect", boolean.class); + private static final MethodHandle PARTIAL_HASHED_SEED_SETTER = Reflection.findSetter(JoinGamePacket.class, "partialHashedSeed", long.class); + private static final MethodHandle LEVEL_NAMES_SETTER = Reflection.findSetter(JoinGamePacket.class, "levelNames", ImmutableSet.class); + private static final MethodHandle REGISTRY_SETTER = Reflection.findSetter(JoinGamePacket.class, "registry", CompoundBinaryTag.class); + private static final MethodHandle CURRENT_DIMENSION_DATA_SETTER = Reflection.findSetter(JoinGamePacket.class, "currentDimensionData", CompoundBinaryTag.class); + private static final MethodHandle ROOT_NODE_SETTER = Reflection.findSetter(AvailableCommandsPacket.class, "rootNode", RootCommandNode.class); + private static final ImmutableSet LEVELS = ImmutableSet.of( Dimension.OVERWORLD.getKey(), Dimension.NETHER.getKey(), Dimension.THE_END.getKey() ); - private static final MethodHandle PARTIAL_HASHED_SEED_FIELD; - private static final MethodHandle CURRENT_DIMENSION_DATA_FIELD; - private static final MethodHandle ROOT_NODE_FIELD; - private static final MethodHandle GRACEFUL_DISCONNECT_FIELD; - private static final MethodHandle REGISTRY_FIELD; - private static final MethodHandle LEVEL_NAMES_FIELDS; - private static final CompoundBinaryTag CHAT_TYPE_119; private static final CompoundBinaryTag CHAT_TYPE_1191; private static final CompoundBinaryTag DAMAGE_TYPE_1194; private static final CompoundBinaryTag DAMAGE_TYPE_120; private final Map, PreparedPacket> brandMessages = new HashMap<>(2); - private final LimboAPI plugin; - private final VirtualWorld world; + private final List queuedToRelease = new ArrayList<>(); private final LongAdder currentOnline = new LongAdder(); + private final RootCommandNode commandNode = new RootCommandNode<>(); - private final ReadWriteLock lock = new ReentrantReadWriteLock(); - private final List queuedToRelease = new ArrayList<>(); - private final List> registrars = ImmutableList.of( - new BrigadierCommandRegistrar(this.commandNode, this.lock.writeLock()), - new SimpleCommandRegistrar(this.commandNode, this.lock.writeLock()), - new RawCommandRegistrar(this.commandNode, this.lock.writeLock()) - ); + private final List> registrars; + + private final LimboAPI plugin; + private final VirtualWorld world; private String limboName; private Integer readTimeout; private Long worldTicks; - private short gameMode = GameMode.ADVENTURE.getID(); + private short gameMode = GameMode.ADVENTURE.getId(); private Integer maxSuppressPacketLength; private PreparedPacket joinPackets; @@ -166,24 +157,28 @@ public class LimboImpl implements Limbo { private PreparedPacket firstChunks; private List delayedChunks; private PreparedPacket respawnPackets; - protected PreparedPacket configTransitionPackets; - protected PreparedPacket configPackets; - protected StateRegistry localStateRegistry; + private PreparedPacket configTransitionPackets; + private PreparedPacket configPackets; + private StateRegistry localStateRegistry = LimboProtocol.getLimboStateRegistry(); private boolean shouldRespawn = true; private boolean shouldRejoin = true; private boolean shouldUpdateTags = true; private boolean reducedDebugInfo = Settings.IMP.MAIN.REDUCED_DEBUG_INFO; private int viewDistance = Settings.IMP.MAIN.VIEW_DISTANCE; private int simulationDistance = Settings.IMP.MAIN.SIMULATION_DISTANCE; - private volatile boolean built = true; + private volatile boolean built = false; private boolean disposeScheduled = false; public LimboImpl(LimboAPI plugin, VirtualWorld world) { + var lock = new ReentrantReadWriteLock(); + this.registrars = List.of( + new BrigadierCommandRegistrar(this.commandNode, lock.writeLock()), + new SimpleCommandRegistrar(this.commandNode, lock.writeLock()), + new RawCommandRegistrar(this.commandNode, lock.writeLock()) + ); + this.plugin = plugin; this.world = world; - this.localStateRegistry = LimboProtocol.getLimboStateRegistry(); - - this.refresh(); } protected void refresh() { @@ -211,30 +206,30 @@ protected void refresh() { .prepare(joinGame1212, ProtocolVersion.MINECRAFT_1_21_2); PreparedPacket fastRejoinPackets = this.plugin.createPreparedPacket(); - this.createFastClientServerSwitch(legacyJoinGame, ProtocolVersion.MINECRAFT_1_7_2) + LimboImpl.createFastClientServerSwitch(legacyJoinGame, ProtocolVersion.MINIMUM_VERSION) .forEach(minecraftPacket -> fastRejoinPackets.prepare(minecraftPacket, ProtocolVersion.MINIMUM_VERSION, ProtocolVersion.MINECRAFT_1_15_2)); - this.createFastClientServerSwitch(joinGame, ProtocolVersion.MINECRAFT_1_16) + LimboImpl.createFastClientServerSwitch(joinGame, ProtocolVersion.MINECRAFT_1_16) .forEach(minecraftPacket -> fastRejoinPackets.prepare(minecraftPacket, ProtocolVersion.MINECRAFT_1_16, ProtocolVersion.MINECRAFT_1_16_1)); - this.createFastClientServerSwitch(joinGame1162, ProtocolVersion.MINECRAFT_1_16_2) + LimboImpl.createFastClientServerSwitch(joinGame1162, ProtocolVersion.MINECRAFT_1_16_2) .forEach(minecraftPacket -> fastRejoinPackets.prepare(minecraftPacket, ProtocolVersion.MINECRAFT_1_16_2, ProtocolVersion.MINECRAFT_1_18)); - this.createFastClientServerSwitch(joinGame1182, ProtocolVersion.MINECRAFT_1_18_2) + LimboImpl.createFastClientServerSwitch(joinGame1182, ProtocolVersion.MINECRAFT_1_18_2) .forEach(minecraftPacket -> fastRejoinPackets.prepare(minecraftPacket, ProtocolVersion.MINECRAFT_1_18_2, ProtocolVersion.MINECRAFT_1_18_2)); - this.createFastClientServerSwitch(joinGame119, ProtocolVersion.MINECRAFT_1_19) + LimboImpl.createFastClientServerSwitch(joinGame119, ProtocolVersion.MINECRAFT_1_19) .forEach(minecraftPacket -> fastRejoinPackets.prepare(minecraftPacket, ProtocolVersion.MINECRAFT_1_19, ProtocolVersion.MINECRAFT_1_19)); - this.createFastClientServerSwitch(joinGame1191, ProtocolVersion.MINECRAFT_1_19_1) + LimboImpl.createFastClientServerSwitch(joinGame1191, ProtocolVersion.MINECRAFT_1_19_1) .forEach(minecraftPacket -> fastRejoinPackets.prepare(minecraftPacket, ProtocolVersion.MINECRAFT_1_19_1, ProtocolVersion.MINECRAFT_1_19_3)); - this.createFastClientServerSwitch(joinGame1194, ProtocolVersion.MINECRAFT_1_19_4) + LimboImpl.createFastClientServerSwitch(joinGame1194, ProtocolVersion.MINECRAFT_1_19_4) .forEach(minecraftPacket -> fastRejoinPackets.prepare(minecraftPacket, ProtocolVersion.MINECRAFT_1_19_4, ProtocolVersion.MINECRAFT_1_19_4)); - this.createFastClientServerSwitch(joinGame120, ProtocolVersion.MINECRAFT_1_20) + LimboImpl.createFastClientServerSwitch(joinGame120, ProtocolVersion.MINECRAFT_1_20) .forEach(minecraftPacket -> fastRejoinPackets.prepare(minecraftPacket, ProtocolVersion.MINECRAFT_1_20, ProtocolVersion.MINECRAFT_1_20_5)); - this.createFastClientServerSwitch(joinGame121, ProtocolVersion.MINECRAFT_1_21) + LimboImpl.createFastClientServerSwitch(joinGame121, ProtocolVersion.MINECRAFT_1_21) .forEach(minecraftPacket -> fastRejoinPackets.prepare(minecraftPacket, ProtocolVersion.MINECRAFT_1_21, ProtocolVersion.MINECRAFT_1_21)); - this.createFastClientServerSwitch(joinGame1212, ProtocolVersion.MINECRAFT_1_21_2) + LimboImpl.createFastClientServerSwitch(joinGame1212, ProtocolVersion.MINECRAFT_1_21_2) .forEach(minecraftPacket -> fastRejoinPackets.prepare(minecraftPacket, ProtocolVersion.MINECRAFT_1_21_2)); this.joinPackets = this.addPostJoin(joinPackets); this.fastRejoinPackets = this.addPostJoin(fastRejoinPackets); - this.safeRejoinPackets = this.addPostJoin(this.plugin.createPreparedPacket().prepare(this.createSafeClientServerSwitch(legacyJoinGame))); + this.safeRejoinPackets = this.addPostJoin(this.plugin.createPreparedPacket().prepare(LimboImpl.createSafeClientServerSwitch(legacyJoinGame))); this.postJoinPackets = this.addPostJoin(this.plugin.createPreparedPacket()); this.configTransitionPackets = this.plugin.createPreparedPacket() @@ -256,51 +251,41 @@ protected void refresh() { this.firstChunks = this.createFirstChunks(); this.delayedChunks = this.createDelayedChunksPackets(); PreparedPacket respawnPackets = this.plugin.createPreparedPacket() - .prepare( - this.createPlayerPosAndLook( - this.world.getSpawnX(), this.world.getSpawnY(), this.world.getSpawnZ(), this.world.getYaw(), this.world.getPitch() - ) - ).prepare( - this.createUpdateViewPosition((int) this.world.getSpawnX(), (int) this.world.getSpawnZ()), - ProtocolVersion.MINECRAFT_1_14 - ); + .prepare(LimboImpl.createPlayerPosition(this.world.getSpawnX(), this.world.getSpawnY(), this.world.getSpawnZ(), this.world.getYaw(), this.world.getPitch())) + .prepare(LimboImpl.createSetChunkCacheCenter((int) this.world.getSpawnX(), (int) this.world.getSpawnZ()), ProtocolVersion.MINECRAFT_1_14); if (this.shouldUpdateTags) { - respawnPackets.prepare(SimpleTagManager::getUpdateTagsPacket, - ProtocolVersion.MINECRAFT_1_13, ProtocolVersion.MINECRAFT_1_20); + respawnPackets.prepare(SimpleTagManager::getUpdateTagsPacket, ProtocolVersion.MINECRAFT_1_13, ProtocolVersion.MINECRAFT_1_20); } this.respawnPackets = respawnPackets.build(); this.built = true; } - private ChangeGameStatePacket createLevelChunksLoadStartGameState() { - return new ChangeGameStatePacket(13, 0); + private GameEventPacket createLevelChunksLoadStartGameEvent() { + return new GameEventPacket(13, 0); // LEVEL_CHUNKS_LOAD_START } private RegistrySyncPacket createRegistrySyncLegacy(ProtocolVersion version) { - JoinGamePacket join = this.createJoinGamePacket(version); - ByteBuf encodedRegistry = this.plugin.getPreparedPacketFactory().getPreparedPacketAllocator().ioBuffer(); - ProtocolUtils.writeBinaryTag(encodedRegistry, version, join.getRegistry()); + ProtocolUtils.writeBinaryTag(encodedRegistry, version, LimboImpl.createRegistry(version)); RegistrySyncPacket sync = new RegistrySyncPacket(); sync.replace(encodedRegistry); return sync; } + @SuppressWarnings("unchecked") private void createRegistrySyncModern(PreparedPacket packet, ProtocolVersion from, ProtocolVersion to) { - JoinGamePacket join = this.createJoinGamePacket(from); - - CompoundBinaryTag registryTag = join.getRegistry(); + CompoundBinaryTag registryTag = LimboImpl.createRegistry(from); for (String key : registryTag.keySet()) { CompoundBinaryTag entry = registryTag.getCompound(key); String type = entry.getString("type"); ListBinaryTag values = entry.getList("value", BinaryTagTypes.COMPOUND); - Pair emptyTag = null; - Pair[] tags = new Pair[0]; + Map.Entry emptyTag = null; + Map.Entry[] tags = new Map.Entry[0]; for (BinaryTag elementTag : values) { CompoundBinaryTag element = (CompoundBinaryTag) elementTag; @@ -309,32 +294,33 @@ private void createRegistrySyncModern(PreparedPacket packet, ProtocolVersion fro tags = Arrays.copyOf(tags, id + 1); } - tags[id] = Pair.of(element.getString("name"), element.getCompound("element")); + tags[id] = Map.entry(element.getString("name"), element.getCompound("element")); if (emptyTag == null) { emptyTag = tags[id]; } } - for (int i = 0; i < tags.length; i++) { + for (int i = 0; i < tags.length; ++i) { if (tags[i] == null) { - tags[i] = Pair.of("limboapi_padding_" + i, emptyTag.value()); + tags[i] = Map.entry("limboapi_padding_" + i, emptyTag.getValue()); } } - Pair[] patchedTags = tags; + var patchedTags = tags; packet.prepare(version -> { ByteBuf registry = this.plugin.getPreparedPacketFactory().getPreparedPacketAllocator().ioBuffer(); ProtocolUtils.writeString(registry, type); - ProtocolUtils.writeVarInt(registry, patchedTags.length); - for (Pair tag : patchedTags) { - ProtocolUtils.writeString(registry, tag.left()); - - registry.writeBoolean(tag.right() != null); - if (tag.right() != null) { - ProtocolUtils.writeBinaryTag(registry, version, tag.right()); + LimboProtocolUtils.writeArray(registry, patchedTags, pair -> { + ProtocolUtils.writeString(registry, pair.getKey()); + BinaryTag tag = pair.getValue(); + if (tag == null) { + registry.writeBoolean(false); + } else { + registry.writeBoolean(true); + ProtocolUtils.writeBinaryTag(registry, version, tag); } - } + }); RegistrySyncPacket sync = new RegistrySyncPacket(); sync.replace(registry); @@ -344,20 +330,20 @@ private void createRegistrySyncModern(PreparedPacket packet, ProtocolVersion fro } private TagsUpdatePacket createTagsUpdate(ProtocolVersion version) { - return new TagsUpdatePacket(SimpleTagManager.getUpdateTagsPacket(version).toVelocityTags()); + return new TagsUpdatePacket(SimpleTagManager.getUpdateTagsPacket(version).tags()); } private PreparedPacket addPostJoin(PreparedPacket packet) { return packet.prepare(this.createAvailableCommandsPacket(), ProtocolVersion.MINECRAFT_1_13) .prepare(this.createDefaultSpawnPositionPacket()) - .prepare(this.createLevelChunksLoadStartGameState(), ProtocolVersion.MINECRAFT_1_20_3) + .prepare(this.createLevelChunksLoadStartGameEvent(), ProtocolVersion.MINECRAFT_1_20_3) .prepare(this.createWorldTicksPacket()) .prepare(this::createBrandMessage) .build(); } @Override - public void spawnPlayer(Player apiPlayer, LimboSessionHandler handler) { + public void spawnPlayer(Player apiPlayer, LimboSessionHandler handler) { // TODO prevent double spawnPlayer without proper disconnect (?) (я уже не помню откуда это и при каких условиях возникает) if (!this.built) { synchronized (this) { if (!this.built) { @@ -382,21 +368,20 @@ public void spawnPlayer(Player apiPlayer, LimboSessionHandler handler) { boolean shouldSpawnPlayerImmediately = true; // Discard information from previous server - if (player.getConnection().getActiveSessionHandler() instanceof ClientPlaySessionHandler sessionHandler) { + if (connection.getActiveSessionHandler() instanceof ClientPlaySessionHandler sessionHandler) { connection.eventLoop().execute(() -> { player.getTabList().clearAll(); for (UUID serverBossBar : sessionHandler.getServerBossBars()) { - player.getConnection().delayedWrite(BossBarPacket.createRemovePacket(serverBossBar, null)); + connection.delayedWrite(BossBarPacket.createRemovePacket(serverBossBar, null)); } sessionHandler.getServerBossBars().clear(); if (player.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_8)) { - player.getConnection().delayedWrite(GenericTitlePacket.constructTitlePacket( - GenericTitlePacket.ActionType.RESET, player.getProtocolVersion())); + connection.delayedWrite(GenericTitlePacket.constructTitlePacket(GenericTitlePacket.ActionType.RESET, player.getProtocolVersion())); player.clearPlayerListHeaderAndFooter(); } - player.getConnection().flush(); + connection.flush(); }); } @@ -407,13 +392,12 @@ public void spawnPlayer(Player apiPlayer, LimboSessionHandler handler) { MinecraftConnection serverConnection = server.getConnection(); if (serverConnection != null) { try { - GRACEFUL_DISCONNECT_FIELD.invokeExact(server, true); - } catch (Throwable e) { - throw new ReflectionException(e); + GRACEFUL_DISCONNECT_SETTER.invokeExact(server, true); + } catch (Throwable t) { + throw new ReflectionException(t); } - connection.eventLoop().execute(() -> - serverConnection.getChannel().close().addListener(f -> this.spawnPlayerLocal(player, handler, previousServer))); + connection.eventLoop().execute(() -> serverConnection.getChannel().close().addListener(f -> this.spawnPlayerLocal(player, handler, previousServer))); shouldSpawnPlayerImmediately = false; } @@ -427,15 +411,15 @@ public void spawnPlayer(Player apiPlayer, LimboSessionHandler handler) { } } - protected void spawnPlayerLocal(Class handlerClass, - LimboSessionHandlerImpl sessionHandler, ConnectedPlayer player, MinecraftConnection connection) { + protected void spawnPlayerLocal(Class handlerClass, LimboSessionHandlerImpl sessionHandler, ConnectedPlayer player, MinecraftConnection connection) { if (!connection.eventLoop().inEventLoop()) { connection.eventLoop().execute(() -> this.spawnPlayerLocal(handlerClass, sessionHandler, player, connection)); return; } connection.setActiveSessionHandler(connection.getState(), sessionHandler); - if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) >= 0) { + ProtocolVersion version = connection.getProtocolVersion(); + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) { if (connection.getState() != StateRegistry.CONFIG) { if (this.shouldRejoin) { // Switch to CONFIG state @@ -451,14 +435,14 @@ protected void spawnPlayerLocal(Class handlerClas sessionHandler.onConfig(new LimboPlayerImpl(this.plugin, this, player)); - if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) < 0 - || (connection.getState() != StateRegistry.CONFIG && !this.shouldRejoin)) { + if (version.lessThan(ProtocolVersion.MINECRAFT_1_20_2) || (connection.getState() != StateRegistry.CONFIG && !this.shouldRejoin)) { this.onSpawn(handlerClass, connection, player, sessionHandler); } connection.flush(); } + @SuppressWarnings("UnnecessaryToStringCall") private void spawnPlayerLocal(ConnectedPlayer player, LimboSessionHandler handler, RegisteredServer previousServer) { MinecraftConnection connection = player.getConnection(); connection.eventLoop().execute(() -> { @@ -471,20 +455,19 @@ private void spawnPlayerLocal(ConnectedPlayer player, LimboSessionHandler handle } if (Settings.IMP.MAIN.LOGGING_ENABLED) { - LimboAPI.getLogger().info(player.getUsername() + " (" + player.getRemoteAddress() + ") has connected to the " + this.limboName + " Limbo"); + LimboAPI.getLogger().info("{} has connected to the {} Limbo", player.toString(), this.limboName); } // With an abnormally large number of connections from the same nickname, - // requests don't have time to be processed, and an error occurs that "minecraft-encoder" doesn't exist. + // requests don't have time to be processed, and an error occurs that "minecraft-encoder" doesn't exist if (pipeline.get(Connections.MINECRAFT_ENCODER) != null) { if (this.readTimeout != null) { pipeline.replace(Connections.READ_TIMEOUT, LimboProtocol.READ_TIMEOUT, new ReadTimeoutHandler(this.readTimeout, TimeUnit.MILLISECONDS)); } boolean compressionEnabled = false; - if (pipeline.get(PreparedPacketFactory.PREPARED_ENCODER) == null) { - if (this.plugin.isCompressionEnabled() && connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) { + if (this.plugin.isCompressionEnabled() && connection.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_8)) { if (pipeline.get(Connections.FRAME_ENCODER) != null) { if (!Settings.IMP.MAIN.COMPATIBILITY_MODE) { pipeline.remove(Connections.FRAME_ENCODER); @@ -493,24 +476,17 @@ private void spawnPlayerLocal(ConnectedPlayer player, LimboSessionHandler handle if (pipeline.context(Connections.COMPRESSION_DECODER) != null) { this.plugin.fixDecompressor(pipeline, this.plugin.getServer().getConfiguration().getCompressionThreshold(), false); } - } else { - ChannelHandler minecraftCompressDecoder = pipeline.remove(Connections.COMPRESSION_DECODER); - if (minecraftCompressDecoder != null) { - this.plugin.fixDecompressor(pipeline, this.plugin.getServer().getConfiguration().getCompressionThreshold(), false); - pipeline.replace(Connections.COMPRESSION_ENCODER, Connections.COMPRESSION_ENCODER, new ChannelOutboundHandlerAdapter()); - compressionEnabled = true; - } + } else if (pipeline.remove(Connections.COMPRESSION_DECODER) != null) { + this.plugin.fixDecompressor(pipeline, this.plugin.getServer().getConfiguration().getCompressionThreshold(), false); + pipeline.replace(Connections.COMPRESSION_ENCODER, Connections.COMPRESSION_ENCODER, new ChannelOutboundHandlerAdapter()); + compressionEnabled = true; } } else if (!Settings.IMP.MAIN.COMPATIBILITY_MODE) { pipeline.remove(Connections.FRAME_ENCODER); } this.plugin.inject3rdParty(player, connection, pipeline); - if (compressionEnabled) { - pipeline.fireUserEventTriggered(VelocityConnectionEvent.COMPRESSION_ENABLED); - } else { - pipeline.fireUserEventTriggered(VelocityConnectionEvent.COMPRESSION_DISABLED); - } + pipeline.fireUserEventTriggered(compressionEnabled ? VelocityConnectionEvent.COMPRESSION_ENABLED : VelocityConnectionEvent.COMPRESSION_DISABLED); } } else { connection.close(); @@ -524,17 +500,7 @@ private void spawnPlayerLocal(ConnectedPlayer player, LimboSessionHandler handle } } - LimboSessionHandlerImpl sessionHandler = new LimboSessionHandlerImpl( - this.plugin, - this, - player, - handler, - connection.getState(), - connection.getActiveSessionHandler(), - previousServer, - () -> this.limboName - ); - + LimboSessionHandlerImpl sessionHandler = new LimboSessionHandlerImpl(this.plugin, this, player, handler, connection.getState(), connection.getActiveSessionHandler(), previousServer); if (connection.getActiveSessionHandler() instanceof LoginConfirmHandler confirm) { confirm.waitForConfirmation(() -> { this.currentOnline.increment(); @@ -547,8 +513,7 @@ private void spawnPlayerLocal(ConnectedPlayer player, LimboSessionHandler handle }); } - protected void onSpawn(Class handlerClass, - MinecraftConnection connection, ConnectedPlayer player, LimboSessionHandlerImpl sessionHandler) { + protected void onSpawn(Class handlerClass, MinecraftConnection connection, ConnectedPlayer player, LimboSessionHandlerImpl sessionHandler) { this.plugin.setState(connection, this.localStateRegistry); if (this.plugin.isLimboJoined(player)) { if (this.shouldRejoin) { @@ -566,31 +531,21 @@ protected void onSpawn(Class handlerClass, connection.delayedWrite(this.joinPackets); } + UUID uuid = LimboAPI.getClientUniqueId(player); MinecraftPacket playerInfoPacket; - - UUID uuid = this.plugin.getInitialID(player); - if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19_1) <= 0) { - playerInfoPacket = new LegacyPlayerListItemPacket( - LegacyPlayerListItemPacket.ADD_PLAYER, - List.of( - new LegacyPlayerListItemPacket.Item(uuid) - .setName(player.getUsername()) - .setGameMode(this.gameMode) - .setProperties(player.getGameProfileProperties()) - ) + if (connection.getProtocolVersion().noGreaterThan(ProtocolVersion.MINECRAFT_1_19_1)) { + playerInfoPacket = new LegacyPlayerListItemPacket(LegacyPlayerListItemPacket.ADD_PLAYER, + Collections.singletonList(new LegacyPlayerListItemPacket.Item(uuid).setName(player.getUsername()).setGameMode(this.gameMode).setProperties(player.getGameProfileProperties())) ); } else { UpsertPlayerInfoPacket.Entry playerInfoEntry = new UpsertPlayerInfoPacket.Entry(uuid); playerInfoEntry.setDisplayName(new ComponentHolder(player.getProtocolVersion(), Component.text(player.getUsername()))); playerInfoEntry.setGameMode(this.gameMode); playerInfoEntry.setProfile(player.getGameProfile()); - playerInfoPacket = new UpsertPlayerInfoPacket( - EnumSet.of( - UpsertPlayerInfoPacket.Action.UPDATE_DISPLAY_NAME, - UpsertPlayerInfoPacket.Action.UPDATE_GAME_MODE, - UpsertPlayerInfoPacket.Action.ADD_PLAYER), - List.of(playerInfoEntry)); + EnumSet.of(UpsertPlayerInfoPacket.Action.UPDATE_DISPLAY_NAME, UpsertPlayerInfoPacket.Action.UPDATE_GAME_MODE, UpsertPlayerInfoPacket.Action.ADD_PLAYER), + Collections.singletonList(playerInfoEntry) + ); } connection.delayedWrite(playerInfoPacket); @@ -615,29 +570,28 @@ public void respawnPlayer(Player player) { connection.write(this.firstChunks); } - if (!this.delayedChunks.isEmpty()) { - AtomicReference> task = new AtomicReference<>(); - task.set(connection.eventLoop().scheduleAtFixedRate(new Runnable() { + List chunksSnapshot = this.delayedChunks; + if (!chunksSnapshot.isEmpty()) { + ScheduledFuture[] task = new ScheduledFuture[1]; + task[0] = connection.eventLoop().scheduleAtFixedRate(new Runnable() { - private final List chunksSnapshot = LimboImpl.this.delayedChunks; private int index; @Override public void run() { if (connection.isClosed()) { - task.get().cancel(false); + task[0].cancel(false); return; } - connection.write(this.chunksSnapshot.get(this.index)); - if (++this.index >= this.chunksSnapshot.size()) { - task.get().cancel(false); + connection.write(chunksSnapshot.get(this.index)); + if (++this.index >= chunksSnapshot.size()) { + task[0].cancel(false); } } - }, 50, 50, TimeUnit.MILLISECONDS)); - + }, 50, 50, TimeUnit.MILLISECONDS); if (connection.getActiveSessionHandler() instanceof LimboSessionHandlerImpl sessionHandler) { - sessionHandler.setRespawnTask(task.get()); + sessionHandler.setRespawnTask(task[0]); } } } @@ -672,11 +626,14 @@ public void onDisconnect() { @Override public Limbo setName(String name) { this.limboName = name; - this.built = false; return this; } + public String getName() { + return this.limboName; + } + @Override public Limbo setReadTimeout(int millis) { this.readTimeout = millis; @@ -686,15 +643,13 @@ public Limbo setReadTimeout(int millis) { @Override public Limbo setWorldTime(long ticks) { this.worldTicks = ticks; - this.built = false; return this; } @Override public Limbo setGameMode(GameMode gameMode) { - this.gameMode = gameMode.getID(); - + this.gameMode = gameMode.getId(); this.built = false; return this; } @@ -702,7 +657,6 @@ public Limbo setGameMode(GameMode gameMode) { @Override public Limbo setShouldRejoin(boolean shouldRejoin) { this.shouldRejoin = shouldRejoin; - this.built = false; return this; } @@ -710,7 +664,6 @@ public Limbo setShouldRejoin(boolean shouldRejoin) { @Override public Limbo setShouldRespawn(boolean shouldRespawn) { this.shouldRespawn = shouldRespawn; - this.built = false; return this; } @@ -718,7 +671,6 @@ public Limbo setShouldRespawn(boolean shouldRespawn) { @Override public Limbo setShouldUpdateTags(boolean shouldUpdateTags) { this.shouldUpdateTags = shouldUpdateTags; - this.built = false; return this; } @@ -726,7 +678,6 @@ public Limbo setShouldUpdateTags(boolean shouldUpdateTags) { @Override public Limbo setReducedDebugInfo(boolean reducedDebugInfo) { this.reducedDebugInfo = reducedDebugInfo; - this.built = false; return this; } @@ -734,7 +685,6 @@ public Limbo setReducedDebugInfo(boolean reducedDebugInfo) { @Override public Limbo setViewDistance(int viewDistance) { this.viewDistance = viewDistance; - this.built = false; return this; } @@ -742,7 +692,6 @@ public Limbo setViewDistance(int viewDistance) { @Override public Limbo setSimulationDistance(int simulationDistance) { this.simulationDistance = simulationDistance; - this.built = false; return this; } @@ -750,27 +699,26 @@ public Limbo setSimulationDistance(int simulationDistance) { @Override public Limbo setMaxSuppressPacketLength(int maxSuppressPacketLength) { this.maxSuppressPacketLength = maxSuppressPacketLength; - return this; } @Override - public Limbo registerCommand(LimboCommandMeta commandMeta) { + public Limbo registerCommand(CommandMeta commandMeta) { return this.registerCommand(commandMeta, (SimpleCommand) invocation -> { - // Do nothing. + // Do nothing }); } @Override public Limbo registerCommand(CommandMeta commandMeta, Command command) { for (CommandRegistrar registrar : this.registrars) { - if (this.tryRegister(registrar, commandMeta, command)) { + if (LimboImpl.tryRegister(registrar, commandMeta, command)) { this.built = false; return this; } } - throw new IllegalArgumentException(command + " does not implement a registrable Command sub-interface."); + throw new IllegalArgumentException(command + " does not implement a registrable Command sub-interface"); } public Limbo registerPacket(PacketDirection direction, Class packetClass, Supplier packetSupplier, PacketMapping[] packetMappings) { @@ -780,7 +728,6 @@ public Limbo registerPacket(PacketDirection direction, Class packetClass, Sup } LimboProtocol.register(this.localStateRegistry, direction, packetClass, packetSupplier, packetMappings); - return this; } @@ -795,7 +742,6 @@ public void dispose() { private List takeSnapshot() { List packets = new ArrayList<>(); - if (this.joinPackets != null) { packets.add(this.joinPackets); } @@ -823,7 +769,6 @@ private List takeSnapshot() { if (this.configPackets != null) { packets.add(this.configPackets); } - return packets; } @@ -834,18 +779,18 @@ private void localDispose() { this.brandMessages.clear(); } - // From Velocity. - private boolean tryRegister(CommandRegistrar registrar, CommandMeta commandMeta, Command command) { + // From Velocity + private static boolean tryRegister(CommandRegistrar registrar, CommandMeta commandMeta, Command command) { Class superInterface = registrar.registrableSuperInterface(); if (superInterface.isInstance(command)) { registrar.register(commandMeta, superInterface.cast(command)); return true; - } else { - return false; } + + return false; } - private CompoundBinaryTag createRegistry(String registryName, Map tags) { + private static CompoundBinaryTag createRegistry(String registryName, Map tags) { int id = 0; ListBinaryTag.Builder builder = ListBinaryTag.builder(BinaryTagTypes.COMPOUND); @@ -854,20 +799,22 @@ private CompoundBinaryTag createRegistry(String registryName, Map= 0 ? "#minecraft:infiniburn_nether" : "minecraft:infiniburn_nether") + .putString("infiniburn", version.noLessThan(ProtocolVersion.MINECRAFT_1_18_2) ? "#minecraft:infiniburn_nether" : "minecraft:infiniburn_nether") .putDouble("coordinate_scale", 1.0) .putString("effects", dimension.getKey()) .putInt("min_y", 0) .putInt("height", 256) .putInt("monster_spawn_block_light_limit", 0) - .putInt("monster_spawn_light_level", 0) - .build(); - - if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0) { - return CompoundBinaryTag.builder() - .putString("name", dimension.getKey()) - .putInt("id", dimension.getModernID()) - .put("element", details) - .build(); - } else { - return details.putString("name", dimension.getKey()); - } + .putInt("monster_spawn_light_level", 0); + return version.noLessThan(ProtocolVersion.MINECRAFT_1_16_2) + ? CompoundBinaryTag.builder() + .putString("name", dimension.getKey()) + .putInt("id", dimension.getModernId()) + .put("element", details.build()) + .build() + : details.putString("name", dimension.getKey()).build(); } private JoinGamePacket createJoinGamePacket(ProtocolVersion version) { @@ -906,12 +849,12 @@ private JoinGamePacket createJoinGamePacket(ProtocolVersion version) { joinGame.setIsHardcore(true); joinGame.setGamemode(this.gameMode); joinGame.setPreviousGamemode((short) -1); - joinGame.setDimension(dimension.getModernID()); + joinGame.setDimension(dimension.getModernId()); joinGame.setDifficulty((short) 0); try { - PARTIAL_HASHED_SEED_FIELD.invokeExact(joinGame, ThreadLocalRandom.current().nextLong()); - } catch (Throwable e) { - throw new ReflectionException(e); + PARTIAL_HASHED_SEED_SETTER.invokeExact(joinGame, ThreadLocalRandom.current().nextLong()); + } catch (Throwable t) { + throw new ReflectionException(t); } joinGame.setMaxPlayers(1); @@ -925,30 +868,42 @@ private JoinGamePacket createJoinGamePacket(ProtocolVersion version) { String key = dimension.getKey(); joinGame.setDimensionInfo(new DimensionInfo(key, key, false, false, version)); - CompoundBinaryTag.Builder registryContainer = CompoundBinaryTag.builder(); - ListBinaryTag encodedDimensionRegistry = ListBinaryTag.builder(BinaryTagTypes.COMPOUND) - .add(this.createDimensionData(Dimension.OVERWORLD, version)) - .add(this.createDimensionData(Dimension.NETHER, version)) - .add(this.createDimensionData(Dimension.THE_END, version)) - .build(); + try { + ListBinaryTag dimensionRegistry = LimboImpl.createDimensionRegistry(version); + CompoundBinaryTag currentDimensionData = dimensionRegistry.getCompound(dimension.getModernId()); + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16_2)) { + currentDimensionData = currentDimensionData.getCompound("element"); + } + + LEVEL_NAMES_SETTER.invokeExact(joinGame, LEVELS); + REGISTRY_SETTER.invokeExact(joinGame, LimboImpl.createRegistry(version, dimensionRegistry)); + CURRENT_DIMENSION_DATA_SETTER.invokeExact(joinGame, currentDimensionData); + } catch (Throwable t) { + throw new ReflectionException(t); + } - if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0) { - CompoundBinaryTag.Builder dimensionRegistryEntry = CompoundBinaryTag.builder(); - dimensionRegistryEntry.putString("type", "minecraft:dimension_type"); - dimensionRegistryEntry.put("value", encodedDimensionRegistry); - registryContainer.put("minecraft:dimension_type", dimensionRegistryEntry.build()); - registryContainer.put("minecraft:worldgen/biome", Biome.getRegistry(version)); - if (version.compareTo(ProtocolVersion.MINECRAFT_1_19) == 0) { - registryContainer.put("minecraft:chat_type", CHAT_TYPE_119); - } else if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_1) >= 0) { - registryContainer.put("minecraft:chat_type", CHAT_TYPE_1191); + return joinGame; + } + + private static CompoundBinaryTag createRegistry(ProtocolVersion version) { + return LimboImpl.createRegistry(version, LimboImpl.createDimensionRegistry(version)); + } + + private static CompoundBinaryTag createRegistry(ProtocolVersion version, ListBinaryTag dimensionRegistry) { + CompoundBinaryTag.Builder registry = CompoundBinaryTag.builder(); + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16_2)) { + registry.put("minecraft:dimension_type", CompoundBinaryTag.builder().putString("type", "minecraft:dimension_type").put("value", dimensionRegistry).build()); + registry.put("minecraft:worldgen/biome", Biome.getRegistry(version)); + if (version == ProtocolVersion.MINECRAFT_1_19) { + registry.put("minecraft:chat_type", CHAT_TYPE_119); + } else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)) { + registry.put("minecraft:chat_type", CHAT_TYPE_1191); } // TODO: Generate mappings for damage_type registry - if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_4) == 0) { - registryContainer.put("minecraft:damage_type", DAMAGE_TYPE_1194); - } else if (version.compareTo(ProtocolVersion.MINECRAFT_1_21) >= 0) { - CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder(); + if (version == ProtocolVersion.MINECRAFT_1_19_4) { + registry.put("minecraft:damage_type", DAMAGE_TYPE_1194); + } else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21)) { ListBinaryTag values = DAMAGE_TYPE_120.getList("value"); ListBinaryTag.Builder tags = ListBinaryTag.builder(BinaryTagTypes.COMPOUND); @@ -956,174 +911,149 @@ private JoinGamePacket createJoinGamePacket(ProtocolVersion version) { tags.add((CompoundBinaryTag) tag); } - String[] types; - if (version.compareTo(ProtocolVersion.MINECRAFT_1_21_2) >= 0) { - types = new String[] { "minecraft:campfire", "minecraft:ender_pearl", "minecraft:mace_smash" }; - } else { - types = new String[] { "minecraft:campfire" }; - } - int id = values.size(); - for (String name : types) { - CompoundBinaryTag.Builder type = CompoundBinaryTag.builder() + for (String name : version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2) + ? new String[] {"minecraft:campfire", "minecraft:ender_pearl", "minecraft:mace_smash"} + : new String[] {"minecraft:campfire"}) { + tags.add(CompoundBinaryTag.builder() .putString("name", name) .putInt("id", id++) - .put("element", values.getCompound(0).getCompound("element")); - - tags.add(type.build()); + .put("element", values.getCompound(0).getCompound("element")) + .build() + ); } - registryContainer.put("minecraft:damage_type", this.createRegistry("minecraft:damage_type", tags.build())); - } else if (version.compareTo(ProtocolVersion.MINECRAFT_1_20) >= 0) { - registryContainer.put("minecraft:damage_type", DAMAGE_TYPE_120); + registry.put("minecraft:damage_type", LimboImpl.createRegistry("minecraft:damage_type", tags.build())); + } else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20)) { + registry.put("minecraft:damage_type", DAMAGE_TYPE_120); } // TODO: Auto-generate mappings and implement some APIs - if (version.compareTo(ProtocolVersion.MINECRAFT_1_21) >= 0) { - CompoundBinaryTag.Builder paintingVariant = CompoundBinaryTag.builder() - .putInt("width", 1) - .putInt("height", 1) - .putString("asset_id", "minecraft:alban"); - - registryContainer.put("minecraft:painting_variant", this.createRegistry("minecraft:painting_variant", - Map.of("minecraft:alban", paintingVariant.build()))); - - if (version.compareTo(ProtocolVersion.MINECRAFT_1_21_5) >= 0) { - // Cat - CompoundBinaryTag.Builder catVariant = CompoundBinaryTag.builder() - .putString("asset_id", "minecraft:entity/cat/all_black") - .put("spawn_conditions", ListBinaryTag.empty()); - - registryContainer.put("minecraft:cat_variant", this.createRegistry("minecraft:cat_variant", - Map.of("minecraft:all_black", catVariant.build()))); - - // Chicken - CompoundBinaryTag.Builder chickenVariant = CompoundBinaryTag.builder() - .putString("asset_id", "minecraft:entity/chicken/cold_chicken") - .putString("model", "cold") - .put("spawn_conditions", ListBinaryTag.empty()); - - registryContainer.put("minecraft:chicken_variant", this.createRegistry("minecraft:chicken_variant", - Map.of("minecraft:cold", chickenVariant.build()))); - - // Cow - CompoundBinaryTag.Builder cowVariant = CompoundBinaryTag.builder() - .putString("asset_id", "minecraft:entity/cow/cold_cow") - .putString("model", "cold") - .put("spawn_conditions", ListBinaryTag.empty()); - - registryContainer.put("minecraft:cow_variant", this.createRegistry("minecraft:cow_variant", - Map.of("minecraft:cold", cowVariant.build()))); - - // Frog - CompoundBinaryTag.Builder frogVariant = CompoundBinaryTag.builder() - .putString("asset_id", "minecraft:entity/frog/cold_frog") - .put("spawn_conditions", ListBinaryTag.empty()); - - registryContainer.put("minecraft:frog_variant", this.createRegistry("minecraft:frog_variant", - Map.of("minecraft:cold", frogVariant.build()))); - - // Pig - CompoundBinaryTag.Builder pigVariant = CompoundBinaryTag.builder() - .putString("asset_id", "minecraft:entity/pig/cold_pig") - .putString("model", "cold") - .put("spawn_conditions", ListBinaryTag.empty()); - - registryContainer.put("minecraft:pig_variant", this.createRegistry("minecraft:pig_variant", - Map.of("minecraft:cold", pigVariant.build()))); - - // Wolf Sound Variant - CompoundBinaryTag.Builder wolfSoundVariant = CompoundBinaryTag.builder() - .putString("ambient_sound", "minecraft:entity.wolf_angry.ambient") - .putString("death_sound", "minecraft:entity.wolf_angry.death") - .putString("growl_sound", "minecraft:entity.wolf_angry.growl") - .putString("hurt_sound", "minecraft:entity.wolf_angry.hurt") - .putString("pant_sound", "minecraft:entity.wolf_angry.pant") - .putString("whine_sound", "minecraft:entity.wolf_angry.whine"); - - registryContainer.put("minecraft:wolf_sound_variant", this.createRegistry("minecraft:wolf_sound_variant", - Map.of("minecraft:angry", wolfSoundVariant.build()))); - - // Wolf - CompoundBinaryTag.Builder wolfVariant = CompoundBinaryTag.builder() - .put("assets", CompoundBinaryTag.builder() - .putString("wild", "minecraft:entity/wolf/wolf_ashen") - .putString("tame", "minecraft:entity/wolf/wolf_ashen_tame") - .putString("angry", "minecraft:entity/wolf/wolf_ashen_angry") - .build()) - .put("spawn_conditions", ListBinaryTag.empty()); - - registryContainer.put("minecraft:wolf_variant", this.createRegistry("minecraft:wolf_variant", - Map.of("minecraft:ashen", wolfVariant.build()))); + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) { + // TODO мб ещё сюда и варианты для баннеров добавлять (если их в регистре не отправить, то они пустыми будут) + if (version.lessThan(ProtocolVersion.MINECRAFT_1_21_5)) { + registry.put("minecraft:wolf_variant", LimboImpl.createRegistry("minecraft:wolf_variant", Collections.singletonMap( + "minecraft:ashen", CompoundBinaryTag.builder() + .putString("wild_texture", "minecraft:entity/wolf/wolf_ashen") + .putString("tame_texture", "minecraft:entity/wolf/wolf_ashen_tame") + .putString("angry_texture", "minecraft:entity/wolf/wolf_ashen_angry") + .put("biomes", ListBinaryTag.builder().add(StringBinaryTag.stringBinaryTag("minecraft:plains")).build()) + .build() + ))); } else { - CompoundBinaryTag.Builder wolfVariant = CompoundBinaryTag.builder() - .putString("wild_texture", "minecraft:entity/wolf/wolf_ashen") - .putString("tame_texture", "minecraft:entity/wolf/wolf_ashen_tame") - .putString("angry_texture", "minecraft:entity/wolf/wolf_ashen_angry") - .put("biomes", ListBinaryTag.builder() - .add(StringBinaryTag.stringBinaryTag("minecraft:plains")).build() - ); - - registryContainer.put("minecraft:wolf_variant", this.createRegistry("minecraft:wolf_variant", - Map.of("minecraft:ashen", wolfVariant.build()))); + registry.put("minecraft:wolf_variant", LimboImpl.createRegistry("minecraft:wolf_variant", Collections.singletonMap( + "minecraft:ashen", CompoundBinaryTag.builder() + .put("assets", CompoundBinaryTag.builder() + .putString("wild", "minecraft:entity/wolf/wolf_ashen") + .putString("tame", "minecraft:entity/wolf/wolf_ashen_tame") + .putString("angry", "minecraft:entity/wolf/wolf_ashen_angry") + .build() + ).put("spawn_conditions", ListBinaryTag.empty()) + .build() + ))); + registry.put("minecraft:wolf_sound_variant", LimboImpl.createRegistry("minecraft:wolf_sound_variant", Collections.singletonMap( + "minecraft:angry", CompoundBinaryTag.builder() + .putString("ambient_sound", "minecraft:entity.wolf_angry.ambient") + .putString("death_sound", "minecraft:entity.wolf_angry.death") + .putString("growl_sound", "minecraft:entity.wolf_angry.growl") + .putString("hurt_sound", "minecraft:entity.wolf_angry.hurt") + .putString("pant_sound", "minecraft:entity.wolf_angry.pant") + .putString("whine_sound", "minecraft:entity.wolf_angry.whine") + .build() + ))); + registry.put("minecraft:cat_variant", LimboImpl.createRegistry("minecraft:cat_variant", Collections.singletonMap( + "minecraft:all_black", CompoundBinaryTag.builder() + .putString("asset_id", "minecraft:entity/cat/all_black") + .put("spawn_conditions", ListBinaryTag.empty()) + .build() + ))); + registry.put("minecraft:chicken_variant", LimboImpl.createRegistry("minecraft:chicken_variant", Collections.singletonMap( + "minecraft:cold", CompoundBinaryTag.builder() + .putString("asset_id", "minecraft:entity/chicken/cold_chicken") + .putString("model", "cold") + .put("spawn_conditions", ListBinaryTag.empty()) + .build() + ))); + registry.put("minecraft:cow_variant", LimboImpl.createRegistry("minecraft:cow_variant", Collections.singletonMap( + "minecraft:cold", CompoundBinaryTag.builder() + .putString("asset_id", "minecraft:entity/cow/cold_cow") + .putString("model", "cold") + .put("spawn_conditions", ListBinaryTag.empty()) + .build() + ))); + registry.put("minecraft:frog_variant", LimboImpl.createRegistry("minecraft:frog_variant", Collections.singletonMap( + "minecraft:cold", CompoundBinaryTag.builder() + .putString("asset_id", "minecraft:entity/frog/cold_frog") + .put("spawn_conditions", ListBinaryTag.empty()) + .build() + ))); + registry.put("minecraft:pig_variant", LimboImpl.createRegistry("minecraft:pig_variant", Collections.singletonMap( + "minecraft:cold", CompoundBinaryTag.builder() + .putString("asset_id", "minecraft:entity/pig/cold_pig") + .putString("model", "cold") + .put("spawn_conditions", ListBinaryTag.empty()) + .build() + ))); } } + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21)) { + registry.put("minecraft:painting_variant", LimboImpl.createRegistry("minecraft:painting_variant", Collections.singletonMap( + "minecraft:alban", CompoundBinaryTag.builder() + .putInt("width", 1) + .putInt("height", 1) + .putString("asset_id", "minecraft:alban") + .build() + ))); + } } else { - registryContainer.put("dimension", encodedDimensionRegistry); + registry.put("dimension", dimensionRegistry); } - try { - CompoundBinaryTag currentDimensionData = encodedDimensionRegistry.getCompound(dimension.getModernID()); - if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0) { - currentDimensionData = currentDimensionData.getCompound("element"); - } - - CURRENT_DIMENSION_DATA_FIELD.invokeExact(joinGame, currentDimensionData); - LEVEL_NAMES_FIELDS.invokeExact(joinGame, LEVELS); - REGISTRY_FIELD.invokeExact(joinGame, registryContainer.build()); - } catch (Throwable e) { - throw new ReflectionException(e); - } + return registry.build(); + } - return joinGame; + private static ListBinaryTag createDimensionRegistry(ProtocolVersion version) { + return ListBinaryTag.builder(BinaryTagTypes.COMPOUND) + .add(LimboImpl.createDimensionData(Dimension.OVERWORLD, version)) + .add(LimboImpl.createDimensionData(Dimension.NETHER, version)) + .add(LimboImpl.createDimensionData(Dimension.THE_END, version)) + .build(); } private JoinGamePacket createLegacyJoinGamePacket() { JoinGamePacket joinGame = this.createJoinGamePacket(ProtocolVersion.MINIMUM_VERSION); - joinGame.setDimension(this.world.getDimension().getLegacyID()); + joinGame.setDimension(this.world.getDimension().getLegacyId()); return joinGame; } private DefaultSpawnPositionPacket createDefaultSpawnPositionPacket() { - return new DefaultSpawnPositionPacket((int) this.world.getSpawnX(), (int) this.world.getSpawnY(), (int) this.world.getSpawnZ(), 0.0F); + return new DefaultSpawnPositionPacket(this.world.getDimension().getKey(), (int) this.world.getSpawnX(), (int) this.world.getSpawnY(), (int) this.world.getSpawnZ(), 0.0F, 0.0F); } - private TimeUpdatePacket createWorldTicksPacket() { - return this.worldTicks == null ? null : new TimeUpdatePacket(this.worldTicks, this.worldTicks); + private SetTimePacket createWorldTicksPacket() { + return this.worldTicks == null ? null : new SetTimePacket(this.worldTicks, this.worldTicks); } private AvailableCommandsPacket createAvailableCommandsPacket() { try { AvailableCommandsPacket packet = new AvailableCommandsPacket(); - ROOT_NODE_FIELD.invokeExact(packet, this.commandNode); + ROOT_NODE_SETTER.invokeExact(packet, this.commandNode); return packet; - } catch (Throwable e) { - throw new ReflectionException(e); + } catch (Throwable t) { + throw new ReflectionException(t); } } private PreparedPacket createFirstChunks() { PreparedPacket packet = this.plugin.createPreparedPacket(); - List> orderedChunks = this.world.getOrderedChunks(); - int chunkCounter = 0; - for (List chunksWithSameDistance : orderedChunks) { + for (List chunksWithSameDistance : this.world.getOrderedChunks()) { if (++chunkCounter > Settings.IMP.MAIN.CHUNK_RADIUS_SEND_ON_SPAWN) { break; } for (VirtualChunk chunk : chunksWithSameDistance) { - packet.prepare(this.createChunkData(chunk, this.world.getDimension())); + this.plugin.getPacketFactory().prepareCompleteChunkDataPacket(this.plugin.getPrepareMinVersion(), this.plugin.getPrepareMaxVersion(), packet, chunk.createSnapshot(true), this.world.getDimension()); } } @@ -1131,17 +1061,17 @@ private PreparedPacket createFirstChunks() { } private List createDelayedChunksPackets() { - List> orderedChunks = this.world.getOrderedChunks(); + var orderedChunks = this.world.getOrderedChunks(); if (orderedChunks.size() <= Settings.IMP.MAIN.CHUNK_RADIUS_SEND_ON_SPAWN) { - return List.of(); + return Collections.emptyList(); } List packets = new LinkedList<>(); PreparedPacket packet = this.plugin.createPreparedPacket(); int chunkCounter = 0; - Iterator> distanceIterator = orderedChunks.listIterator(); - for (int i = 0; i < Settings.IMP.MAIN.CHUNK_RADIUS_SEND_ON_SPAWN; i++) { + var distanceIterator = orderedChunks.listIterator(); + for (int i = 0; i < Settings.IMP.MAIN.CHUNK_RADIUS_SEND_ON_SPAWN; ++i) { distanceIterator.next(); } @@ -1153,17 +1083,16 @@ private List createDelayedChunksPackets() { chunkCounter = 0; } - packet.prepare(this.createChunkData(chunk, this.world.getDimension())); + this.plugin.getPacketFactory().prepareCompleteChunkDataPacket(this.plugin.getPrepareMinVersion(), this.plugin.getPrepareMaxVersion(), packet, chunk.createSnapshot(true), this.world.getDimension()); } } packets.add(packet.build()); - return packets; } - // From Velocity. - private List createFastClientServerSwitch(JoinGamePacket joinGame, ProtocolVersion version) { + // From Velocity + private static List createFastClientServerSwitch(JoinGamePacket joinGame, ProtocolVersion version) { // In order to handle switching to another server, you will need to send two packets: // // - The join game packet from the backend server, with a different dimension. @@ -1172,24 +1101,18 @@ private List createFastClientServerSwitch(JoinGamePacket joinGa // Most notably, by having the client accept the join game packet, we can work around the need // to perform entity ID rewrites, eliminating potential issues from rewriting packets and // improving compatibility with mods. - List packets = new ArrayList<>(); - RespawnPacket respawn = RespawnPacket.fromJoinGame(joinGame); - - if (version.compareTo(ProtocolVersion.MINECRAFT_1_16) < 0) { + if (version.lessThan(ProtocolVersion.MINECRAFT_1_16)) { // Before Minecraft 1.16, we could not switch to the same dimension without sending an // additional respawn. On older versions of Minecraft this forces the client to perform // garbage collection which adds additional latency. joinGame.setDimension(joinGame.getDimension() == 0 ? -1 : 0); } - packets.add(joinGame); - packets.add(respawn); - - return packets; + return List.of(joinGame, respawn); } - private List createSafeClientServerSwitch(JoinGamePacket joinGame) { + private static List createSafeClientServerSwitch(JoinGamePacket joinGame) { // Some clients do not behave well with the "fast" respawn sequence. In this case we will use // a "safe" respawn sequence that involves sending three packets to the client. They have the // same effect but tend to work better with buggier clients (Forge 1.8 in particular). @@ -1204,8 +1127,7 @@ private List createSafeClientServerSwitch(JoinGamePacket joinGa packets.add(fakeSwitchPacket); // Now send a respawn packet in the correct dimension. - RespawnPacket correctSwitchPacket = RespawnPacket.fromJoinGame(joinGame); - packets.add(correctSwitchPacket); + packets.add(RespawnPacket.fromJoinGame(joinGame)); return packets; } @@ -1221,26 +1143,22 @@ private PreparedPacket getBrandMessage(Class claz private PluginMessagePacket createBrandMessage(ProtocolVersion version) { String brand = "LimboAPI (" + Settings.IMP.VERSION + ") -> " + this.limboName; - ByteBuf bufWithBrandString = Unpooled.buffer(); - if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) < 0) { - bufWithBrandString.writeCharSequence(brand, StandardCharsets.UTF_8); + ByteBuf buf = Unpooled.buffer(2 + brand.length()); + if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_7_6)) { + buf.writeCharSequence(brand, StandardCharsets.UTF_8); } else { - ProtocolUtils.writeString(bufWithBrandString, brand); + ProtocolUtils.writeString(buf, brand); } - return new PluginMessagePacket("MC|Brand", bufWithBrandString); - } - - private PositionRotationPacket createPlayerPosAndLook(double posX, double posY, double posZ, float yaw, float pitch) { - return new PositionRotationPacket(posX, posY, posZ, yaw, pitch, false, 44, true); + return new PluginMessagePacket("MC|Brand", buf); } - private UpdateViewPositionPacket createUpdateViewPosition(int posX, int posZ) { - return new UpdateViewPositionPacket(posX >> 4, posZ >> 4); + private static PlayerPositionPacket createPlayerPosition(double posX, double posY, double posZ, float yaw, float pitch) { + return new PlayerPositionPacket(posX, posY, posZ, yaw, pitch, false, 44, true); } - private ChunkDataPacket createChunkData(VirtualChunk chunk, Dimension dimension) { - return new ChunkDataPacket(chunk.getFullChunkSnapshot(), dimension.hasLegacySkyLight(), dimension.getMaxSections()); + private static SetChunkCacheCenterPacket createSetChunkCacheCenter(int posX, int posZ) { + return new SetChunkCacheCenterPacket(posX >> 4, posZ >> 4); } public Integer getReadTimeout() { @@ -1249,32 +1167,19 @@ public Integer getReadTimeout() { static { try { - PARTIAL_HASHED_SEED_FIELD = MethodHandles.privateLookupIn(JoinGamePacket.class, MethodHandles.lookup()) - .findSetter(JoinGamePacket.class, "partialHashedSeed", long.class); - CURRENT_DIMENSION_DATA_FIELD = MethodHandles.privateLookupIn(JoinGamePacket.class, MethodHandles.lookup()) - .findSetter(JoinGamePacket.class, "currentDimensionData", CompoundBinaryTag.class); - ROOT_NODE_FIELD = MethodHandles.privateLookupIn(AvailableCommandsPacket.class, MethodHandles.lookup()) - .findSetter(AvailableCommandsPacket.class, "rootNode", RootCommandNode.class); - GRACEFUL_DISCONNECT_FIELD = MethodHandles.privateLookupIn(VelocityServerConnection.class, MethodHandles.lookup()) - .findSetter(VelocityServerConnection.class, "gracefulDisconnect", boolean.class); - REGISTRY_FIELD = MethodHandles.privateLookupIn(JoinGamePacket.class, MethodHandles.lookup()) - .findSetter(JoinGamePacket.class, "registry", CompoundBinaryTag.class); - LEVEL_NAMES_FIELDS = MethodHandles.privateLookupIn(JoinGamePacket.class, MethodHandles.lookup()) - .findSetter(JoinGamePacket.class, "levelNames", ImmutableSet.class); - try (InputStream stream = LimboAPI.class.getResourceAsStream("/mapping/chat_type_1_19.nbt")) { - CHAT_TYPE_119 = BinaryTagIO.unlimitedReader().read(Objects.requireNonNull(stream), BinaryTagIO.Compression.GZIP); + BinaryTagIO.Reader reader = BinaryTagIO.unlimitedReader(); + try (InputStream stream = LimboAPI.class.getResourceAsStream("/mappings/chat_type_1_19.nbt")) { + CHAT_TYPE_119 = reader.read(Objects.requireNonNull(stream), BinaryTagIO.Compression.GZIP); } - try (InputStream stream = LimboAPI.class.getResourceAsStream("/mapping/chat_type_1_19_1.nbt")) { - CHAT_TYPE_1191 = BinaryTagIO.unlimitedReader().read(Objects.requireNonNull(stream), BinaryTagIO.Compression.GZIP); + try (InputStream stream = LimboAPI.class.getResourceAsStream("/mappings/chat_type_1_19_1.nbt")) { + CHAT_TYPE_1191 = reader.read(Objects.requireNonNull(stream), BinaryTagIO.Compression.GZIP); } - try (InputStream stream = LimboAPI.class.getResourceAsStream("/mapping/damage_type_1_19_4.nbt")) { - DAMAGE_TYPE_1194 = BinaryTagIO.unlimitedReader().read(Objects.requireNonNull(stream), BinaryTagIO.Compression.GZIP); + try (InputStream stream = LimboAPI.class.getResourceAsStream("/mappings/damage_type_1_19_4.nbt")) { + DAMAGE_TYPE_1194 = reader.read(Objects.requireNonNull(stream), BinaryTagIO.Compression.GZIP); } - try (InputStream stream = LimboAPI.class.getResourceAsStream("/mapping/damage_type_1_20.nbt")) { - DAMAGE_TYPE_120 = BinaryTagIO.unlimitedReader().read(Objects.requireNonNull(stream), BinaryTagIO.Compression.GZIP); + try (InputStream stream = LimboAPI.class.getResourceAsStream("/mappings/damage_type_1_20.nbt")) { + DAMAGE_TYPE_120 = reader.read(Objects.requireNonNull(stream), BinaryTagIO.Compression.GZIP); } - } catch (NoSuchFieldException | IllegalAccessException e) { - throw new ReflectionException(e); } catch (IOException e) { throw new IllegalStateException(e); } diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/LimboPlayerImpl.java b/plugin/src/main/java/net/elytrium/limboapi/server/LimboPlayerImpl.java index 33e79279..b65d38d5 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/server/LimboPlayerImpl.java +++ b/plugin/src/main/java/net/elytrium/limboapi/server/LimboPlayerImpl.java @@ -29,30 +29,34 @@ import java.awt.Graphics2D; import java.awt.Image; import java.awt.image.BufferedImage; +import java.util.Collections; import java.util.EnumSet; -import java.util.List; import java.util.UUID; import java.util.concurrent.ScheduledExecutorService; +import net.elytrium.commons.utils.reflection.ReflectionException; import net.elytrium.limboapi.LimboAPI; import net.elytrium.limboapi.api.Limbo; -import net.elytrium.limboapi.api.material.Item; -import net.elytrium.limboapi.api.material.VirtualItem; -import net.elytrium.limboapi.api.player.GameMode; -import net.elytrium.limboapi.api.player.LimboPlayer; -import net.elytrium.limboapi.api.protocol.item.ItemComponentMap; -import net.elytrium.limboapi.api.protocol.packets.data.AbilityFlags; -import net.elytrium.limboapi.api.protocol.packets.data.MapData; -import net.elytrium.limboapi.api.protocol.packets.data.MapPalette; +import net.elytrium.limboapi.api.protocol.data.EntityData; +import net.elytrium.limboapi.api.world.item.Item; +import net.elytrium.limboapi.api.world.item.VirtualItem; +import net.elytrium.limboapi.api.world.player.GameMode; +import net.elytrium.limboapi.api.world.player.LimboPlayer; +import net.elytrium.limboapi.api.world.item.datacomponent.DataComponentMap; +import net.elytrium.limboapi.api.protocol.data.AbilityFlags; +import net.elytrium.limboapi.api.protocol.data.MapData; +import net.elytrium.limboapi.api.protocol.data.MapPalette; import net.elytrium.limboapi.injection.login.LoginTasksQueue; -import net.elytrium.limboapi.protocol.packets.s2c.ChangeGameStatePacket; +import net.elytrium.limboapi.protocol.packets.s2c.GameEventPacket; import net.elytrium.limboapi.protocol.packets.s2c.MapDataPacket; import net.elytrium.limboapi.protocol.packets.s2c.PlayerAbilitiesPacket; -import net.elytrium.limboapi.protocol.packets.s2c.PositionRotationPacket; +import net.elytrium.limboapi.protocol.packets.s2c.PlayerPositionPacket; +import net.elytrium.limboapi.protocol.packets.s2c.SetEntityDataPacket; import net.elytrium.limboapi.protocol.packets.s2c.SetSlotPacket; -import net.elytrium.limboapi.protocol.packets.s2c.TimeUpdatePacket; +import net.elytrium.limboapi.protocol.packets.s2c.SetTimePacket; import net.elytrium.limboapi.server.world.SimpleItem; import net.kyori.adventure.nbt.CompoundBinaryTag; import net.kyori.adventure.nbt.IntBinaryTag; +import org.checkerframework.checker.nullness.qual.Nullable; public class LimboPlayerImpl implements LimboPlayer { @@ -61,7 +65,6 @@ public class LimboPlayerImpl implements LimboPlayer { private final ConnectedPlayer player; private final MinecraftConnection connection; private final LimboSessionHandlerImpl sessionHandler; - private final ProtocolVersion version; private GameMode gameMode = GameMode.ADVENTURE; @@ -72,17 +75,16 @@ public LimboPlayerImpl(LimboAPI plugin, LimboImpl server, ConnectedPlayer player this.connection = this.player.getConnection(); this.sessionHandler = (LimboSessionHandlerImpl) this.connection.getActiveSessionHandler(); - this.version = this.player.getProtocolVersion(); } @Override - public void writePacket(Object packetObj) { - this.connection.delayedWrite(packetObj); + public void writePacket(Object msg) { + this.connection.delayedWrite(msg); } @Override - public void writePacketAndFlush(Object packetObj) { - this.connection.write(packetObj); + public void writePacketAndFlush(Object msg) { + this.connection.write(msg); } @Override @@ -91,8 +93,8 @@ public void flushPackets() { } @Override - public void closeWith(Object packetObj) { - this.connection.closeWith(packetObj); + public void closeWith(Object msg) { + this.connection.closeWith(msg); } @Override @@ -101,36 +103,17 @@ public ScheduledExecutorService getScheduledExecutor() { } @Override - public void sendImage(BufferedImage image) { - this.sendImage(0, image, true, true); - } - - @Override - public void sendImage(BufferedImage image, boolean sendItem) { - this.sendImage(0, image, sendItem, true); - } - - @Override - public void sendImage(int mapID, BufferedImage image) { - this.sendImage(mapID, image, true, true); - } - - @Override - public void sendImage(int mapID, BufferedImage image, boolean sendItem) { - this.sendImage(mapID, image, sendItem, true); - } - - @Override - public void sendImage(int mapID, BufferedImage image, boolean sendItem, boolean resize) { + public void sendImage(int mapId, BufferedImage image, boolean sendItem, int itemSlot, boolean resize) { + ProtocolVersion version = this.player.getProtocolVersion(); if (sendItem) { - this.setInventory( - 36, + // TODO check 1.16.5 and 1.20.6 + this.setItem( + itemSlot, SimpleItem.fromItem(Item.FILLED_MAP), 1, - mapID, - this.version.compareTo(ProtocolVersion.MINECRAFT_1_17) < 0 - ? null - : CompoundBinaryTag.builder().put("map", IntBinaryTag.intBinaryTag(mapID)).build() + (short) mapId, + version.noGreaterThan(ProtocolVersion.MINECRAFT_1_16_4) ? null : CompoundBinaryTag.builder().put("map", IntBinaryTag.intBinaryTag(mapId)).build(), + null ); } @@ -145,21 +128,20 @@ public void sendImage(int mapID, BufferedImage image, boolean sendItem, boolean image = resizedImage; } else { throw new IllegalStateException( - "You either need to provide an image of " + MapData.MAP_DIM_SIZE + "x" + MapData.MAP_DIM_SIZE - + " pixels or set the resize parameter to true so that API will automatically resize your image." + "You either need to provide an image of " + MapData.MAP_DIM_SIZE + "x" + MapData.MAP_DIM_SIZE + " pixels or set the resize parameter to true so that API will automatically resize your image" ); } } - int[] toWrite = MapPalette.imageToBytes(image, this.version); - if (this.version.compareTo(ProtocolVersion.MINECRAFT_1_8) < 0) { + int[] toWrite = MapPalette.imageToBytes(image, version); + if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_7_6)) { byte[][] canvas = new byte[MapData.MAP_DIM_SIZE][MapData.MAP_DIM_SIZE]; for (int i = 0; i < MapData.MAP_SIZE; ++i) { - canvas[i & 127][i >> 7] = (byte) toWrite[i]; + canvas[i & 0x7F][i >> 7] = (byte) toWrite[i]; } for (int i = 0; i < MapData.MAP_DIM_SIZE; ++i) { - this.writePacket(new MapDataPacket(mapID, (byte) 0, new MapData(i, canvas[i]))); + this.writePacket(new MapDataPacket(mapId, (byte) 0, new MapData(i, canvas[i]))); } this.flushPackets(); @@ -169,74 +151,96 @@ public void sendImage(int mapID, BufferedImage image, boolean sendItem, boolean canvas[i] = (byte) toWrite[i]; } - this.writePacketAndFlush(new MapDataPacket(mapID, (byte) 0, new MapData(canvas))); + this.writePacketAndFlush(new MapDataPacket(mapId, (byte) 0, new MapData(canvas))); } } @Override - public void setInventory(VirtualItem item, int count) { - this.writePacketAndFlush(new SetSlotPacket(0, 36, item, count, 0, null, null)); + public void setItemInMainHand(VirtualItem item, int count) { + this.writePacketAndFlush(new SetSlotPacket(0, 36, item, count)); + } + + @Override + public void setItemInOffHand(VirtualItem item, int count) { + this.writePacketAndFlush(new SetSlotPacket(0, 45, item, count)); + } + + @Override + public void setItem(int slot, VirtualItem item, int count) { + this.writePacketAndFlush(new SetSlotPacket(0, slot, item, count)); + } + + @Override + public void setItem(int slot, VirtualItem item, int count, CompoundBinaryTag nbt) { + this.writePacketAndFlush(new SetSlotPacket(0, slot, item, count, nbt)); + } + + @Override + public void setItem(int slot, VirtualItem item, int count, DataComponentMap map) { + this.writePacketAndFlush(new SetSlotPacket(0, slot, item, count, map)); + } + + @Override + public void setItem(int slot, VirtualItem item, int count, short data) { + this.writePacketAndFlush(new SetSlotPacket(0, slot, item, count, data)); } @Override - public void setInventory(VirtualItem item, int slot, int count) { - this.writePacketAndFlush(new SetSlotPacket(0, slot, item, count, 0, null, null)); + public void setItem(int slot, VirtualItem item, int count, short data, @Nullable CompoundBinaryTag nbt) { + this.writePacketAndFlush(new SetSlotPacket(0, slot, item, count, data, nbt)); } @Override - public void setInventory(int slot, VirtualItem item, int count, int data, CompoundBinaryTag nbt) { - this.writePacketAndFlush(new SetSlotPacket(0, slot, item, count, data, nbt, null)); + public void setItem(int slot, VirtualItem item, int count, short data, @Nullable DataComponentMap map) { + this.writePacketAndFlush(new SetSlotPacket(0, slot, item, count, data, map)); } @Override - public void setInventory(int slot, VirtualItem item, int count, int data, ItemComponentMap map) { - this.writePacketAndFlush(new SetSlotPacket(0, slot, item, count, data, null, map)); + public void setItem(int slot, VirtualItem item, int count, short data, CompoundBinaryTag nbt, DataComponentMap map) { + this.writePacketAndFlush(new SetSlotPacket(0, slot, item, count, data, nbt, map)); } @Override public void setGameMode(GameMode gameMode) { - boolean is17 = this.version.compareTo(ProtocolVersion.MINECRAFT_1_8) < 0; - if (gameMode != GameMode.SPECTATOR || !is17) { // Spectator game mode was added in 1.8. + ProtocolVersion version = this.player.getProtocolVersion(); + boolean is17 = version.noGreaterThan(ProtocolVersion.MINECRAFT_1_7_6); + if (gameMode != GameMode.SPECTATOR || !is17) { // Spectator game mode was added in 1.8 this.gameMode = gameMode; - - int id = this.gameMode.getID(); - this.sendAbilities(); + int id = gameMode.getId(); + this.sendGameModeSpecificAbilities(); if (!is17) { - UUID uuid = this.plugin.getInitialID(this.player); - if (this.connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19_1) <= 0) { - this.writePacket( - new LegacyPlayerListItemPacket(LegacyPlayerListItemPacket.UPDATE_GAMEMODE, - List.of( - new LegacyPlayerListItemPacket.Item(uuid).setGameMode(id) - ) - ) - ); + UUID uuid = LimboAPI.getClientUniqueId(this.player); + if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_19_1)) { + this.writePacket(new LegacyPlayerListItemPacket(LegacyPlayerListItemPacket.UPDATE_GAMEMODE, Collections.singletonList(new LegacyPlayerListItemPacket.Item(uuid).setGameMode(id)))); } else { UpsertPlayerInfoPacket.Entry playerInfoEntry = new UpsertPlayerInfoPacket.Entry(uuid); playerInfoEntry.setGameMode(id); - - this.writePacket(new UpsertPlayerInfoPacket(EnumSet.of(UpsertPlayerInfoPacket.Action.UPDATE_GAME_MODE), List.of(playerInfoEntry))); + this.writePacket(new UpsertPlayerInfoPacket(EnumSet.of(UpsertPlayerInfoPacket.Action.UPDATE_GAME_MODE), Collections.singletonList(playerInfoEntry))); } } - this.writePacket(new ChangeGameStatePacket(3, id)); - + this.writePacket(new GameEventPacket(3, id)); // CHANGE_GAME_MODE this.flushPackets(); } } + @Override + public void setWorldTime(long ticks) { + this.writePacketAndFlush(new SetTimePacket(ticks, ticks)); + } + @Override public void teleport(double posX, double posY, double posZ, float yaw, float pitch) { - this.writePacketAndFlush(new PositionRotationPacket(posX, posY, posZ, yaw, pitch, false, 44, true)); + this.writePacketAndFlush(new PlayerPositionPacket(posX, posY, posZ, yaw, pitch, false, 44, true)); } @Override public void disableFalling() { - this.writePacketAndFlush(new PlayerAbilitiesPacket((byte) (this.getAbilities() | AbilityFlags.FLYING | AbilityFlags.ALLOW_FLYING), 0F, 0F)); + this.writePacketAndFlush(new PlayerAbilitiesPacket((byte) (this.getGameModeSpecificAbilities() | AbilityFlags.FLYING | AbilityFlags.ALLOW_FLYING), 0F, 0F)); } @Override public void enableFalling() { - this.writePacketAndFlush(new PlayerAbilitiesPacket((byte) (this.getAbilities() & (~AbilityFlags.FLYING)), 0.05F, 0.1F)); + this.writePacketAndFlush(new PlayerAbilitiesPacket((byte) (this.getGameModeSpecificAbilities() & (~AbilityFlags.FLYING)), 0.05F, 0.1F)); } @Override @@ -245,7 +249,7 @@ public void disconnect() { if (this.connection.getActiveSessionHandler() == this.sessionHandler) { this.sessionHandler.disconnect(() -> { if (this.plugin.hasLoginQueue(this.player)) { - if (this.connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) >= 0) { + if (this.player.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) { this.sessionHandler.disconnectToConfig(() -> this.plugin.getLoginQueue(this.player).next()); } else { this.sessionHandler.disconnected(); @@ -270,7 +274,7 @@ public void disconnect(RegisteredServer server) { if (this.connection.getActiveSessionHandler() == this.sessionHandler) { this.sessionHandler.disconnect(() -> { if (this.plugin.hasLoginQueue(this.player)) { - if (this.connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) >= 0) { + if (this.player.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) { this.sessionHandler.disconnectToConfig(() -> { this.plugin.setNextServer(this.player, server); this.plugin.getLoginQueue(this.player).next(); @@ -289,27 +293,33 @@ public void disconnect(RegisteredServer server) { } private void deject() { - this.plugin.deject3rdParty(this.connection.getChannel().pipeline()); - this.plugin.fixCompressor(this.connection.getChannel().pipeline(), this.version); + var pipeline = this.connection.getChannel().pipeline(); + this.plugin.deject3rdParty(pipeline); + this.plugin.fixCompressor(pipeline); } private void sendToRegisteredServer(RegisteredServer server) { this.deject(); this.connection.setState(StateRegistry.PLAY); - if (this.connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) >= 0) { + ProtocolVersion version = this.player.getProtocolVersion(); + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) { this.sessionHandler.disconnectToConfig(() -> { // Rollback original CONFIG handler ClientConfigSessionHandler handler = new ClientConfigSessionHandler(this.plugin.getServer(), this.player); - LoginTasksQueue.BRAND_CHANNEL_SETTER.accept(handler, "minecraft:brand"); + try { + LoginTasksQueue.BRAND_CHANNEL_SETTER.invokeExact(handler, "minecraft:brand"); + } catch (Throwable t) { + throw new ReflectionException(t); + } this.connection.setActiveSessionHandler(StateRegistry.CONFIG, handler); this.player.createConnectionRequest(server).fireAndForget(); }); } else { - if (this.connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_19_1) <= 0) { + if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_19_1)) { this.connection.delayedWrite(new LegacyPlayerListItemPacket( LegacyPlayerListItemPacket.REMOVE_PLAYER, - List.of(new LegacyPlayerListItemPacket.Item(this.plugin.getInitialID(this.player))) + Collections.singletonList(new LegacyPlayerListItemPacket.Item(LimboAPI.getClientUniqueId(this.player))) )); } @@ -319,22 +329,27 @@ private void sendToRegisteredServer(RegisteredServer server) { } @Override - public void sendAbilities() { - this.writePacketAndFlush(new PlayerAbilitiesPacket(this.getAbilities(), 0.05F, 0.1F)); + public void setEntityData(int id, EntityData data) { + this.writePacketAndFlush(new SetEntityDataPacket(id, data)); + } + + @Override + public void sendGameModeSpecificAbilities() { + this.writePacketAndFlush(new PlayerAbilitiesPacket(this.getGameModeSpecificAbilities(), 0.05F, 0.1F)); } @Override - public void sendAbilities(int abilities, float flySpeed, float walkSpeed) { - this.writePacketAndFlush(new PlayerAbilitiesPacket((byte) abilities, flySpeed, walkSpeed)); + public void sendAbilities(int abilities, float flyingSpeed, float walkingSpeed) { + this.writePacketAndFlush(new PlayerAbilitiesPacket((byte) abilities, flyingSpeed, walkingSpeed)); } @Override - public void sendAbilities(byte abilities, float flySpeed, float walkSpeed) { - this.writePacketAndFlush(new PlayerAbilitiesPacket(abilities, flySpeed, walkSpeed)); + public void sendAbilities(byte abilities, float flyingSpeed, float walkingSpeed) { + this.writePacketAndFlush(new PlayerAbilitiesPacket(abilities, flyingSpeed, walkingSpeed)); } @Override - public byte getAbilities() { + public byte getGameModeSpecificAbilities() { return switch (this.gameMode) { case CREATIVE -> AbilityFlags.ALLOW_FLYING | AbilityFlags.CREATIVE_MODE | AbilityFlags.INVULNERABLE; case SPECTATOR -> AbilityFlags.ALLOW_FLYING | AbilityFlags.INVULNERABLE | AbilityFlags.FLYING; @@ -360,15 +375,6 @@ public Player getProxyPlayer() { @Override public int getPing() { LimboSessionHandlerImpl handler = (LimboSessionHandlerImpl) this.connection.getActiveSessionHandler(); - if (handler != null) { - return handler.getPing(); - } else { - return -1; - } - } - - @Override - public void setWorldTime(long ticks) { - this.writePacketAndFlush(new TimeUpdatePacket(ticks, ticks)); + return handler == null ? -1 : handler.getPing(); } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/LimboSessionHandlerImpl.java b/plugin/src/main/java/net/elytrium/limboapi/server/LimboSessionHandlerImpl.java index ea8e668e..8b20c89b 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/server/LimboSessionHandlerImpl.java +++ b/plugin/src/main/java/net/elytrium/limboapi/server/LimboSessionHandlerImpl.java @@ -45,30 +45,29 @@ import io.netty.channel.ChannelPipeline; import io.netty.handler.timeout.ReadTimeoutHandler; import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; +import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; -import java.util.function.Supplier; import net.elytrium.commons.utils.reflection.ReflectionException; import net.elytrium.limboapi.LimboAPI; import net.elytrium.limboapi.Settings; import net.elytrium.limboapi.api.LimboSessionHandler; -import net.elytrium.limboapi.api.player.LimboPlayer; +import net.elytrium.limboapi.api.world.player.LimboPlayer; import net.elytrium.limboapi.injection.login.confirmation.LoginConfirmHandler; import net.elytrium.limboapi.protocol.LimboProtocol; import net.elytrium.limboapi.protocol.packets.c2s.MoveOnGroundOnlyPacket; import net.elytrium.limboapi.protocol.packets.c2s.MovePacket; import net.elytrium.limboapi.protocol.packets.c2s.MovePositionOnlyPacket; import net.elytrium.limboapi.protocol.packets.c2s.MoveRotationOnlyPacket; -import net.elytrium.limboapi.protocol.packets.c2s.PlayerChatSessionPacket; -import net.elytrium.limboapi.protocol.packets.c2s.TeleportConfirmPacket; +import net.elytrium.limboapi.protocol.packets.c2s.ChatSessionUpdatePacket; +import net.elytrium.limboapi.protocol.packets.c2s.AcceptTeleportationPacket; +import net.elytrium.limboapi.utils.Reflection; public class LimboSessionHandlerImpl implements MinecraftSessionHandler { - private static final MethodHandle TEARDOWN_METHOD; + public static final MethodHandle TEARDOWN_METHOD = Reflection.findVirtualVoid(ConnectedPlayer.class, "teardown"); private final LimboAPI plugin; private final LimboImpl limbo; @@ -77,7 +76,6 @@ public class LimboSessionHandlerImpl implements MinecraftSessionHandler { private final StateRegistry originalState; private final MinecraftSessionHandler originalHandler; private final RegisteredServer previousServer; - private final Supplier limboName; private final CompletableFuture playTransition = new CompletableFuture<>(); private final CompletableFuture configTransition = new CompletableFuture<>(); private final CompletableFuture chatSession = new CompletableFuture<>(); @@ -93,7 +91,7 @@ public class LimboSessionHandlerImpl implements MinecraftSessionHandler { private int keepAlivesSkipped; private long keepAliveSentTime; private int ping = -1; - private int genericBytes; + private int cumulativeBytes; private boolean loaded; private boolean switching; private boolean disconnecting; @@ -101,7 +99,7 @@ public class LimboSessionHandlerImpl implements MinecraftSessionHandler { public LimboSessionHandlerImpl(LimboAPI plugin, LimboImpl limbo, ConnectedPlayer player, LimboSessionHandler callback, StateRegistry originalState, MinecraftSessionHandler originalHandler, - RegisteredServer previousServer, Supplier limboName) { + RegisteredServer previousServer) { this.plugin = plugin; this.limbo = limbo; this.player = player; @@ -109,26 +107,24 @@ public LimboSessionHandlerImpl(LimboAPI plugin, LimboImpl limbo, ConnectedPlayer this.originalState = originalState; this.originalHandler = originalHandler; this.previousServer = previousServer; - this.limboName = limboName; - this.loaded = player.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_18_2) < 0; + this.loaded = player.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_18_2); if (originalHandler instanceof LimboSessionHandlerImpl sessionHandler) { this.settings = sessionHandler.getSettings(); this.brand = sessionHandler.getBrand(); + this.ping = sessionHandler.ping; } } + @SuppressWarnings("UnnecessaryToStringCall") public void onConfig(LimboPlayer player) { this.loaded = true; this.limboPlayer = player; this.callback.onConfig(this.limbo, player); - Integer serverReadTimeout = this.limbo.getReadTimeout(); - if (serverReadTimeout == null) { - serverReadTimeout = this.plugin.getServer().getConfiguration().getReadTimeout(); - } + int serverReadTimeout = Objects.requireNonNullElseGet(this.limbo.getReadTimeout(), () -> this.plugin.getServer().getConfiguration().getReadTimeout()); - // We should always send multiple keepalives inside a single timeout to not trigger Netty read timeout. + // We should always send multiple keepalives inside a single timeout to not trigger Netty read timeout serverReadTimeout /= 2; this.keepAliveTask = player.getScheduledExecutor().scheduleAtFixedRate(() -> { @@ -140,9 +136,9 @@ public void onConfig(LimboPlayer player) { if (this.keepAlivePending) { if (++this.keepAlivesSkipped == 2) { - connection.closeWith(this.plugin.getPackets().getTimeOut(this.player.getConnection().getState())); + connection.closeWith(this.plugin.getPackets().getTimeOut(connection.getState())); if (Settings.IMP.MAIN.LOGGING_ENABLED) { - LimboAPI.getLogger().warn("{} was kicked due to keepalive timeout.", this.player); + LimboAPI.getLogger().warn("{} was kicked due to keepalive timeout", this.player.toString()); } } } else if (this.keepAliveSentTime == 0 && this.originalHandler instanceof LimboSessionHandlerImpl sessionHandler) { @@ -150,7 +146,6 @@ public void onConfig(LimboPlayer player) { this.keepAlivePending = sessionHandler.keepAlivePending; this.keepAlivesSkipped = sessionHandler.keepAlivesSkipped; this.keepAliveSentTime = sessionHandler.keepAliveSentTime; - this.ping = sessionHandler.ping; } else { this.keepAliveKey = ThreadLocalRandom.current().nextInt(); KeepAlivePacket keepAlive = new KeepAlivePacket(); @@ -160,7 +155,7 @@ public void onConfig(LimboPlayer player) { this.keepAlivesSkipped = 0; this.keepAliveSentTime = System.currentTimeMillis(); } - }, 250, serverReadTimeout, TimeUnit.MILLISECONDS); + }, this.originalHandler instanceof LimboSessionHandlerImpl ? 0 : 250, serverReadTimeout, TimeUnit.MILLISECONDS); } public void onSpawn() { @@ -182,7 +177,7 @@ public void disconnectToConfig(Runnable runnable) { this.loaded = false; if (this.player.isOnlineMode() && this.player.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_21_2) && this.joinGameTriggered) { - // There is a race condition in the client then it reconnects too quickly (https://bugs.mojang.com/browse/MC-272506) + // There is a race condition in the client when it reconnects too quickly (https://bugs.mojang.com/browse/MC-272506) if (!this.chatSession.isDone() && this.chatSessionTimeoutTask == null) { this.chatSessionTimeoutTask = this.player.getConnection().eventLoop() .schedule(() -> this.chatSession.complete(this), Settings.IMP.MAIN.CHAT_SESSION_PACKET_TIMEOUT, TimeUnit.MILLISECONDS); @@ -198,6 +193,7 @@ public void disconnectToConfig(Runnable runnable) { } @Override + @SuppressWarnings("UnnecessaryToStringCall") public boolean handle(FinishedUpdatePacket packet) { // Switching to CONFIG state if (this.player.getConnection().getState() != StateRegistry.CONFIG) { @@ -212,7 +208,7 @@ public boolean handle(FinishedUpdatePacket packet) { this.player.getConnection().closeWith(this.plugin.getPackets().getInvalidSwitch()); if (Settings.IMP.MAIN.LOGGING_ENABLED) { - LimboAPI.getLogger().warn("{} sent an unexpected state switch confirmation.", this.player); + LimboAPI.getLogger().warn("{} sent an unexpected state switch confirmation", this.player.toString()); } } @@ -227,9 +223,14 @@ public boolean handle(FinishedUpdatePacket packet) { public boolean handle(MovePacket packet) { if (this.loaded) { this.callback.onGround(packet.isOnGround()); - this.callback.onMove(packet.getX(), packet.getY(), packet.getZ()); - this.callback.onMove(packet.getX(), packet.getY(), packet.getZ(), packet.getYaw(), packet.getPitch()); - this.callback.onRotate(packet.getYaw(), packet.getPitch()); + double posX = packet.getX(); + double posY = packet.getY(); + double posZ = packet.getZ(); + float yaw = packet.getYaw(); + float pitch = packet.getPitch(); + this.callback.onMove(posX, posY, posZ, yaw, pitch); + this.callback.onMove(posX, posY, posZ); + this.callback.onRotate(yaw, pitch); } return true; @@ -261,23 +262,25 @@ public boolean handle(MoveOnGroundOnlyPacket packet) { return true; } - public boolean handle(TeleportConfirmPacket packet) { + public boolean handle(AcceptTeleportationPacket packet) { if (this.loaded) { - this.callback.onTeleport(packet.getTeleportID()); + this.callback.onTeleport(packet.getId()); } return true; } @Override + @SuppressWarnings("UnnecessaryToStringCall") public boolean handle(KeepAlivePacket packet) { MinecraftConnection connection = this.player.getConnection(); if (this.keepAlivePending) { if (packet.getRandomId() != this.keepAliveKey) { connection.closeWith(this.plugin.getPackets().getInvalidPing()); if (Settings.IMP.MAIN.LOGGING_ENABLED) { - LimboAPI.getLogger().warn("{} sent an invalid keepalive.", this.player); + LimboAPI.getLogger().warn("{} sent an invalid keepalive", this.player.toString()); } + return false; } else { this.keepAlivePending = false; @@ -288,10 +291,10 @@ public boolean handle(KeepAlivePacket packet) { } } else { connection.closeWith(this.plugin.getPackets().getInvalidPing()); - if (Settings.IMP.MAIN.LOGGING_ENABLED) { - LimboAPI.getLogger().warn("{} sent an unexpected keepalive.", this.player); + LimboAPI.getLogger().warn("{} sent an unexpected keepalive", this.player.toString()); } + return false; } } @@ -335,11 +338,11 @@ private boolean handleChat(String message) { @Override public void handleUnknown(ByteBuf packet) { int readableBytes = packet.readableBytes(); - this.genericBytes += readableBytes; + this.cumulativeBytes += readableBytes; if (readableBytes > Settings.IMP.MAIN.MAX_UNKNOWN_PACKET_LENGTH) { this.kickTooBigPacket("unknown", readableBytes); - } else if (this.genericBytes > Settings.IMP.MAIN.MAX_MULTI_GENERIC_PACKET_LENGTH) { - this.kickTooBigPacket("unknown, multi", this.genericBytes); + } else if (this.cumulativeBytes > Settings.IMP.MAIN.MAX_MULTI_GENERIC_PACKET_LENGTH) { + this.kickTooBigPacket("unknown, multi", this.cumulativeBytes); } } @@ -347,27 +350,26 @@ public void handleUnknown(ByteBuf packet) { public void handleGeneric(MinecraftPacket packet) { if (packet instanceof ClientSettingsPacket clientSettings) { this.settings = clientSettings; - } else if (packet instanceof PlayerChatSessionPacket) { + } else if (packet instanceof ChatSessionUpdatePacket) { if (this.chatSessionTimeoutTask != null) { this.chatSessionTimeoutTask.cancel(true); } this.chatSession.complete(this); } else if (packet instanceof PluginMessagePacket pluginMessage) { int singleLength = pluginMessage.content().readableBytes() + pluginMessage.getChannel().length() * 4; - this.genericBytes += singleLength; + this.cumulativeBytes += singleLength; if (singleLength > Settings.IMP.MAIN.MAX_SINGLE_GENERIC_PACKET_LENGTH) { this.kickTooBigPacket("generic (PluginMessage packet (custom payload)), single", singleLength); return; - } else if (this.genericBytes > Settings.IMP.MAIN.MAX_MULTI_GENERIC_PACKET_LENGTH) { - this.kickTooBigPacket("generic (PluginMessage packet (custom payload)), multi", this.genericBytes); + } else if (this.cumulativeBytes > Settings.IMP.MAIN.MAX_MULTI_GENERIC_PACKET_LENGTH) { + this.kickTooBigPacket("generic (PluginMessage packet (custom payload)), multi", this.cumulativeBytes); return; } - if (this.player.getConnection().getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) >= 0 - && PluginMessageUtil.isMcBrand(pluginMessage)) { + if (this.player.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_20_2) && PluginMessageUtil.isMcBrand(pluginMessage)) { try { this.brand = ProtocolUtils.readString(pluginMessage.content().slice(), Settings.IMP.MAIN.MAX_BRAND_NAME_LENGTH); - } catch (QuietDecoderException ignored) { + } catch (QuietDecoderException e) { this.kickTooBigPacket("brand name", pluginMessage.content().readableBytes()); return; } @@ -380,11 +382,11 @@ public void handleGeneric(MinecraftPacket packet) { this.callback.onGeneric(packet); } + @SuppressWarnings("UnnecessaryToStringCall") private void kickTooBigPacket(String type, int length) { this.player.getConnection().closeWith(this.plugin.getPackets().getTooBigPacket()); - if (Settings.IMP.MAIN.LOGGING_ENABLED) { - LimboAPI.getLogger().warn("{} sent too big packet. (type: {}, length: {})", this.player, type, length); + LimboAPI.getLogger().warn("{} sent too big packet (type: {}, length: {})", this.player.toString(), type, length); } } @@ -405,21 +407,18 @@ public void release() { @Override public void disconnected() { - //this.disconnected = true; this.release(); if (Settings.IMP.MAIN.LOGGING_ENABLED) { - LimboAPI.getLogger().info( - "{} ({}) has disconnected from the {} Limbo", this.player.getUsername(), this.player.getRemoteAddress(), this.limboName.get() - ); + LimboAPI.getLogger().info("{} has disconnected from the {} Limbo", this.player.toString(), this.limbo.getName()); } MinecraftConnection connection = this.player.getConnection(); if (connection.isClosed()) { try { TEARDOWN_METHOD.invokeExact(this.player); - } catch (Throwable e) { - throw new ReflectionException(e); + } catch (Throwable t) { + throw new ReflectionException(t); } return; @@ -442,16 +441,14 @@ public void disconnected() { ChannelPipeline pipeline = connection.getChannel().pipeline(); if (pipeline.get(LimboProtocol.READ_TIMEOUT) != null) { - pipeline.replace(LimboProtocol.READ_TIMEOUT, Connections.READ_TIMEOUT, - new ReadTimeoutHandler(this.plugin.getServer().getConfiguration().getReadTimeout(), TimeUnit.MILLISECONDS) - ); + pipeline.replace(LimboProtocol.READ_TIMEOUT, Connections.READ_TIMEOUT, new ReadTimeoutHandler(this.plugin.getServer().getConfiguration().getReadTimeout(), TimeUnit.MILLISECONDS)); } } public void disconnect(Runnable runnable) { if (!this.disconnecting) { this.disconnecting = true; - if (this.player.getConnection().getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) < 0) { + if (this.player.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_20_2)) { runnable.run(); } else { this.playTransition.thenRun(runnable); @@ -486,13 +483,4 @@ public ClientSettingsPacket getSettings() { public String getBrand() { return this.brand; } - - static { - try { - TEARDOWN_METHOD = MethodHandles.privateLookupIn(ConnectedPlayer.class, MethodHandles.lookup()) - .findVirtual(ConnectedPlayer.class, "teardown", MethodType.methodType(void.class)); - } catch (NoSuchMethodException | IllegalAccessException e) { - throw new ReflectionException(e); - } - } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/DataComponentRegistry.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/DataComponentRegistry.java new file mode 100644 index 00000000..9f9f0136 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/DataComponentRegistry.java @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2021 - 2024 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limboapi.server.item; + +import com.google.gson.internal.LinkedTreeMap; +import com.velocitypowered.api.network.ProtocolVersion; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import java.lang.invoke.MethodHandle; +import java.lang.reflect.Field; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import net.elytrium.commons.utils.reflection.ReflectionException; +import net.elytrium.limboapi.LimboAPI; +import net.elytrium.limboapi.api.world.item.datacomponent.DataComponentType; +import net.elytrium.limboapi.api.world.item.datacomponent.DataComponentTypes; +import net.elytrium.limboapi.api.world.item.datacomponent.type.Unbreakable; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import net.elytrium.limboapi.server.item.codec.AdventureModePredicateCodec; +import net.elytrium.limboapi.server.item.codec.ArmorTrimCodec; +import net.elytrium.limboapi.server.item.codec.AttributeModifiersCodec; +import net.elytrium.limboapi.server.item.codec.BannerPatternLayerCodec; +import net.elytrium.limboapi.server.item.codec.BeehiveOccupantCodec; +import net.elytrium.limboapi.server.item.codec.BlocksAttacksCodec; +import net.elytrium.limboapi.server.item.codec.ConsumableCodec; +import net.elytrium.limboapi.server.item.codec.CustomModelDataCodec; +import net.elytrium.limboapi.server.item.codec.DyedColorCodec; +import net.elytrium.limboapi.server.item.codec.EnchantmentsCodec; +import net.elytrium.limboapi.server.item.codec.EquippableCodec; +import net.elytrium.limboapi.server.item.codec.FireworkExplosionCodec; +import net.elytrium.limboapi.server.item.codec.FireworksCodec; +import net.elytrium.limboapi.server.item.codec.FoodPropertiesCodec; +import net.elytrium.limboapi.server.item.codec.InstrumentCodec; +import net.elytrium.limboapi.server.item.codec.JukeboxPlayableCodec; +import net.elytrium.limboapi.server.item.codec.LodestoneTrackerCodec; +import net.elytrium.limboapi.server.item.codec.PaintingVariantCodec; +import net.elytrium.limboapi.server.item.codec.PotionContentsCodec; +import net.elytrium.limboapi.server.item.codec.ResolvableProfileCodec; +import net.elytrium.limboapi.server.item.codec.SuspiciousStewEffectCodec; +import net.elytrium.limboapi.server.item.codec.ToolCodec; +import net.elytrium.limboapi.server.item.codec.TooltipDisplayCodec; +import net.elytrium.limboapi.server.item.codec.UseCooldownCodec; +import net.elytrium.limboapi.server.item.codec.WeaponCodec; +import net.elytrium.limboapi.server.item.codec.WrittenBookContentCodec; +import net.elytrium.limboapi.server.item.codec.data.ComponentHolderCodec; +import net.elytrium.limboapi.server.item.codec.data.ConsumeEffectCodec; +import net.elytrium.limboapi.server.item.codec.data.EitherCodec; +import net.elytrium.limboapi.server.item.codec.data.FilterableCodec; +import net.elytrium.limboapi.server.item.codec.data.HolderSetCodec; +import net.elytrium.limboapi.server.item.codec.data.ItemStackCodec; +import net.elytrium.limboapi.server.item.codec.data.SoundEventCodec; +import net.elytrium.limboapi.server.item.codec.data.TrimMaterialCodec; +import net.elytrium.limboapi.server.item.codec.data.TypedEntityDataCodec; +import net.elytrium.limboapi.utils.JsonUtil; +import net.elytrium.limboapi.utils.Reflection; +import net.elytrium.limboapi.utils.Unit; +import org.checkerframework.checker.nullness.qual.NonNull; + +public class DataComponentRegistry { + + private static final EnumMap>, Object2IntOpenHashMap>> REGISTRY = new EnumMap<>(ProtocolVersion.class); + + static { + var mappings = JsonUtil.>parse(LimboAPI.class.getResourceAsStream("/mappings/data_component_types_mappings.json")); + + Field[] fields = DataComponentTypes.class.getDeclaredFields(); + Map types = new HashMap<>(fields.length); + for (Field field : fields) { + String name = field.getName(); + try { + types.put(name, Reflection.LOOKUP.findStaticSetter(DataComponentTypes.class, name, field.getType())); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new ReflectionException(e); + } + } + + mappings.forEach((name, versions) -> { + DataComponentTypeImpl type; + try { + types.get(name.replace('/', '_').substring(10/*"minecraft:".length()*/).toUpperCase(Locale.US)).invoke(type = new DataComponentTypeImpl<>(name)); + } catch (Throwable t) { + throw new ReflectionException(name, t); + } + + versions.forEach((version, _id) -> { + var entry = DataComponentRegistry.REGISTRY.computeIfAbsent(ProtocolVersion.getProtocolVersion(Integer.parseInt(version)), key -> { + Object2IntOpenHashMap t2i = new Object2IntOpenHashMap<>(mappings.size()); + t2i.defaultReturnValue(Integer.MIN_VALUE); + return Map.entry(new Int2ObjectOpenHashMap<>(mappings.size()), t2i); + }); + int id = _id.intValue(); + entry.getKey().put(id, type); + entry.getValue().put(type, id); + }); + }); + DataComponentRegistry.REGISTRY.values().forEach(entry -> { + entry.getKey().trim(); + entry.getValue().trim(); + }); + + DataComponentRegistry.register(ByteBufCodecs.COMPOUND_TAG, + DataComponentTypes.CUSTOM_DATA, DataComponentTypes.INTANGIBLE_PROJECTILE, DataComponentTypes.MAP_DECORATIONS, + DataComponentTypes.DEBUG_STICK_STATE, DataComponentTypes.BUCKET_ENTITY_DATA, DataComponentTypes.CONTAINER_LOOT + ); + DataComponentRegistry.register(ByteBufCodecs.VAR_INT, + DataComponentTypes.MAX_STACK_SIZE, DataComponentTypes.MAX_DAMAGE, DataComponentTypes.DAMAGE, DataComponentTypes.RARITY, DataComponentTypes.REPAIR_COST, + DataComponentTypes.ENCHANTABLE, DataComponentTypes.MAP_ID, DataComponentTypes.MAP_POST_PROCESSING, DataComponentTypes.OMINOUS_BOTTLE_AMPLIFIER, DataComponentTypes.BASE_COLOR, + DataComponentTypes.VILLAGER_VARIANT, DataComponentTypes.WOLF_VARIANT, DataComponentTypes.WOLF_SOUND_VARIANT, DataComponentTypes.WOLF_COLLAR, DataComponentTypes.FOX_VARIANT, + DataComponentTypes.SALMON_SIZE, DataComponentTypes.PARROT_VARIANT, DataComponentTypes.TROPICAL_FISH_PATTERN, DataComponentTypes.TROPICAL_FISH_BASE_COLOR, + DataComponentTypes.TROPICAL_FISH_PATTERN_COLOR, DataComponentTypes.MOOSHROOM_VARIANT, DataComponentTypes.RABBIT_VARIANT, DataComponentTypes.PIG_VARIANT, + DataComponentTypes.COW_VARIANT, DataComponentTypes.FROG_VARIANT, DataComponentTypes.HORSE_VARIANT, DataComponentTypes.LLAMA_VARIANT, DataComponentTypes.AXOLOTL_VARIANT, + DataComponentTypes.CAT_VARIANT, DataComponentTypes.CAT_COLLAR, DataComponentTypes.SHEEP_COLOR, DataComponentTypes.SHULKER_COLOR + ); + DataComponentRegistry.register(StreamCodec.lt(ProtocolVersion.MINECRAFT_1_21_5, ByteBufCodecs.BOOL.map(value -> value ? Unbreakable.TRUE : Unbreakable.FALSE, Unbreakable::showInTooltip), Unbreakable.TRUE), + DataComponentTypes.UNBREAKABLE + ); + DataComponentRegistry.register(ComponentHolderCodec.CODEC, DataComponentTypes.CUSTOM_NAME, DataComponentTypes.ITEM_NAME); + DataComponentRegistry.register(ByteBufCodecs.STRING_UTF8, + DataComponentTypes.ITEM_MODEL, DataComponentTypes.DAMAGE_RESISTANT, DataComponentTypes.TOOLTIP_STYLE, + DataComponentTypes.PROVIDES_BANNER_PATTERNS, DataComponentTypes.NOTE_BLOCK_SOUND + ); + DataComponentRegistry.register(ByteBufCodecs.collection(ComponentHolderCodec.CODEC, 256), DataComponentTypes.LORE); + DataComponentRegistry.register(EnchantmentsCodec.CODEC, DataComponentTypes.ENCHANTMENTS, DataComponentTypes.STORED_ENCHANTMENTS); + DataComponentRegistry.register(AdventureModePredicateCodec.CODEC, DataComponentTypes.CAN_PLACE_ON, DataComponentTypes.CAN_BREAK); + DataComponentRegistry.register(AttributeModifiersCodec.CODEC, DataComponentTypes.ATTRIBUTE_MODIFIERS); + DataComponentRegistry.register(CustomModelDataCodec.CODEC, DataComponentTypes.CUSTOM_MODEL_DATA); + DataComponentRegistry.register(Unit.CODEC, + DataComponentTypes.HIDE_ADDITIONAL_TOOLTIP, DataComponentTypes.HIDE_TOOLTIP, + DataComponentTypes.CREATIVE_SLOT_LOCK, DataComponentTypes.FIRE_RESISTANT, DataComponentTypes.GLIDER + ); + DataComponentRegistry.register(ByteBufCodecs.BOOL, DataComponentTypes.ENCHANTMENT_GLINT_OVERRIDE); + DataComponentRegistry.register(TooltipDisplayCodec.CODEC, DataComponentTypes.TOOLTIP_DISPLAY); + DataComponentRegistry.register(FoodPropertiesCodec.CODEC, DataComponentTypes.FOOD); + DataComponentRegistry.register(ConsumableCodec.CODEC, DataComponentTypes.CONSUMABLE); + DataComponentRegistry.register(ItemStackCodec.CODEC, DataComponentTypes.USE_REMAINDER); + DataComponentRegistry.register(UseCooldownCodec.CODEC, DataComponentTypes.USE_COOLDOWN); + DataComponentRegistry.register(ToolCodec.CODEC, DataComponentTypes.TOOL); + DataComponentRegistry.register(WeaponCodec.CODEC, DataComponentTypes.WEAPON); + DataComponentRegistry.register(EquippableCodec.CODEC, DataComponentTypes.EQUIPPABLE); + DataComponentRegistry.register(HolderSetCodec.CODEC, DataComponentTypes.REPAIRABLE); + DataComponentRegistry.register(ConsumeEffectCodec.COLLECTION_CODEC, DataComponentTypes.DEATH_PROTECTION); + DataComponentRegistry.register(BlocksAttacksCodec.CODEC, DataComponentTypes.BLOCKS_ATTACKS); + DataComponentRegistry.register(DyedColorCodec.CODEC, DataComponentTypes.DYED_COLOR); + DataComponentRegistry.register(ByteBufCodecs.INT, DataComponentTypes.MAP_COLOR); + DataComponentRegistry.register(ByteBufCodecs.collection(ItemStackCodec.CODEC), DataComponentTypes.CHARGED_PROJECTILES, DataComponentTypes.BUNDLE_CONTENTS); + DataComponentRegistry.register(PotionContentsCodec.CODEC, DataComponentTypes.POTION_CONTENTS); + DataComponentRegistry.register(ByteBufCodecs.FLOAT, DataComponentTypes.POTION_DURATION_SCALE); + DataComponentRegistry.register(ByteBufCodecs.collection(SuspiciousStewEffectCodec.CODEC), DataComponentTypes.SUSPICIOUS_STEW_EFFECTS); + DataComponentRegistry.register(ByteBufCodecs.collection(FilterableCodec.codec(ByteBufCodecs.STRING_UTF8_1024, ByteBufCodecs.OPTIONAL_STRING_UTF8_1024), 100), DataComponentTypes.WRITABLE_BOOK_CONTENT); + DataComponentRegistry.register(WrittenBookContentCodec.CODEC, DataComponentTypes.WRITTEN_BOOK_CONTENT); + DataComponentRegistry.register(ArmorTrimCodec.CODEC, DataComponentTypes.TRIM); + DataComponentRegistry.register(TypedEntityDataCodec.TYPED_ENTITY_DATA_CODEC, DataComponentTypes.ENTITY_DATA, DataComponentTypes.BLOCK_ENTITY_DATA); + DataComponentRegistry.register(InstrumentCodec.EITHER_CODEC, DataComponentTypes.INSTRUMENT); + DataComponentRegistry.register(EitherCodec.codec(TrimMaterialCodec.HOLDER_CODEC, ByteBufCodecs.STRING_UTF8), DataComponentTypes.PROVIDES_TRIM_MATERIAL); + DataComponentRegistry.register(JukeboxPlayableCodec.CODEC, DataComponentTypes.JUKEBOX_PLAYABLE); + DataComponentRegistry.register(ByteBufCodecs.TAG, DataComponentTypes.RECIPES); + DataComponentRegistry.register(LodestoneTrackerCodec.CODEC, DataComponentTypes.LODESTONE_TRACKER); + DataComponentRegistry.register(FireworkExplosionCodec.CODEC, DataComponentTypes.FIREWORK_EXPLOSION); + DataComponentRegistry.register(FireworksCodec.CODEC, DataComponentTypes.FIREWORKS); + DataComponentRegistry.register(ResolvableProfileCodec.CODEC, DataComponentTypes.PROFILE); + DataComponentRegistry.register(ByteBufCodecs.collection(BannerPatternLayerCodec.CODEC), DataComponentTypes.BANNER_PATTERNS); + DataComponentRegistry.register(ByteBufCodecs.collection(ByteBufCodecs.VAR_INT, 4), DataComponentTypes.POT_DECORATIONS); + DataComponentRegistry.register(ByteBufCodecs.collection(ItemStackCodec.OPTIONAL_CODEC, 256), DataComponentTypes.CONTAINER); + DataComponentRegistry.register(ByteBufCodecs.STRING_2_STRING_MAP, DataComponentTypes.BLOCK_STATE); + DataComponentRegistry.register(ByteBufCodecs.collection(BeehiveOccupantCodec.CODEC), DataComponentTypes.BEES); + DataComponentRegistry.register(StreamCodec.lt(ProtocolVersion.MINECRAFT_1_21_2, ByteBufCodecs.TAG, ByteBufCodecs.COMPOUND_TAG), DataComponentTypes.LOCK); + DataComponentRegistry.register(SoundEventCodec.HOLDER_CODEC, DataComponentTypes.BREAK_SOUND); + DataComponentRegistry.register(EitherCodec.codec(ByteBufCodecs.VAR_INT, ByteBufCodecs.STRING_UTF8), DataComponentTypes.CHICKEN_VARIANT); + DataComponentRegistry.register(PaintingVariantCodec.HOLDER_CODEC, DataComponentTypes.PAINTING_VARIANT); + } + + @SafeVarargs + @SuppressWarnings("unchecked") + private static void register(StreamCodec codec, DataComponentType.Valued<@NonNull ? extends T>... types) { + for (DataComponentType type : types) { + var impl = (DataComponentTypeImpl) type; + if (impl.codec != null) { + throw new IllegalStateException("Codec already registered"); + } + + impl.codec = codec; + } + } + + @SuppressWarnings("rawtypes") + private static void register(StreamCodec codec, DataComponentType.NonValued... types) { + for (DataComponentType type : types) { + var impl = (DataComponentTypeImpl) type; + if (impl.codec != null) { + throw new IllegalStateException("Codec already registered"); + } + + impl.codec = codec; + } + } + + public static DataComponentType getType(int id, ProtocolVersion version) { + var type = DataComponentRegistry.REGISTRY.get(version).getKey().get(id); + if (type == null) { + throw new IllegalStateException("Component not found: " + id); + } + + return type; + } + + public static int getId(DataComponentType type, ProtocolVersion version) { + return DataComponentRegistry.REGISTRY.get(version).getValue().getInt(type); + } + + public static final class DataComponentTypeImpl implements DataComponentType.Valued<@NonNull T>, DataComponentType.NonValued { + + private final String name; + + private StreamCodec codec; + + private DataComponentTypeImpl(String name) { + this.name = name; + } + + public StreamCodec codec() { + return this.codec; + } + + @Override + public String toString() { + return this.name; + } + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/SimpleDataComponentMap.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/SimpleDataComponentMap.java new file mode 100644 index 00000000..5758c232 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/SimpleDataComponentMap.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2021 - 2025 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limboapi.server.item; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import net.elytrium.limboapi.api.world.item.datacomponent.DataComponentType; +import net.elytrium.limboapi.api.world.item.datacomponent.DataComponentMap; +import net.elytrium.limboapi.utils.Unit; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +public class SimpleDataComponentMap implements DataComponentMap { + + private Map components; + + @Override + public void setData(DataComponentType.NonValued type) { + if (this.components == null) { + this.components = new HashMap<>(); + } + + this.components.put(type, Unit.INSTANCE); + } + + @Override + public void setData(DataComponentType.Valued<@NonNull T> type, @NonNull T value) { + Objects.requireNonNull(value, "value cannot be null"); + if (this.components == null) { + this.components = new HashMap<>(); + } + + this.components.put(type, value); + } + + @Override + public void unsetData(DataComponentType type) { + if (this.components == null) { + this.components = new HashMap<>(); + } + + this.components.put(type, null); + } + + @Override + public void resetData(DataComponentType type) { + if (this.components != null && !this.components.isEmpty()) { + this.components.remove(type); + } + } + + @Override + @SuppressWarnings("unchecked") + public T getData(DataComponentType.Valued<@NonNull T> type) { + return this.components == null || this.components.isEmpty() ? null : (T) this.components.get(type); + } + + @Override + public boolean hasData(DataComponentType type) { + return this.components != null && !this.components.isEmpty() && this.components.containsKey(type); + } + + @Override + public DataComponentMap read(Object bufObj, ProtocolVersion version) { + ByteBuf buf = (ByteBuf) bufObj; + + int added = ProtocolUtils.readVarInt(buf); + int removed = ProtocolUtils.readVarInt(buf); + if (added != 0 || removed != 0) { + this.read(buf, version, added, removed); + } + + return this; + } + + private void read(ByteBuf buf, ProtocolVersion version, int added, int removed) { + if (this.components == null) { + this.components = new HashMap<>(added + removed); + } + + for (int i = 0; i < added; ++i) { + DataComponentRegistry.DataComponentTypeImpl type = (DataComponentRegistry.DataComponentTypeImpl) DataComponentRegistry.getType(ProtocolUtils.readVarInt(buf), version); + this.components.put(type, type.codec().decode(buf, version)); + } + + for (int i = 0; i < removed; ++i) { + this.components.put(DataComponentRegistry.getType(ProtocolUtils.readVarInt(buf), version), null); + } + } + + @Override + @SuppressWarnings("unchecked") + public DataComponentMap write(Object bufObj, ProtocolVersion version) { + ByteBuf buf = (ByteBuf) bufObj; + + if (this.components == null || this.components.isEmpty()) { + ProtocolUtils.writeVarInt(buf, 0); + ProtocolUtils.writeVarInt(buf, 0); + } else { + int added = 0; + int removed = 0; + for (Object component : this.components.values()) { + if (component == null) { + ++removed; + } else { + ++added; + } + } + + ProtocolUtils.writeVarInt(buf, added); + ProtocolUtils.writeVarInt(buf, removed); + + this.components.forEach((key, value) -> { + if (value != null) { + int id = DataComponentRegistry.getId(key, version); + if (id != Integer.MIN_VALUE) { // allow plugins to send version-specific components, so don't throw exception if no id for this version + ProtocolUtils.writeVarInt(buf, id); + ((DataComponentRegistry.DataComponentTypeImpl) key).codec().encode(buf, version, value); + } + } + }); + + this.components.forEach((key, component) -> { + if (component == null) { + int id = DataComponentRegistry.getId(key, version); + if (id != Integer.MIN_VALUE) { + ProtocolUtils.writeVarInt(buf, id); + } + } + }); + } + + return this; + } + + @Override + public String toString() { + return "SimpleDataComponentMap{" + + "components=" + this.components + + "}"; + } + + public static DataComponentMap read(ByteBuf buf, ProtocolVersion version) { + int added = ProtocolUtils.readVarInt(buf); + int removed = ProtocolUtils.readVarInt(buf); + if (added == 0 && removed == 0) { + return null; + } else { + SimpleDataComponentMap map = new SimpleDataComponentMap(); + map.read(buf, version, added, removed); + return map; + } + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/SimpleItemComponentManager.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/SimpleItemComponentManager.java deleted file mode 100644 index 6c2780ff..00000000 --- a/plugin/src/main/java/net/elytrium/limboapi/server/item/SimpleItemComponentManager.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package net.elytrium.limboapi.server.item; - -import com.google.gson.Gson; -import com.google.gson.internal.LinkedTreeMap; -import com.velocitypowered.api.network.ProtocolVersion; -import it.unimi.dsi.fastutil.Function; -import it.unimi.dsi.fastutil.objects.Object2IntMap; -import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import net.elytrium.limboapi.LimboAPI; -import net.elytrium.limboapi.server.item.type.BooleanItemComponent; -import net.elytrium.limboapi.server.item.type.ComponentItemComponent; -import net.elytrium.limboapi.server.item.type.ComponentsItemComponent; -import net.elytrium.limboapi.server.item.type.DyedColorItemComponent; -import net.elytrium.limboapi.server.item.type.EmptyItemComponent; -import net.elytrium.limboapi.server.item.type.EnchantmentsItemComponent; -import net.elytrium.limboapi.server.item.type.GameProfileItemComponent; -import net.elytrium.limboapi.server.item.type.IntItemComponent; -import net.elytrium.limboapi.server.item.type.StringItemComponent; -import net.elytrium.limboapi.server.item.type.StringsItemComponent; -import net.elytrium.limboapi.server.item.type.TagItemComponent; -import net.elytrium.limboapi.server.item.type.VarIntItemComponent; -import net.elytrium.limboapi.server.item.type.WriteableItemComponent; - -public class SimpleItemComponentManager { - - private static final Gson GSON = new Gson(); - - private static final Map> ID = new HashMap<>(); - - static { - LinkedTreeMap> mapping = GSON.fromJson( - new InputStreamReader( - Objects.requireNonNull(LimboAPI.class.getResourceAsStream("/mapping/data_component_types_mapping.json")), - StandardCharsets.UTF_8 - ), - LinkedTreeMap.class - ); - - LinkedTreeMap components = GSON.fromJson( - new InputStreamReader( - Objects.requireNonNull(LimboAPI.class.getResourceAsStream("/mapping/data_component_types.json")), - StandardCharsets.UTF_8 - ), - LinkedTreeMap.class - ); - - Map cache = new HashMap<>(); - for (ProtocolVersion version : ProtocolVersion.values()) { - if (version.compareTo(ProtocolVersion.MINECRAFT_1_20_5) < 0) { - continue; - } - - cache.put(version.name().substring("MINECRAFT_".length()).replace('_', '.'), version); - } - - components.forEach((name, id) -> { - mapping.get(id).forEach((version, protocolId) -> { - ID.computeIfAbsent(cache.get(version), key -> new Object2IntOpenHashMap<>()).put(name, Integer.parseInt(protocolId)); - }); - }); - } - - private final Map> factory = new HashMap<>(); - - public SimpleItemComponentManager() { - // TODO: implement missing components: - // trim, intangible_projectile, food, suspicious_stew_effects, lock, tool, - // can_break, writable_book_content, potion_contents, bees, banner_patterns, - // pot_decorations, map_decorations, debug_stick_state, can_place_on, lodestone_tracker, - // written_book_content, container_loot, container, block_state, attribute_modifiers, - // bundle_contents, firework_explosion, charged_projectiles, fireworks - this.register("minecraft:lore", version -> new ComponentsItemComponent("minecraft:lore")); - this.register("minecraft:dyed_color", version -> new DyedColorItemComponent("minecraft:dyed_color")); - this.register("minecraft:profile", version -> new GameProfileItemComponent("minecraft:profile")); - - for (String type : new String[] { "minecraft:max_stack_size", "minecraft:max_damage", - "minecraft:damage", "minecraft:rarity", "minecraft:custom_model_data", - "minecraft:repair_cost", "minecraft:map_id", "minecraft:map_post_processing", - "minecraft:ominous_bottle_amplifier", "minecraft:base_color" }) { - this.register(type, version -> new VarIntItemComponent(type)); - } - - for (String type : new String[] { "minecraft:unbreakable", "minecraft:enchantment_glint_override" }) { - this.register(type, version -> new BooleanItemComponent(type)); - } - - for (String type : new String[] { "minecraft:custom_name", "minecraft:item_name" }) { - this.register(type, version -> new ComponentItemComponent(type)); - } - - for (String type : new String[] { "minecraft:hide_additional_tooltip", "minecraft:hide_tooltip", - "minecraft:creative_slot_lock", "minecraft:fire_resistant" }) { - this.register(type, version -> new EmptyItemComponent(type)); - } - - for (String type : new String[] { "minecraft:enchantments", "minecraft:stored_enchantments" }) { - this.register(type, version -> new EnchantmentsItemComponent(type)); - } - - for (String type : new String[] { "minecraft:map_color" }) { - this.register(type, version -> new IntItemComponent(type)); - } - - for (String type : new String[] { "minecraft:custom_data", "minecraft:entity_data", - "minecraft:bucket_entity_data", "minecraft:block_entity_data" }) { - this.register(type, version -> new TagItemComponent(type)); - } - - for (String type : new String[] { "minecraft:instrument", "minecraft:note_block_sound" }) { - this.register(type, version -> new StringItemComponent(type)); - } - - for (String type : new String[] { "minecraft:recipes" }) { - this.register(type, version -> new StringsItemComponent(type)); - } - } - - public void register(String name, Function factory) { - this.factory.put(name, factory); - } - - public WriteableItemComponent createComponent(ProtocolVersion version, String name) { - return (WriteableItemComponent) this.factory.get(name).apply(version); - } - - public int getId(String name, ProtocolVersion version) { - Object2IntMap ids = ID.get(version); - if (ids == null) { - throw new IllegalArgumentException("unsupported version: " + version); - } - - if (!ids.containsKey(name)) { - throw new IllegalStateException("component not found: " + name); - } - - return ids.getInt(name); - } -} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/SimpleItemComponentMap.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/SimpleItemComponentMap.java deleted file mode 100644 index 35413e89..00000000 --- a/plugin/src/main/java/net/elytrium/limboapi/server/item/SimpleItemComponentMap.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package net.elytrium.limboapi.server.item; - -import com.velocitypowered.api.network.ProtocolVersion; -import com.velocitypowered.proxy.protocol.ProtocolUtils; -import io.netty.buffer.ByteBuf; -import java.util.ArrayList; -import java.util.List; -import net.elytrium.limboapi.api.protocol.item.ItemComponent; -import net.elytrium.limboapi.api.protocol.item.ItemComponentMap; -import net.elytrium.limboapi.server.item.type.WriteableItemComponent; - -public class SimpleItemComponentMap implements ItemComponentMap { - - private final List> addedComponents = new ArrayList<>(); - private final List> removedComponents = new ArrayList<>(); - private final SimpleItemComponentManager manager; - - public SimpleItemComponentMap(SimpleItemComponentManager manager) { - this.manager = manager; - } - - @Override - public ItemComponentMap add(ProtocolVersion version, String name, T value) { - this.addedComponents.add((WriteableItemComponent) this.manager.createComponent(version, name).setValue(value)); - return this; - } - - @Override - public ItemComponentMap remove(ProtocolVersion version, String name) { - this.removedComponents.add(this.manager.createComponent(version, name)); - return null; - } - - @Override - public List getAdded() { - return (List) (Object) this.addedComponents; - } - - @Override - public List getRemoved() { - return (List) (Object) this.removedComponents; - } - - @Override - public void read(ProtocolVersion version, Object buffer) { - // TODO: implement - throw new UnsupportedOperationException("read"); - } - - @Override - public void write(ProtocolVersion version, Object buffer) { - ByteBuf buf = (ByteBuf) buffer; - - ProtocolUtils.writeVarInt(buf, this.getAdded().size()); - ProtocolUtils.writeVarInt(buf, this.getRemoved().size()); - - for (WriteableItemComponent component : this.addedComponents) { - ProtocolUtils.writeVarInt(buf, this.manager.getId(component.getName(), version)); - component.write(version, buf); - } - - for (WriteableItemComponent component : this.removedComponents) { - ProtocolUtils.writeVarInt(buf, this.manager.getId(component.getName(), version)); - } - } -} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/AdventureModePredicateCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/AdventureModePredicateCodec.java new file mode 100644 index 00000000..d80d18e0 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/AdventureModePredicateCodec.java @@ -0,0 +1,136 @@ +package net.elytrium.limboapi.server.item.codec; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import it.unimi.dsi.fastutil.ints.IntObjectImmutablePair; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Objects; +import net.elytrium.limboapi.api.world.item.datacomponent.DataComponentType; +import net.elytrium.limboapi.api.world.item.datacomponent.type.AdventureModePredicate; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import net.elytrium.limboapi.server.item.DataComponentRegistry; +import net.elytrium.limboapi.server.item.codec.data.HolderSetCodec; +import net.elytrium.limboapi.utils.Unit; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface AdventureModePredicateCodec { + + StreamCodec CODEC = StreamCodec.composite( + ByteBufCodecs.collection(BlockPredicateCodec.CODEC), AdventureModePredicate::predicates, + StreamCodec.lt(ProtocolVersion.MINECRAFT_1_21_5, ByteBufCodecs.BOOL, true), AdventureModePredicate::showInTooltip, + AdventureModePredicate::new + ); + + interface BlockPredicateCodec { + + StreamCodec CODEC = StreamCodec.composite( + HolderSetCodec.OPTIONAL_CODEC, AdventureModePredicate.BlockPredicate::blocks, + ByteBufCodecs.optional(ByteBufCodecs.collection(PropertyMatcherCodec.CODEC)), AdventureModePredicate.BlockPredicate::properties, + ByteBufCodecs.optional(ByteBufCodecs.COMPOUND_TAG), AdventureModePredicate.BlockPredicate::nbt, + DataComponentMatchersCodec.CODEC, AdventureModePredicate.BlockPredicate::components, + AdventureModePredicate.BlockPredicate::new + ); + + interface PropertyMatcherCodec { + + StreamCodec CODEC = StreamCodec.composite( + ByteBufCodecs.STRING_UTF8, AdventureModePredicate.PropertyMatcher::name, + ValueMatcherCodec.CODEC, AdventureModePredicate.PropertyMatcher::valueMatcher, + AdventureModePredicate.PropertyMatcher::new + ); + + interface ValueMatcherCodec { + + StreamCodec EXACT_MATCHER_CODEC = ByteBufCodecs.STRING_UTF8.map( + AdventureModePredicate.ValueMatcher.Exact::new, AdventureModePredicate.ValueMatcher.Exact::value + ); + StreamCodec RANGED_MATCHER_CODEC = StreamCodec.composite( + ByteBufCodecs.OPTIONAL_STRING_UTF8, AdventureModePredicate.ValueMatcher.Ranged::minValue, + ByteBufCodecs.OPTIONAL_STRING_UTF8, AdventureModePredicate.ValueMatcher.Ranged::maxValue, + AdventureModePredicate.ValueMatcher.Ranged::new + ); + StreamCodec CODEC = new StreamCodec<>() { + + @Override + public AdventureModePredicate.ValueMatcher decode(ByteBuf buf, ProtocolVersion version) { + return buf.readBoolean() ? ValueMatcherCodec.EXACT_MATCHER_CODEC.decode(buf, version) : ValueMatcherCodec.RANGED_MATCHER_CODEC.decode(buf, version); + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, AdventureModePredicate.ValueMatcher value) { + if (value instanceof AdventureModePredicate.ValueMatcher.Exact exact) { + buf.writeBoolean(true); + ValueMatcherCodec.EXACT_MATCHER_CODEC.encode(buf, version, exact); + } else if (value instanceof AdventureModePredicate.ValueMatcher.Ranged ranged) { + buf.writeBoolean(false); + ValueMatcherCodec.RANGED_MATCHER_CODEC.encode(buf, version, ranged); + } else { + throw new IllegalArgumentException(value.getClass().getName()); + } + } + }; + } + } + + interface DataComponentMatchersCodec { + + StreamCodec> EXACT_PREDICATE_CODEC = new StreamCodec<>() { + + @Override + @SuppressWarnings("unchecked") + public Collection decode(ByteBuf buf, ProtocolVersion version) { + int count = ProtocolUtils.readVarInt(buf); + var result = new ArrayList(count); + for (int i = 0; i < count; ++i) { + DataComponentType type = DataComponentRegistry.getType(ProtocolUtils.readVarInt(buf), version); + Object value = ((DataComponentRegistry.DataComponentTypeImpl) type).codec().decode(buf, version); + result.add(value == Unit.INSTANCE + ? new AdventureModePredicate.DataComponentExactPredicate.NonValued((DataComponentType.NonValued) type) + : new AdventureModePredicate.DataComponentExactPredicate.Valued<>((DataComponentType.Valued<@NonNull Object>) type, value) + ); + } + + return result; + } + + @Override + @SuppressWarnings("unchecked") + public void encode(ByteBuf buf, ProtocolVersion version, Collection value) { + Object[] result = value.stream().map(predicate -> { + int id = DataComponentRegistry.getId(predicate.type(), version); + if (id == Integer.MIN_VALUE) { + return null; + } + + return IntObjectImmutablePair.of(id, predicate); + }).filter(Objects::nonNull).toArray(); + ProtocolUtils.writeVarInt(buf, result.length); + for (Object _pair : result) { + var pair = (IntObjectImmutablePair) _pair; + ProtocolUtils.writeVarInt(buf, pair.leftInt()); + AdventureModePredicate.DataComponentExactPredicate predicate = pair.right(); + ((DataComponentRegistry.DataComponentTypeImpl) predicate.type()).codec().encode(buf, version, predicate instanceof AdventureModePredicate.DataComponentExactPredicate.NonValued + ? Unit.INSTANCE + : ((AdventureModePredicate.DataComponentExactPredicate.Valued<@NonNull Object>) predicate).value() + ); + } + } + }; + StreamCodec PARTIAL_PREDICATE_CODEC = StreamCodec.composite( + ByteBufCodecs.VAR_INT, AdventureModePredicate.DataComponentPartialPredicate::id, + ByteBufCodecs.TAG, AdventureModePredicate.DataComponentPartialPredicate::predicate, + AdventureModePredicate.DataComponentPartialPredicate::new + ); + StreamCodec CODEC = StreamCodec.composite( + DataComponentMatchersCodec.EXACT_PREDICATE_CODEC, AdventureModePredicate.DataComponentMatchers::exact, + ByteBufCodecs.collection(DataComponentMatchersCodec.PARTIAL_PREDICATE_CODEC, 64), AdventureModePredicate.DataComponentMatchers::partial, + AdventureModePredicate.DataComponentMatchers::new + ); + } + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/ArmorTrimCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/ArmorTrimCodec.java new file mode 100644 index 00000000..4918ef4d --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/ArmorTrimCodec.java @@ -0,0 +1,21 @@ +package net.elytrium.limboapi.server.item.codec; + +import com.velocitypowered.api.network.ProtocolVersion; +import net.elytrium.limboapi.api.world.item.datacomponent.type.ArmorTrim; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import net.elytrium.limboapi.server.item.codec.data.HolderCodec; +import net.elytrium.limboapi.server.item.codec.data.TrimMaterialCodec; +import net.elytrium.limboapi.server.item.codec.data.TrimPatternCodec; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface ArmorTrimCodec { + + StreamCodec CODEC = StreamCodec.composite( + TrimMaterialCodec.HOLDER_CODEC, ArmorTrim::material, + HolderCodec.codec(TrimPatternCodec.CODEC), ArmorTrim::pattern, + StreamCodec.lt(ProtocolVersion.MINECRAFT_1_21_5, ByteBufCodecs.BOOL, true), ArmorTrim::showInTooltip, + ArmorTrim::new + ); +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/AttributeModifiersCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/AttributeModifiersCodec.java new file mode 100644 index 00000000..a5b618de --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/AttributeModifiersCodec.java @@ -0,0 +1,73 @@ +package net.elytrium.limboapi.server.item.codec; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import net.elytrium.limboapi.api.world.item.datacomponent.type.AttributeModifiers; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import net.elytrium.limboapi.server.item.codec.data.ComponentHolderCodec; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface AttributeModifiersCodec { + + StreamCodec CODEC = StreamCodec.composite( + ByteBufCodecs.collection(EntryCodec.CODEC), AttributeModifiers::modifiers, + StreamCodec.lt(ProtocolVersion.MINECRAFT_1_21_5, ByteBufCodecs.BOOL, true), AttributeModifiers::showInTooltip, + AttributeModifiers::new + ); + + interface EntryCodec { + + StreamCodec CODEC = StreamCodec.composite( + ByteBufCodecs.VAR_INT, AttributeModifiers.Entry::attribute, + AttributeModifierCodec.CODEC, AttributeModifiers.Entry::modifier, + ByteBufCodecs.VAR_INT, AttributeModifiers.Entry::slot, + StreamCodec.ge(ProtocolVersion.MINECRAFT_1_21_6, DisplayCodec.CODEC, AttributeModifiers.Display.Default.INSTANCE), AttributeModifiers.Entry::display, + AttributeModifiers.Entry::new + ); + + interface AttributeModifierCodec { + + StreamCodec CODEC = StreamCodec.composite( + StreamCodec.lt(ProtocolVersion.MINECRAFT_1_21, ByteBufCodecs.UUID), AttributeModifiers.AttributeModifier::uuid, + ByteBufCodecs.STRING_UTF8, AttributeModifiers.AttributeModifier::name, + ByteBufCodecs.DOUBLE, AttributeModifiers.AttributeModifier::amount, + ByteBufCodecs.VAR_INT, AttributeModifiers.AttributeModifier::operation, + AttributeModifiers.AttributeModifier::new + ); + } + + interface DisplayCodec { + + StreamCodec CODEC = new StreamCodec<>() { + + @Override + public AttributeModifiers.Display decode(ByteBuf buf, ProtocolVersion version) { + int id = ProtocolUtils.readVarInt(buf); + return switch (id) { + case 0 -> AttributeModifiers.Display.Default.INSTANCE; + case 1 -> AttributeModifiers.Display.Hidden.INSTANCE; + case 2 -> new AttributeModifiers.Display.OverrideText(ComponentHolderCodec.CODEC.decode(buf, version)); + default -> throw new IllegalStateException("Unexpected value: " + id); + }; + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, AttributeModifiers.Display value) { + if (value instanceof AttributeModifiers.Display.Default) { + ProtocolUtils.writeVarInt(buf, 0); + } else if (value instanceof AttributeModifiers.Display.Hidden) { + ProtocolUtils.writeVarInt(buf, 1); + } else if (value instanceof AttributeModifiers.Display.OverrideText overrideText) { + ProtocolUtils.writeVarInt(buf, 2); + ComponentHolderCodec.CODEC.encode(buf, version, overrideText.component()); + } else { + throw new IllegalArgumentException(value.getClass().getName()); + } + } + }; + } + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/BannerPatternLayerCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/BannerPatternLayerCodec.java new file mode 100644 index 00000000..3734aeed --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/BannerPatternLayerCodec.java @@ -0,0 +1,26 @@ +package net.elytrium.limboapi.server.item.codec; + +import net.elytrium.limboapi.api.world.item.datacomponent.type.BannerPatternLayer; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import net.elytrium.limboapi.server.item.codec.data.HolderCodec; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface BannerPatternLayerCodec { + + StreamCodec CODEC = StreamCodec.composite( + HolderCodec.codec(BannerPatternCodec.CODEC), BannerPatternLayer::pattern, + ByteBufCodecs.VAR_INT, BannerPatternLayer::color, + BannerPatternLayer::new + ); + + interface BannerPatternCodec { + + StreamCodec CODEC = StreamCodec.composite( + ByteBufCodecs.STRING_UTF8, BannerPatternLayer.BannerPattern::assetId, + ByteBufCodecs.STRING_UTF8, BannerPatternLayer.BannerPattern::translationKey, + BannerPatternLayer.BannerPattern::new + ); + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/BeehiveOccupantCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/BeehiveOccupantCodec.java new file mode 100644 index 00000000..0a122e9a --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/BeehiveOccupantCodec.java @@ -0,0 +1,18 @@ +package net.elytrium.limboapi.server.item.codec; + +import net.elytrium.limboapi.api.world.item.datacomponent.type.BeehiveOccupant; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import net.elytrium.limboapi.server.item.codec.data.TypedEntityDataCodec; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface BeehiveOccupantCodec { + + StreamCodec CODEC = StreamCodec.composite( + TypedEntityDataCodec.TYPED_ENTITY_DATA_CODEC, BeehiveOccupant::entityData, + ByteBufCodecs.VAR_INT, BeehiveOccupant::ticksInHive, + ByteBufCodecs.VAR_INT, BeehiveOccupant::minTicksInHive, + BeehiveOccupant::new + ); +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/BlocksAttacksCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/BlocksAttacksCodec.java new file mode 100644 index 00000000..bc44e395 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/BlocksAttacksCodec.java @@ -0,0 +1,44 @@ +package net.elytrium.limboapi.server.item.codec; + +import net.elytrium.limboapi.api.world.item.datacomponent.type.BlocksAttacks; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import net.elytrium.limboapi.server.item.codec.data.HolderSetCodec; +import net.elytrium.limboapi.server.item.codec.data.SoundEventCodec; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface BlocksAttacksCodec { + + StreamCodec CODEC = StreamCodec.composite( + ByteBufCodecs.FLOAT, BlocksAttacks::blockDelaySeconds, + ByteBufCodecs.FLOAT, BlocksAttacks::disableCooldownScale, + ByteBufCodecs.collection(DamageReductionCodec.CODEC), BlocksAttacks::damageReductions, + ItemDamageFunctionCodec.CODEC, BlocksAttacks::itemDamage, + ByteBufCodecs.OPTIONAL_STRING_UTF8, BlocksAttacks::bypassedBy, + SoundEventCodec.OPTIONAL_HOLDER_CODEC, BlocksAttacks::blockSound, + SoundEventCodec.OPTIONAL_HOLDER_CODEC, BlocksAttacks::disableSound, + BlocksAttacks::new + ); + + interface DamageReductionCodec { + + StreamCodec CODEC = StreamCodec.composite( + ByteBufCodecs.FLOAT, BlocksAttacks.DamageReduction::horizontalBlockingAngle, + HolderSetCodec.OPTIONAL_CODEC, BlocksAttacks.DamageReduction::type, + ByteBufCodecs.FLOAT, BlocksAttacks.DamageReduction::base, + ByteBufCodecs.FLOAT, BlocksAttacks.DamageReduction::factor, + BlocksAttacks.DamageReduction::new + ); + } + + interface ItemDamageFunctionCodec { + + StreamCodec CODEC = StreamCodec.composite( + ByteBufCodecs.FLOAT, BlocksAttacks.ItemDamageFunction::threshold, + ByteBufCodecs.FLOAT, BlocksAttacks.ItemDamageFunction::base, + ByteBufCodecs.FLOAT, BlocksAttacks.ItemDamageFunction::factor, + BlocksAttacks.ItemDamageFunction::new + ); + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/ConsumableCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/ConsumableCodec.java new file mode 100644 index 00000000..7c4e6a75 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/ConsumableCodec.java @@ -0,0 +1,21 @@ +package net.elytrium.limboapi.server.item.codec; + +import net.elytrium.limboapi.api.world.item.datacomponent.type.Consumable; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import net.elytrium.limboapi.server.item.codec.data.ConsumeEffectCodec; +import net.elytrium.limboapi.server.item.codec.data.SoundEventCodec; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface ConsumableCodec { + + StreamCodec CODEC = StreamCodec.composite( + ByteBufCodecs.FLOAT, Consumable::consumeSeconds, + ByteBufCodecs.VAR_INT, Consumable::animation, + SoundEventCodec.HOLDER_CODEC, Consumable::sound, + ByteBufCodecs.BOOL, Consumable::hasConsumeParticles, + ConsumeEffectCodec.COLLECTION_CODEC, Consumable::onConsumeEffects, + Consumable::new + ); +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/CustomModelDataCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/CustomModelDataCodec.java new file mode 100644 index 00000000..cca6bf9c --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/CustomModelDataCodec.java @@ -0,0 +1,39 @@ +package net.elytrium.limboapi.server.item.codec; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import java.util.function.Function; +import net.elytrium.limboapi.api.world.item.datacomponent.type.CustomModelData; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface CustomModelDataCodec { + + StreamCodec CODEC = new StreamCodec<>() { + + private static final StreamCodec CODEC = StreamCodec.composite( + ByteBufCodecs.list(ByteBufCodecs.FLOAT.map(Function.identity(), Number::floatValue)), CustomModelData::floats, + ByteBufCodecs.list(ByteBufCodecs.BOOL), CustomModelData::flags, + ByteBufCodecs.list(ByteBufCodecs.STRING_UTF8), CustomModelData::strings, + ByteBufCodecs.list(ByteBufCodecs.INT), CustomModelData::colors, + CustomModelData::new + ); + + @Override + public CustomModelData decode(ByteBuf buf, ProtocolVersion version) { + return version.noLessThan(ProtocolVersion.MINECRAFT_1_21_4) ? CODEC.decode(buf, version) : new CustomModelData(ProtocolUtils.readVarInt(buf)); + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, CustomModelData value) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_4)) { + CODEC.encode(buf, version, value); + } else { + ProtocolUtils.writeVarInt(buf, value.floats().get(0).intValue()); + } + } + }; +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/protocol/data/StorageUtils.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/DyedColorCodec.java similarity index 58% rename from plugin/src/main/java/net/elytrium/limboapi/protocol/data/StorageUtils.java rename to plugin/src/main/java/net/elytrium/limboapi/server/item/codec/DyedColorCodec.java index 65cbc9a7..29caaee9 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/protocol/data/StorageUtils.java +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/DyedColorCodec.java @@ -15,23 +15,20 @@ * along with this program. If not, see . */ -package net.elytrium.limboapi.protocol.data; +package net.elytrium.limboapi.server.item.codec; import com.velocitypowered.api.network.ProtocolVersion; +import net.elytrium.limboapi.api.world.item.datacomponent.type.DyedColor; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import org.jspecify.annotations.NullMarked; -public class StorageUtils { +@NullMarked +public interface DyedColorCodec { - public static int fixBitsPerEntry(ProtocolVersion version, int bitsPerEntry) { - if (bitsPerEntry < 4) { - return 4; - } else if (bitsPerEntry < 9) { - return bitsPerEntry; - } else if (version.compareTo(ProtocolVersion.MINECRAFT_1_13) < 0) { - return 13; - } else if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_4) < 0) { - return 14; - } else { - return 15; - } - } + StreamCodec CODEC = StreamCodec.composite( + ByteBufCodecs.INT, DyedColor::rgb, + StreamCodec.lt(ProtocolVersion.MINECRAFT_1_21_5, ByteBufCodecs.BOOL, true), DyedColor::showInTooltip, + DyedColor::new + ); } diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/BooleanItemComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/EnchantmentsCodec.java similarity index 52% rename from plugin/src/main/java/net/elytrium/limboapi/server/item/type/BooleanItemComponent.java rename to plugin/src/main/java/net/elytrium/limboapi/server/item/codec/EnchantmentsCodec.java index 32672c16..6d9f4026 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/BooleanItemComponent.java +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/EnchantmentsCodec.java @@ -15,19 +15,21 @@ * along with this program. If not, see . */ -package net.elytrium.limboapi.server.item.type; +package net.elytrium.limboapi.server.item.codec; import com.velocitypowered.api.network.ProtocolVersion; -import io.netty.buffer.ByteBuf; +import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; +import net.elytrium.limboapi.api.world.item.datacomponent.type.Enchantments; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import org.jspecify.annotations.NullMarked; -public class BooleanItemComponent extends WriteableItemComponent { +@NullMarked +public interface EnchantmentsCodec { - public BooleanItemComponent(String name) { - super(name); - } - - @Override - public void write(ProtocolVersion version, ByteBuf buffer) { - buffer.writeBoolean(this.getValue()); - } + StreamCodec CODEC = StreamCodec.composite( + ByteBufCodecs.map(Int2IntOpenHashMap::new, ByteBufCodecs.VAR_INT, ByteBufCodecs.VAR_INT), Enchantments::enchantments, + StreamCodec.lt(ProtocolVersion.MINECRAFT_1_21_5, ByteBufCodecs.BOOL, true), Enchantments::showInTooltip, + Enchantments::new + ); } diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/EquippableCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/EquippableCodec.java new file mode 100644 index 00000000..5b773045 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/EquippableCodec.java @@ -0,0 +1,29 @@ +package net.elytrium.limboapi.server.item.codec; + +import com.velocitypowered.api.network.ProtocolVersion; +import net.elytrium.limboapi.api.world.item.datacomponent.type.Equippable; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.Holder; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import net.elytrium.limboapi.server.item.codec.data.HolderSetCodec; +import net.elytrium.limboapi.server.item.codec.data.SoundEventCodec; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface EquippableCodec { + + StreamCodec CODEC = StreamCodec.composite( + ByteBufCodecs.VAR_INT, Equippable::slot, + SoundEventCodec.HOLDER_CODEC, Equippable::equipSound, + ByteBufCodecs.OPTIONAL_STRING_UTF8, Equippable::assetId, + ByteBufCodecs.OPTIONAL_STRING_UTF8, Equippable::cameraOverlay, + HolderSetCodec.OPTIONAL_CODEC, Equippable::allowedEntities, + ByteBufCodecs.BOOL, Equippable::dispensable, + ByteBufCodecs.BOOL, Equippable::swappable, + ByteBufCodecs.BOOL, Equippable::damageOnHurt, + StreamCodec.ge(ProtocolVersion.MINECRAFT_1_21_5, ByteBufCodecs.BOOL, true), Equippable::equipOnInteract, + StreamCodec.ge(ProtocolVersion.MINECRAFT_1_21_6, ByteBufCodecs.BOOL, false), Equippable::canBeSheared, + StreamCodec.ge(ProtocolVersion.MINECRAFT_1_21_6, SoundEventCodec.HOLDER_CODEC, Holder.ref(0)), Equippable::shearingSound, + Equippable::new + ); +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/FireworkExplosionCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/FireworkExplosionCodec.java new file mode 100644 index 00000000..cd2662fa --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/FireworkExplosionCodec.java @@ -0,0 +1,19 @@ +package net.elytrium.limboapi.server.item.codec; + +import net.elytrium.limboapi.api.world.item.datacomponent.type.FireworkExplosion; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface FireworkExplosionCodec { + + StreamCodec CODEC = StreamCodec.composite( + ByteBufCodecs.VAR_INT, FireworkExplosion::shape, + ByteBufCodecs.INT_COLLECTION, FireworkExplosion::colors, + ByteBufCodecs.INT_COLLECTION, FireworkExplosion::fadeColors, + ByteBufCodecs.BOOL, FireworkExplosion::hasTrail, + ByteBufCodecs.BOOL, FireworkExplosion::hasTwinkle, + FireworkExplosion::new + ); +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/FireworksCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/FireworksCodec.java new file mode 100644 index 00000000..551468c5 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/FireworksCodec.java @@ -0,0 +1,16 @@ +package net.elytrium.limboapi.server.item.codec; + +import net.elytrium.limboapi.api.world.item.datacomponent.type.Fireworks; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface FireworksCodec { + + StreamCodec CODEC = StreamCodec.composite( + ByteBufCodecs.VAR_INT, Fireworks::flightDuration, + ByteBufCodecs.collection(FireworkExplosionCodec.CODEC, 256), Fireworks::explosions, + Fireworks::new + ); +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/FoodPropertiesCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/FoodPropertiesCodec.java new file mode 100644 index 00000000..857dd645 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/FoodPropertiesCodec.java @@ -0,0 +1,34 @@ +package net.elytrium.limboapi.server.item.codec; + +import com.velocitypowered.api.network.ProtocolVersion; +import net.elytrium.limboapi.api.protocol.data.ItemStack; +import net.elytrium.limboapi.api.world.item.datacomponent.type.FoodProperties; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import net.elytrium.limboapi.server.item.codec.data.ItemStackCodec; +import net.elytrium.limboapi.server.item.codec.data.MobEffectCodec; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +public interface FoodPropertiesCodec { + + StreamCodec CODEC = StreamCodec.composite( + ByteBufCodecs.VAR_INT, FoodProperties::nutrition, + ByteBufCodecs.FLOAT, FoodProperties::saturation, + ByteBufCodecs.BOOL, FoodProperties::canAlwaysEat, + StreamCodec.lt(ProtocolVersion.MINECRAFT_1_21_2, ByteBufCodecs.FLOAT, 1.6F), FoodProperties::eatSeconds, + StreamCodec.<@Nullable ItemStack>eq(ProtocolVersion.MINECRAFT_1_21, ByteBufCodecs.optional(ItemStackCodec.CODEC)), FoodProperties::usingConvertsTo, + StreamCodec.lt(ProtocolVersion.MINECRAFT_1_21_2, ByteBufCodecs.collection(PossibleEffectCodec.CODEC)), FoodProperties::effects, + FoodProperties::new + ); + + interface PossibleEffectCodec { + + StreamCodec CODEC = StreamCodec.composite( + MobEffectCodec.CODEC, FoodProperties.PossibleEffect::effect, + ByteBufCodecs.FLOAT, FoodProperties.PossibleEffect::probability, + FoodProperties.PossibleEffect::new + ); + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/InstrumentCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/InstrumentCodec.java new file mode 100644 index 00000000..6e952080 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/InstrumentCodec.java @@ -0,0 +1,82 @@ +package net.elytrium.limboapi.server.item.codec; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import net.elytrium.limboapi.api.world.item.datacomponent.type.Instrument; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.Either; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.Holder; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import net.elytrium.limboapi.server.item.codec.data.ComponentHolderCodec; +import net.elytrium.limboapi.server.item.codec.data.HolderCodec; +import net.elytrium.limboapi.server.item.codec.data.SoundEventCodec; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface InstrumentCodec { + + StreamCodec CODEC = StreamCodec.composite( + SoundEventCodec.HOLDER_CODEC, Instrument::soundEvent, + new StreamCodec<>() { + + @Override + public Number decode(ByteBuf buf, ProtocolVersion version) { + return version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2) ? buf.readFloat() : ProtocolUtils.readVarInt(buf); + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, Number value) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) { + buf.writeFloat(value.floatValue()); + } else { + ProtocolUtils.writeVarInt(buf, value.intValue()); + } + } + }, Instrument::useDuration, + ByteBufCodecs.FLOAT, Instrument::range, + StreamCodec.ge(ProtocolVersion.MINECRAFT_1_21_2, ComponentHolderCodec.CODEC), Instrument::description, + Instrument::new + ); + StreamCodec> HOLDER_CODEC = HolderCodec.codec(InstrumentCodec.CODEC); + StreamCodec, String>> EITHER_CODEC = new StreamCodec<>() { + + @Override + public Either, String> decode(ByteBuf buf, ProtocolVersion version) { + return version.lessThan(ProtocolVersion.MINECRAFT_1_21_5) || buf.readBoolean() + ? Either.left(InstrumentCodec.HOLDER_CODEC.decode(buf, version)) + : Either.right(ByteBufCodecs.STRING_UTF8.decode(buf, version)); + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, Either, String> value) { + value.ifLeft(left -> { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_5)) { + buf.writeBoolean(true); + } + + InstrumentCodec.HOLDER_CODEC.encode(buf, version, left); + }).ifRight(right -> { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_5)) { + buf.writeBoolean(false); + ByteBufCodecs.STRING_UTF8.encode(buf, version, right); + } else { + // untested btw + int id; + switch (right) { + case "minecraft:ponder_goat_horn" -> id = 0; + case "minecraft:sing_goat_horn" -> id = 1; + case "minecraft:seek_goat_horn" -> id = 2; + case "minecraft:feel_goat_horn" -> id = 3; + case "minecraft:admire_goat_horn" -> id = 4; + case "minecraft:call_goat_horn" -> id = 5; + case "minecraft:yearn_goat_horn" -> id = 6; + case "minecraft:dream_goat_horn" -> id = 7; + default -> id = 0; // TODO warn + } + InstrumentCodec.HOLDER_CODEC.encode(buf, version, Holder.ref(id)); + } + }); + } + }; +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/JukeboxPlayableCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/JukeboxPlayableCodec.java new file mode 100644 index 00000000..a8e900f0 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/JukeboxPlayableCodec.java @@ -0,0 +1,32 @@ +package net.elytrium.limboapi.server.item.codec; + +import com.velocitypowered.api.network.ProtocolVersion; +import net.elytrium.limboapi.api.world.item.datacomponent.type.JukeboxPlayable; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import net.elytrium.limboapi.server.item.codec.data.ComponentHolderCodec; +import net.elytrium.limboapi.server.item.codec.data.EitherCodec; +import net.elytrium.limboapi.server.item.codec.data.HolderCodec; +import net.elytrium.limboapi.server.item.codec.data.SoundEventCodec; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface JukeboxPlayableCodec { + + StreamCodec CODEC = StreamCodec.composite( + EitherCodec.codec(HolderCodec.codec(JukeboxSongCodec.CODEC), ByteBufCodecs.STRING_UTF8), JukeboxPlayable::song, + StreamCodec.lt(ProtocolVersion.MINECRAFT_1_21_5, ByteBufCodecs.BOOL, true), JukeboxPlayable::showInTooltip, + JukeboxPlayable::new + ); + + interface JukeboxSongCodec { + + StreamCodec CODEC = StreamCodec.composite( + SoundEventCodec.HOLDER_CODEC, JukeboxPlayable.JukeboxSong::soundEvent, + ComponentHolderCodec.CODEC, JukeboxPlayable.JukeboxSong::description, + ByteBufCodecs.FLOAT, JukeboxPlayable.JukeboxSong::lengthInSeconds, + ByteBufCodecs.VAR_INT, JukeboxPlayable.JukeboxSong::comparatorOutput, + JukeboxPlayable.JukeboxSong::new + ); + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/LodestoneTrackerCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/LodestoneTrackerCodec.java new file mode 100644 index 00000000..761dae6e --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/LodestoneTrackerCodec.java @@ -0,0 +1,17 @@ +package net.elytrium.limboapi.server.item.codec; + +import net.elytrium.limboapi.api.world.item.datacomponent.type.LodestoneTracker; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import net.elytrium.limboapi.server.item.codec.data.GlobalPosCodec; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface LodestoneTrackerCodec { + + StreamCodec CODEC = StreamCodec.composite( + GlobalPosCodec.OPTIONAL_CODEC, LodestoneTracker::target, + ByteBufCodecs.BOOL, LodestoneTracker::tracked, + LodestoneTracker::new + ); +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/PaintingVariantCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/PaintingVariantCodec.java new file mode 100644 index 00000000..41333ec9 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/PaintingVariantCodec.java @@ -0,0 +1,24 @@ +package net.elytrium.limboapi.server.item.codec; + +import com.velocitypowered.api.network.ProtocolVersion; +import net.elytrium.limboapi.api.world.item.datacomponent.type.PaintingVariant; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.Holder; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import net.elytrium.limboapi.server.item.codec.data.ComponentHolderCodec; +import net.elytrium.limboapi.server.item.codec.data.HolderCodec; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface PaintingVariantCodec { + + StreamCodec CODEC = StreamCodec.composite( + ByteBufCodecs.VAR_INT, PaintingVariant::width, + ByteBufCodecs.VAR_INT, PaintingVariant::height, + ByteBufCodecs.STRING_UTF8, PaintingVariant::assetId, + StreamCodec.ge(ProtocolVersion.MINECRAFT_1_21_2, ComponentHolderCodec.OPTIONAL_CODEC), PaintingVariant::title, + StreamCodec.ge(ProtocolVersion.MINECRAFT_1_21_2, ComponentHolderCodec.OPTIONAL_CODEC), PaintingVariant::author, + PaintingVariant::new + ); + StreamCodec> HOLDER_CODEC = HolderCodec.codec(PaintingVariantCodec.CODEC); +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/PotionContentsCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/PotionContentsCodec.java new file mode 100644 index 00000000..e6d2c7ff --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/PotionContentsCodec.java @@ -0,0 +1,20 @@ +package net.elytrium.limboapi.server.item.codec; + +import com.velocitypowered.api.network.ProtocolVersion; +import net.elytrium.limboapi.api.world.item.datacomponent.type.PotionContents; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import net.elytrium.limboapi.server.item.codec.data.MobEffectCodec; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface PotionContentsCodec { + + StreamCodec CODEC = StreamCodec.composite( + ByteBufCodecs.optional(ByteBufCodecs.VAR_INT), PotionContents::potion, + ByteBufCodecs.optional(ByteBufCodecs.INT), PotionContents::customColor, + MobEffectCodec.COLLECTION_CODEC, PotionContents::customEffects, + StreamCodec.ge(ProtocolVersion.MINECRAFT_1_21_2, ByteBufCodecs.OPTIONAL_STRING_UTF8), PotionContents::customName, + PotionContents::new + ); +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/ResolvableProfileCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/ResolvableProfileCodec.java new file mode 100644 index 00000000..d1421490 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/ResolvableProfileCodec.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2021 - 2024 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limboapi.server.item.codec; + +import com.velocitypowered.api.network.ProtocolVersion; +import io.netty.buffer.ByteBuf; +import java.util.Collection; +import net.elytrium.limboapi.api.world.item.datacomponent.type.ResolvableProfile; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface ResolvableProfileCodec { + + StreamCodec GAME_PROFILE_PROPERTY_CODEC = StreamCodec.composite( + ByteBufCodecs.stringUtf8(64), ResolvableProfile.Property::name, + ByteBufCodecs.STRING_UTF8, ResolvableProfile.Property::value, + ByteBufCodecs.OPTIONAL_STRING_UTF8_1024, ResolvableProfile.Property::signature, + ResolvableProfile.Property::new + ); + StreamCodec> GAME_PROFILE_PROPERTY_COLLECTION_CODEC = ByteBufCodecs.collection(ResolvableProfileCodec.GAME_PROFILE_PROPERTY_CODEC, 16); + StreamCodec RESOLVED_GAME_PROFILE_CODEC = StreamCodec.composite( + ByteBufCodecs.UUID, ResolvableProfile.GameProfile::id, + ByteBufCodecs.PLAYER_NAME, ResolvableProfile.GameProfile::name, + ResolvableProfileCodec.GAME_PROFILE_PROPERTY_COLLECTION_CODEC, ResolvableProfile.GameProfile::properties, + ResolvableProfile.ResolvedGameProfile::new + ); + StreamCodec PARTIAL_GAME_PROFILE_CODEC = StreamCodec.composite( + ByteBufCodecs.OPTIONAL_PLAYER_NAME, ResolvableProfile.GameProfile::name, + ByteBufCodecs.OPTIONAL_UUID, ResolvableProfile.GameProfile::id, + ResolvableProfileCodec.GAME_PROFILE_PROPERTY_COLLECTION_CODEC, ResolvableProfile.GameProfile::properties, + ResolvableProfile.PartialGameProfile::new + ); + StreamCodec PLAYER_SKIN_PATCH_CODEC = StreamCodec.composite( + ByteBufCodecs.OPTIONAL_STRING_UTF8, ResolvableProfile.PlayerSkinPatch::body, + ByteBufCodecs.OPTIONAL_STRING_UTF8, ResolvableProfile.PlayerSkinPatch::cape, + ByteBufCodecs.OPTIONAL_STRING_UTF8, ResolvableProfile.PlayerSkinPatch::elytra, + ByteBufCodecs.optional(ByteBufCodecs.BOOL), ResolvableProfile.PlayerSkinPatch::model, + ResolvableProfile.PlayerSkinPatch::new + ); + StreamCodec CODEC = new StreamCodec<>() { + + @Override + public ResolvableProfile decode(ByteBuf buf, ProtocolVersion version) { + ResolvableProfile.GameProfile profile; + ResolvableProfile.PlayerSkinPatch patch; + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_9)) { + profile = buf.readBoolean() + ? ResolvableProfileCodec.RESOLVED_GAME_PROFILE_CODEC.decode(buf, version) + : ResolvableProfileCodec.PARTIAL_GAME_PROFILE_CODEC.decode(buf, version); + patch = ResolvableProfileCodec.PLAYER_SKIN_PATCH_CODEC.decode(buf, version); + } else { + profile = ResolvableProfileCodec.PARTIAL_GAME_PROFILE_CODEC.decode(buf, version); + patch = ResolvableProfile.PlayerSkinPatch.EMPTY; + } + + return new ResolvableProfile(profile, patch); + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, ResolvableProfile value) { + ResolvableProfile.GameProfile profile = value.gameProfile(); + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_9)) { + if (profile instanceof ResolvableProfile.ResolvedGameProfile) { + buf.writeBoolean(true); + ResolvableProfileCodec.RESOLVED_GAME_PROFILE_CODEC.encode(buf, version, profile); + } else { + buf.writeBoolean(false); + ResolvableProfileCodec.PARTIAL_GAME_PROFILE_CODEC.encode(buf, version, profile); + } + ResolvableProfileCodec.PLAYER_SKIN_PATCH_CODEC.encode(buf, version, value.skinPatch()); + } else { + ResolvableProfileCodec.PARTIAL_GAME_PROFILE_CODEC.encode(buf, version, profile); + } + } + }; +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/SuspiciousStewEffectCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/SuspiciousStewEffectCodec.java new file mode 100644 index 00000000..0cb0af69 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/SuspiciousStewEffectCodec.java @@ -0,0 +1,16 @@ +package net.elytrium.limboapi.server.item.codec; + +import net.elytrium.limboapi.api.world.item.datacomponent.type.SuspiciousStewEffect; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface SuspiciousStewEffectCodec { + + StreamCodec CODEC = StreamCodec.composite( + ByteBufCodecs.VAR_INT, SuspiciousStewEffect::effect, + ByteBufCodecs.VAR_INT, SuspiciousStewEffect::duration, + SuspiciousStewEffect::new + ); +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/ToolCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/ToolCodec.java new file mode 100644 index 00000000..51d1a316 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/ToolCodec.java @@ -0,0 +1,30 @@ +package net.elytrium.limboapi.server.item.codec; + +import com.velocitypowered.api.network.ProtocolVersion; +import net.elytrium.limboapi.api.world.item.datacomponent.type.Tool; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import net.elytrium.limboapi.server.item.codec.data.HolderSetCodec; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface ToolCodec { + + StreamCodec CODEC = StreamCodec.composite( + ByteBufCodecs.collection(RuleCodec.CODEC), Tool::rules, + ByteBufCodecs.FLOAT, Tool::defaultMiningSpeed, + ByteBufCodecs.VAR_INT, Tool::damagePerBlock, + StreamCodec.ge(ProtocolVersion.MINECRAFT_1_21_5, ByteBufCodecs.BOOL, true), Tool::canDestroyBlocksInCreative, + Tool::new + ); + + interface RuleCodec { + + StreamCodec CODEC = StreamCodec.composite( + HolderSetCodec.CODEC, Tool.Rule::blocks, + ByteBufCodecs.optional(ByteBufCodecs.FLOAT), Tool.Rule::speed, + ByteBufCodecs.optional(ByteBufCodecs.BOOL), Tool.Rule::correctForDrops, + Tool.Rule::new + ); + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/TooltipDisplayCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/TooltipDisplayCodec.java new file mode 100644 index 00000000..95e44eae --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/TooltipDisplayCodec.java @@ -0,0 +1,40 @@ +package net.elytrium.limboapi.server.item.codec; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet; +import java.util.Set; +import net.elytrium.limboapi.api.world.item.datacomponent.DataComponentType; +import net.elytrium.limboapi.api.world.item.datacomponent.type.TooltipDisplay; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import net.elytrium.limboapi.server.item.DataComponentRegistry; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface TooltipDisplayCodec { + + StreamCodec CODEC = StreamCodec.composite( + ByteBufCodecs.BOOL, TooltipDisplay::hideTooltip, + new StreamCodec<>() { + + @Override + public Set decode(ByteBuf buf, ProtocolVersion version) { + int count = ProtocolUtils.readVarInt(buf); + var result = new ReferenceLinkedOpenHashSet(count); + for (int i = 0; i < count; ++i) { + result.add(DataComponentRegistry.getType(ProtocolUtils.readVarInt(buf), version)); + } + + return result; + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, Set value) { + ProtocolUtils.writeVarIntArray(buf, value.stream().mapToInt(type -> DataComponentRegistry.getId(type, version)).filter(value1 -> value1 != Integer.MIN_VALUE).toArray()); + } + }, TooltipDisplay::hiddenComponents, + TooltipDisplay::new + ); +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/UseCooldownCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/UseCooldownCodec.java new file mode 100644 index 00000000..7d3ddc30 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/UseCooldownCodec.java @@ -0,0 +1,16 @@ +package net.elytrium.limboapi.server.item.codec; + +import net.elytrium.limboapi.api.world.item.datacomponent.type.UseCooldown; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface UseCooldownCodec { + + StreamCodec CODEC = StreamCodec.composite( + ByteBufCodecs.FLOAT, UseCooldown::seconds, + ByteBufCodecs.OPTIONAL_STRING_UTF8, UseCooldown::cooldownGroup, + UseCooldown::new + ); +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/WeaponCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/WeaponCodec.java new file mode 100644 index 00000000..d5cb396f --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/WeaponCodec.java @@ -0,0 +1,16 @@ +package net.elytrium.limboapi.server.item.codec; + +import net.elytrium.limboapi.api.world.item.datacomponent.type.Weapon; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface WeaponCodec { + + StreamCodec CODEC = StreamCodec.composite( + ByteBufCodecs.VAR_INT, Weapon::itemDamagePerAttack, + ByteBufCodecs.FLOAT, Weapon::disableBlockingForSeconds, + Weapon::new + ); +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/WrittenBookContentCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/WrittenBookContentCodec.java new file mode 100644 index 00000000..5a729a50 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/WrittenBookContentCodec.java @@ -0,0 +1,21 @@ +package net.elytrium.limboapi.server.item.codec; + +import net.elytrium.limboapi.api.world.item.datacomponent.type.WrittenBookContent; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import net.elytrium.limboapi.server.item.codec.data.ComponentHolderCodec; +import net.elytrium.limboapi.server.item.codec.data.FilterableCodec; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface WrittenBookContentCodec { + + StreamCodec CODEC = StreamCodec.composite( + FilterableCodec.codec(ByteBufCodecs.stringUtf8(32)), WrittenBookContent::title, + ByteBufCodecs.STRING_UTF8, WrittenBookContent::author, + ByteBufCodecs.VAR_INT, WrittenBookContent::generation, + ByteBufCodecs.collection(FilterableCodec.codec(ComponentHolderCodec.CODEC)), WrittenBookContent::pages, + ByteBufCodecs.BOOL, WrittenBookContent::resolved, + WrittenBookContent::new + ); +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/BlockPosCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/BlockPosCodec.java new file mode 100644 index 00000000..f1f62828 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/BlockPosCodec.java @@ -0,0 +1,47 @@ +package net.elytrium.limboapi.server.item.codec.data; + +import com.velocitypowered.api.network.ProtocolVersion; +import io.netty.buffer.ByteBuf; +import net.elytrium.limboapi.api.protocol.data.BlockPos; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface BlockPosCodec { + + StreamCodec CODEC = new StreamCodec<>() { + + @Override + public BlockPos decode(ByteBuf buf, ProtocolVersion version) { + if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_8)) { + return new BlockPos(buf.readInt(), buf.readInt(), buf.readInt()); + } else { + long packed = buf.readLong(); + return version.noGreaterThan(ProtocolVersion.MINECRAFT_1_13_2) + ? new BlockPos((int) (packed >> 38), (int) (packed << 26 >> 52), (int) (packed << 38 >> 38)) + : new BlockPos((int) (packed >> 38), (int) (packed << 52 >> 52), (int) (packed << 26 >> 38)); + } + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, BlockPos value) { + BlockPosCodec.encode(buf, version, value.posX(), value.posY(), value.posZ()); + } + }; + StreamCodec<@Nullable BlockPos> OPTIONAL_CODEC = ByteBufCodecs.optional(BlockPosCodec.CODEC); + + static void encode(ByteBuf buf, ProtocolVersion version, int posX, int posY, int posZ) { + if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_7_6)) { + buf.writeInt(posX); + buf.writeInt(posY); + buf.writeInt(posZ); + } else { + buf.writeLong(version.noGreaterThan(ProtocolVersion.MINECRAFT_1_13_2) + ? ((posX & 0x3FFFFFFL) << 38) | ((posY & 0xFFFL) << 26) | (posZ & 0x3FFFFFFL) + : ((posX & 0x3FFFFFFL) << 38) | (posY & 0xFFFL) | ((posZ & 0x3FFFFFFL) << 12) + ); + } + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/BlockStateCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/BlockStateCodec.java new file mode 100644 index 00000000..d53117cc --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/BlockStateCodec.java @@ -0,0 +1,12 @@ +package net.elytrium.limboapi.server.item.codec.data; + +import net.elytrium.limboapi.api.protocol.data.EntityData; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface BlockStateCodec { + + StreamCodec CODEC = ByteBufCodecs.VAR_INT.map(EntityData.BlockState::new, EntityData.BlockState::blockState); +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/ComponentHolderCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/ComponentHolderCodec.java new file mode 100644 index 00000000..32e75122 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/ComponentHolderCodec.java @@ -0,0 +1,65 @@ +package net.elytrium.limboapi.server.item.codec.data; + +import com.google.gson.JsonElement; +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import net.elytrium.limboapi.LimboAPI; +import net.elytrium.limboapi.api.protocol.data.ComponentHolder; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.text.Component; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public class ComponentHolderCodec implements StreamCodec, ComponentHolder.Codec { + + public static final ComponentHolderCodec CODEC = (ComponentHolderCodec) ComponentHolder.CODEC; + public static final StreamCodec<@Nullable ComponentHolder> OPTIONAL_CODEC = ByteBufCodecs.optional(ComponentHolderCodec.CODEC); + + @Override + public ComponentHolder decode(ByteBuf buf, ProtocolVersion version) { + return version.noLessThan(ProtocolVersion.MINECRAFT_1_20_3) + ? new ComponentHolder(ProtocolUtils.readBinaryTag(buf, version, null)) + : version.noLessThan(ProtocolVersion.MINECRAFT_1_13) + ? new ComponentHolder(ProtocolUtils.readString(buf, 0x3FFFF)) + : new ComponentHolder(ProtocolUtils.readString(buf)); + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, ComponentHolder value) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) { + ProtocolUtils.writeBinaryTag(buf, version, value.getBinaryTag(version)); + } else { + ProtocolUtils.writeString(buf, value.getJson(version)); + } + } + + @Override + public Component json2Component(ProtocolVersion version, String json) { + return ProtocolUtils.getJsonChatSerializer(version).deserialize(json); + } + + @Override + public Component binaryTag2Component(ProtocolVersion version, BinaryTag binaryTag) { + JsonElement json = null; + try { + return ProtocolUtils.getJsonChatSerializer(version).deserializeFromTree(json = com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder.deserialize(binaryTag)); + } catch (Exception e) { + LimboAPI.getLogger().error("Error converting binary component to JSON component! Binary: {} JSON: {}", binaryTag, json, e); + throw e; + } + } + + @Override + public String component2Json(ProtocolVersion version, Component component) { + return ProtocolUtils.getJsonChatSerializer(version).serialize(component); + } + + @Override + public BinaryTag component2BinaryTag(ProtocolVersion version, Component component) { + return com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder.serialize(ProtocolUtils.getJsonChatSerializer(version).serializeToTree(component)); + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/ConsumeEffectCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/ConsumeEffectCodec.java new file mode 100644 index 00000000..ee6a1976 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/ConsumeEffectCodec.java @@ -0,0 +1,54 @@ +package net.elytrium.limboapi.server.item.codec.data; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import java.util.Collection; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.ConsumeEffect; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface ConsumeEffectCodec { + + // TODO registry based + StreamCodec CODEC = new StreamCodec<>() { + + @Override + public ConsumeEffect decode(ByteBuf buf, ProtocolVersion version) { + int id = ProtocolUtils.readVarInt(buf); + return switch (id) { + case 0 -> new ConsumeEffect.ApplyStatusEffects(MobEffectCodec.COLLECTION_CODEC.decode(buf, version), buf.readFloat()); + case 1 -> new ConsumeEffect.RemoveStatusEffects(HolderSetCodec.CODEC.decode(buf, version)); + case 2 -> ConsumeEffect.ClearAllStatusEffects.INSTANCE; + case 3 -> new ConsumeEffect.TeleportRandomly(buf.readFloat()); + case 4 -> new ConsumeEffect.PlaySound(SoundEventCodec.HOLDER_CODEC.decode(buf, version)); + default -> throw new IllegalStateException("Unexpected value: " + id); + }; + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, ConsumeEffect value) { + if (value instanceof ConsumeEffect.ApplyStatusEffects applyStatusEffects) { + ProtocolUtils.writeVarInt(buf, 0); + MobEffectCodec.COLLECTION_CODEC.encode(buf, version, applyStatusEffects.effects()); + buf.writeFloat(applyStatusEffects.probability()); + } else if (value instanceof ConsumeEffect.RemoveStatusEffects removeStatusEffects) { + ProtocolUtils.writeVarInt(buf, 1); + HolderSetCodec.CODEC.encode(buf, version, removeStatusEffects.effects()); + } else if (value instanceof ConsumeEffect.ClearAllStatusEffects) { + ProtocolUtils.writeVarInt(buf, 2); + } else if (value instanceof ConsumeEffect.TeleportRandomly teleportRandomly) { + ProtocolUtils.writeVarInt(buf, 3); + buf.writeFloat(teleportRandomly.diameter()); + } else if (value instanceof ConsumeEffect.PlaySound playSound) { + ProtocolUtils.writeVarInt(buf, 4); + SoundEventCodec.HOLDER_CODEC.encode(buf, version, playSound.sound()); + } else { + throw new IllegalArgumentException(value.getClass().getName()); + } + } + }; + StreamCodec> COLLECTION_CODEC = ByteBufCodecs.collection(ConsumeEffectCodec.CODEC); +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/EitherCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/EitherCodec.java new file mode 100644 index 00000000..0e35c4f4 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/EitherCodec.java @@ -0,0 +1,32 @@ +package net.elytrium.limboapi.server.item.codec.data; + +import com.velocitypowered.api.network.ProtocolVersion; +import io.netty.buffer.ByteBuf; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.Either; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface EitherCodec { + + static StreamCodec> codec(StreamCodec leftCodec, StreamCodec rightCodec) { + return new StreamCodec<>() { + + @Override + public Either decode(ByteBuf buf, ProtocolVersion version) { + return buf.readBoolean() ? Either.left(leftCodec.decode(buf, version)) : Either.right(rightCodec.decode(buf, version)); + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, Either value) { + value.ifLeft(left -> { + buf.writeBoolean(true); + leftCodec.encode(buf, version, left); + }).ifRight(right -> { + buf.writeBoolean(false); + rightCodec.encode(buf, version, right); + }); + } + }; + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/FilterableCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/FilterableCodec.java new file mode 100644 index 00000000..a6d9fd45 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/FilterableCodec.java @@ -0,0 +1,23 @@ +package net.elytrium.limboapi.server.item.codec.data; + +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.Filterable; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface FilterableCodec { + + static StreamCodec> codec(StreamCodec codec) { + return FilterableCodec.codec(codec, ByteBufCodecs.optional(codec)); + } + + static StreamCodec> codec(StreamCodec codec, StreamCodec<@Nullable T> optionalCodec) { + return StreamCodec.composite( + codec, Filterable::raw, + optionalCodec, Filterable::filtered, + Filterable::new + ); + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/GlobalPosCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/GlobalPosCodec.java new file mode 100644 index 00000000..03885bef --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/GlobalPosCodec.java @@ -0,0 +1,18 @@ +package net.elytrium.limboapi.server.item.codec.data; + +import net.elytrium.limboapi.api.protocol.data.GlobalPos; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface GlobalPosCodec { + + StreamCodec CODEC = StreamCodec.composite( + ByteBufCodecs.STRING_UTF8, GlobalPos::dimension, + BlockPosCodec.CODEC, GlobalPos::blockPos, + GlobalPos::new + ); + StreamCodec<@Nullable GlobalPos> OPTIONAL_CODEC = ByteBufCodecs.optional(GlobalPosCodec.CODEC); +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/HolderCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/HolderCodec.java new file mode 100644 index 00000000..1cf5b778 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/HolderCodec.java @@ -0,0 +1,36 @@ +package net.elytrium.limboapi.server.item.codec.data; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.Holder; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import org.jspecify.annotations.NullMarked; + +@NullMarked +@SuppressWarnings("unchecked") +public interface HolderCodec { + + static > StreamCodec> codec(StreamCodec codec) { + return new StreamCodec<>() { + + @Override + public Holder decode(ByteBuf buf, ProtocolVersion version) { + int i = ProtocolUtils.readVarInt(buf); + return i == 0 ? codec.decode(buf, version) : Holder.ref(i - 1); + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, Holder value) { + if (value instanceof Holder.Reference reference) { + ProtocolUtils.writeVarInt(buf, reference.id() + 1); + } else if (value instanceof Holder.Direct direct) { + ProtocolUtils.writeVarInt(buf, 0); + codec.encode(buf, version, (T) direct); + } else { + throw new IllegalArgumentException(value.getClass().getName()); + } + } + }; + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/HolderSetCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/HolderSetCodec.java new file mode 100644 index 00000000..49b7998e --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/HolderSetCodec.java @@ -0,0 +1,49 @@ +package net.elytrium.limboapi.server.item.codec.data; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.HolderSet; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface HolderSetCodec { + + StreamCodec CODEC = new StreamCodec<>() { + + @Override + public HolderSet decode(ByteBuf buf, ProtocolVersion version) { + int amount = ProtocolUtils.readVarInt(buf) - 1; + if (amount == -1) { + return new HolderSet.Named(ProtocolUtils.readString(buf)); + } + + int[] blocks = new int[amount]; + for (int i = 0; i < amount; ++i) { + blocks[i] = ProtocolUtils.readVarInt(buf); + } + + return new HolderSet.Direct(blocks); + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, HolderSet value) { + if (value instanceof HolderSet.Named named) { + ProtocolUtils.writeVarInt(buf, 0); + ProtocolUtils.writeString(buf, named.key()); + } else if (value instanceof HolderSet.Direct direct) { + int[] contents = direct.contents(); + ProtocolUtils.writeVarInt(buf, contents.length + 1); + for (int block : contents) { + ProtocolUtils.writeVarInt(buf, block); + } + } else { + throw new IllegalArgumentException(value.getClass().getName()); + } + } + }; + StreamCodec<@Nullable HolderSet> OPTIONAL_CODEC = ByteBufCodecs.optional(HolderSetCodec.CODEC); +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/ItemStackCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/ItemStackCodec.java new file mode 100644 index 00000000..3c705c5d --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/ItemStackCodec.java @@ -0,0 +1,105 @@ +package net.elytrium.limboapi.server.item.codec.data; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.DecoderException; +import io.netty.handler.codec.EncoderException; +import net.elytrium.limboapi.api.protocol.data.ItemStack; +import net.elytrium.limboapi.api.world.WorldVersion; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import net.elytrium.limboapi.server.item.SimpleDataComponentMap; +import net.elytrium.limboapi.server.world.SimpleItem; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface ItemStackCodec { // TODO test every item on every legacy version + + StreamCodec OPTIONAL_CODEC = new StreamCodec<>() { + + @Override + public ItemStack decode(ByteBuf buf, ProtocolVersion version) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) { + int amount = ProtocolUtils.readVarInt(buf); + return amount > 0 + ? new ItemStack(SimpleItem.fromId(version, (short) ProtocolUtils.readVarInt(buf)), amount, SimpleDataComponentMap.read(buf, version)) + : ItemStack.EMPTY; + } else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_13_2)) { + return buf.readBoolean() + ? new ItemStack(SimpleItem.fromId(version, (short) ProtocolUtils.readVarInt(buf)), buf.readByte(), ByteBufCodecs.OPTIONAL_COMPOUND_TAG.decode(buf, version)) + : ItemStack.EMPTY; + } else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_13)) { + short material = buf.readShort(); + return material >= 0 + ? new ItemStack(SimpleItem.fromId(version, material), buf.readByte(), ByteBufCodecs.OPTIONAL_COMPOUND_TAG.decode(buf, version)) + : ItemStack.EMPTY; + } else { + short material = buf.readShort(); + return material >= 0 + ? new ItemStack(SimpleItem.fromId(version, material), buf.readByte(), buf.readShort(), ByteBufCodecs.OPTIONAL_COMPOUND_TAG.decode(buf, version)) + : ItemStack.EMPTY; + } + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, ItemStack value) { + WorldVersion from = WorldVersion.from(version); + boolean hasDamage = version.noGreaterThan(ProtocolVersion.MINECRAFT_1_12_2); + if (value.isEmpty(from, hasDamage)) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) { + ProtocolUtils.writeVarInt(buf, 0); + } else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_13_2)) { + buf.writeBoolean(false); + } else { + buf.writeShort(-1); + } + } else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) { + ProtocolUtils.writeVarInt(buf, value.amount()); + ProtocolUtils.writeVarInt(buf, value.material().itemId(from)); + var map = value.map(); + if (map == null) { + ProtocolUtils.writeVarInt(buf, 0); // added + ProtocolUtils.writeVarInt(buf, 0); // removed + } else { + map.write(buf, version); + } + } else { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_13_2)) { + buf.writeBoolean(true); + ProtocolUtils.writeVarInt(buf, value.material().itemId(from)); + } else { + buf.writeShort(value.material().itemId(from)); + } + + buf.writeByte(value.amount()); + if (hasDamage) { + buf.writeShort(value.data()); + } + + ByteBufCodecs.OPTIONAL_COMPOUND_TAG.encode(buf, version, value.nbt()); + } + } + }; + StreamCodec CODEC = new StreamCodec<>() { + + @Override + public ItemStack decode(ByteBuf buf, ProtocolVersion version) { + ItemStack itemStack = ItemStackCodec.OPTIONAL_CODEC.decode(buf, version); + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5) && itemStack.isEmpty(version, false)) { + throw new DecoderException("Empty ItemStack not allowed"); + } else { + return itemStack; + } + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, ItemStack value) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5) && value.isEmpty(version, false)) { + throw new EncoderException("Empty ItemStack not allowed"); + } else { + ItemStackCodec.OPTIONAL_CODEC.encode(buf, version, value); + } + } + }; +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/MobEffectCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/MobEffectCodec.java new file mode 100644 index 00000000..be07384b --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/MobEffectCodec.java @@ -0,0 +1,32 @@ +package net.elytrium.limboapi.server.item.codec.data; + +import java.util.Collection; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.MobEffect; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface MobEffectCodec { + + StreamCodec CODEC = StreamCodec.composite( + ByteBufCodecs.VAR_INT, MobEffect::effect, + DetailsCodec.CODEC, MobEffect::details, + MobEffect::new + ); + StreamCodec> COLLECTION_CODEC = ByteBufCodecs.collection(MobEffectCodec.CODEC); + + interface DetailsCodec { + + StreamCodec CODEC = StreamCodec.recursive(self -> StreamCodec.composite( + ByteBufCodecs.VAR_INT, MobEffect.Details::amplifier, + ByteBufCodecs.VAR_INT, MobEffect.Details::duration, + ByteBufCodecs.BOOL, MobEffect.Details::ambient, + ByteBufCodecs.BOOL, MobEffect.Details::visible, + ByteBufCodecs.BOOL, MobEffect.Details::showIcon, + ByteBufCodecs.optional(self), MobEffect.Details::hiddenEffect, + MobEffect.Details::new + )); + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/OptionalBlockStateCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/OptionalBlockStateCodec.java new file mode 100644 index 00000000..142a1297 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/OptionalBlockStateCodec.java @@ -0,0 +1,18 @@ +package net.elytrium.limboapi.server.item.codec.data; + +import net.elytrium.limboapi.api.protocol.data.EntityData; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface OptionalBlockStateCodec { + + StreamCodec CODEC = ByteBufCodecs.VAR_INT.map( + id -> EntityData.OptionalBlockState.of(id == 0 ? null : new EntityData.BlockState(id)), + optional -> { + EntityData.BlockState value = optional.blockState(); + return value == null ? 0 : value.blockState(); + } + ); +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/ParticleCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/ParticleCodec.java new file mode 100644 index 00000000..d1032236 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/ParticleCodec.java @@ -0,0 +1,211 @@ +package net.elytrium.limboapi.server.item.codec.data; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import net.elytrium.limboapi.api.protocol.data.BlockPos; +import net.elytrium.limboapi.api.protocol.data.EntityData; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import net.elytrium.limboapi.server.world.SimpleParticlesManager; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface ParticleCodec { + + StreamCodec CODEC = new StreamCodec<>() { + + @Override + public EntityData.Particle decode(ByteBuf buf, ProtocolVersion version) { + int type = ProtocolUtils.readVarInt(buf); + var codec = SimpleParticlesManager.fromProtocolId(version, type).codec(); + return new EntityData.Particle(type, codec == null ? null : codec.decode(buf, version)); + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, EntityData.Particle value) { + int type = value.type(); + ProtocolUtils.writeVarInt(buf, type); + var codec = SimpleParticlesManager.fromProtocolId(version, type).codec(); + if (codec != null) { + codec.encode(buf, version, value.data()); + } + } + }; + StreamCodec COLLECTION_CODEC = ByteBufCodecs.collection(EntityData.Particles::new, ParticleCodec.CODEC); + + interface ParticleDataCodecs { + + StreamCodec POWER_CODEC = StreamCodec.ge( + ProtocolVersion.MINECRAFT_1_21_9, + ByteBufCodecs.FLOAT.map(EntityData.Particle.PowerParticleOption::new, EntityData.Particle.PowerParticleOption::power), + new EntityData.Particle.PowerParticleOption(1.0F) + ); + StreamCodec DUST_PARTICLE_CODEC = new StreamCodec<>() { + + @Override + public EntityData.Particle.DustParticleOptions decode(ByteBuf buf, ProtocolVersion version) { + return version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2) + ? new EntityData.Particle.DustParticleOptions(ProtocolUtils.readVarInt(buf), buf.readFloat()) + : new EntityData.Particle.DustParticleOptions(EntityData.Particle.ColorParticleOption.colorFromFloat(buf.readFloat(), buf.readFloat(), buf.readFloat()), buf.readFloat()); + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, EntityData.Particle.DustParticleOptions value) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) { + ProtocolUtils.writeVarInt(buf, value.color()); + } else { + int color = value.color(); + buf.writeFloat(EntityData.Particle.ColorParticleOption.redFloat(color)); + buf.writeFloat(EntityData.Particle.ColorParticleOption.greenFloat(color)); + buf.writeFloat(EntityData.Particle.ColorParticleOption.blueFloat(color)); + } + + buf.writeFloat(value.scale()); + } + }; + StreamCodec DUST_COLOR_TRANSITION_CODEC = new StreamCodec<>() { + + @Override + public EntityData.Particle.DustColorTransitionOptions decode(ByteBuf buf, ProtocolVersion version) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) { + return new EntityData.Particle.DustColorTransitionOptions(ProtocolUtils.readVarInt(buf), ProtocolUtils.readVarInt(buf), buf.readFloat()); + } + + float fromRed = buf.readFloat(); + float fromGreen = buf.readFloat(); + float fromBlue = buf.readFloat(); + float toRed; + float toGreen; + float toBlue; + float scale; + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) { + toRed = buf.readFloat(); + toGreen = buf.readFloat(); + toBlue = buf.readFloat(); + scale = buf.readFloat(); + } else { + scale = buf.readFloat(); + toRed = buf.readFloat(); + toGreen = buf.readFloat(); + toBlue = buf.readFloat(); + } + + return new EntityData.Particle.DustColorTransitionOptions( + EntityData.Particle.ColorParticleOption.colorFromFloat(fromRed, fromGreen, fromBlue), + EntityData.Particle.ColorParticleOption.colorFromFloat(toRed, toGreen, toBlue), + scale + ); + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, EntityData.Particle.DustColorTransitionOptions value) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) { + ProtocolUtils.writeVarInt(buf, value.fromColor()); + ProtocolUtils.writeVarInt(buf, value.toColor()); + buf.writeFloat(value.scale()); + return; + } + + int fromColor = value.fromColor(); + int toColor = value.toColor(); + buf.writeFloat(EntityData.Particle.ColorParticleOption.redFloat(fromColor)); + buf.writeFloat(EntityData.Particle.ColorParticleOption.greenFloat(fromColor)); + buf.writeFloat(EntityData.Particle.ColorParticleOption.blueFloat(fromColor)); + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) { + buf.writeFloat(EntityData.Particle.ColorParticleOption.redFloat(toColor)); + buf.writeFloat(EntityData.Particle.ColorParticleOption.greenFloat(toColor)); + buf.writeFloat(EntityData.Particle.ColorParticleOption.blueFloat(toColor)); + buf.writeFloat(value.scale()); + } else { + buf.writeFloat(value.scale()); + buf.writeFloat(EntityData.Particle.ColorParticleOption.redFloat(toColor)); + buf.writeFloat(EntityData.Particle.ColorParticleOption.greenFloat(toColor)); + buf.writeFloat(EntityData.Particle.ColorParticleOption.blueFloat(toColor)); + } + } + }; + StreamCodec SPELL_CODEC = StreamCodec.ge(ProtocolVersion.MINECRAFT_1_21_9, + StreamCodec.composite( + ByteBufCodecs.INT, EntityData.Particle.SpellParticleOption::color, + ByteBufCodecs.FLOAT, EntityData.Particle.SpellParticleOption::power, + EntityData.Particle.SpellParticleOption::new + ), new EntityData.Particle.SpellParticleOption(-1, 1.0F) + ); + StreamCodec COLOR_CODEC = ByteBufCodecs.INT.map(EntityData.Particle.ColorParticleOption::new, EntityData.Particle.ColorParticleOption::color); + StreamCodec SCULK_CHARGE = ByteBufCodecs.FLOAT.map(EntityData.Particle.SculkChargeParticleOptions::new, EntityData.Particle.SculkChargeParticleOptions::roll); + StreamCodec VIBRATION_CODEC = new StreamCodec<>() { + + @Override + public EntityData.Particle.VibrationParticle decode(ByteBuf buf, ProtocolVersion version) { + BlockPos origin = version.noGreaterThan(ProtocolVersion.MINECRAFT_1_18_2) ? BlockPosCodec.CODEC.decode(buf, version) : null; + EntityData.Particle.VibrationParticle.PositionSource source; + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) { + int type = ProtocolUtils.readVarInt(buf); + source = switch (type) { + case 0 -> BlockPosCodec.CODEC.decode(buf, version); + case 1 -> EntityPositionSourceCodec.CODEC.decode(buf, version); + default -> throw new IllegalStateException("Unexpected value: " + type); + }; + } else { + String type = ProtocolUtils.readString(buf); + source = switch (type) { + case "minecraft:block" -> BlockPosCodec.CODEC.decode(buf, version); + case "minecraft:entity" -> EntityPositionSourceCodec.CODEC.decode(buf, version); + default -> throw new IllegalStateException("Unexpected value: " + type); + }; + } + + return new EntityData.Particle.VibrationParticle(origin, source, ProtocolUtils.readVarInt(buf)); + } + + @Override + public void encode(ByteBuf buf, ProtocolVersion version, EntityData.Particle.VibrationParticle value) { + if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_18_2)) { + BlockPos origin = value.origin(); + if (origin == null) { + throw new NullPointerException("origin"); + } + + BlockPosCodec.CODEC.encode(buf, version, origin); + } + + if (value.destination() instanceof BlockPos blockPos) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) { + ProtocolUtils.writeVarInt(buf, 0); + } else { + ProtocolUtils.writeString(buf, "minecraft:block"); + } + + BlockPosCodec.CODEC.encode(buf, version, blockPos); + } else if (value.destination() instanceof EntityData.Particle.VibrationParticle.EntityPositionSource entity) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) { + ProtocolUtils.writeVarInt(buf, 1); + } else { + ProtocolUtils.writeString(buf, "minecraft:entity"); + } + + EntityPositionSourceCodec.CODEC.encode(buf, version, entity); + } + + ProtocolUtils.writeVarInt(buf, value.arrivalInTicks()); + } + }; + StreamCodec TRAIL_CODEC = StreamCodec.composite( + Vector3Codec.CODEC, EntityData.Particle.TrailParticleOption::target, + ByteBufCodecs.INT, EntityData.Particle.TrailParticleOption::color, + StreamCodec.ge(ProtocolVersion.MINECRAFT_1_21_4, ByteBufCodecs.VAR_INT, 44), EntityData.Particle.TrailParticleOption::color, + EntityData.Particle.TrailParticleOption::new + ); + StreamCodec SHRIEK_CODEC = ByteBufCodecs.VAR_INT.map(EntityData.Particle.ShriekParticleOption::new, EntityData.Particle.ShriekParticleOption::delay); + + interface EntityPositionSourceCodec { + + StreamCodec CODEC = StreamCodec.composite( + ByteBufCodecs.VAR_INT, EntityData.Particle.VibrationParticle.EntityPositionSource::entityId, + ByteBufCodecs.FLOAT, EntityData.Particle.VibrationParticle.EntityPositionSource::yOffset, + EntityData.Particle.VibrationParticle.EntityPositionSource::new + ); + } + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/QuaternionCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/QuaternionCodec.java new file mode 100644 index 00000000..1ae3264d --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/QuaternionCodec.java @@ -0,0 +1,18 @@ +package net.elytrium.limboapi.server.item.codec.data; + +import net.elytrium.limboapi.api.protocol.data.EntityData; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface QuaternionCodec { + + StreamCodec CODEC = StreamCodec.composite( + ByteBufCodecs.FLOAT, EntityData.Quaternion::x, + ByteBufCodecs.FLOAT, EntityData.Quaternion::y, + ByteBufCodecs.FLOAT, EntityData.Quaternion::z, + ByteBufCodecs.FLOAT, EntityData.Quaternion::w, + EntityData.Quaternion::new + ); +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/RotationsCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/RotationsCodec.java new file mode 100644 index 00000000..55473173 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/RotationsCodec.java @@ -0,0 +1,17 @@ +package net.elytrium.limboapi.server.item.codec.data; + +import net.elytrium.limboapi.api.protocol.data.EntityData; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface RotationsCodec { + + StreamCodec CODEC = StreamCodec.composite( + ByteBufCodecs.FLOAT, EntityData.Rotations::posX, + ByteBufCodecs.FLOAT, EntityData.Rotations::posY, + ByteBufCodecs.FLOAT, EntityData.Rotations::posZ, + EntityData.Rotations::new + ); +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/SoundEventCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/SoundEventCodec.java new file mode 100644 index 00000000..69981d26 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/SoundEventCodec.java @@ -0,0 +1,20 @@ +package net.elytrium.limboapi.server.item.codec.data; + +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.Holder; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.SoundEvent; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +public interface SoundEventCodec { + + StreamCodec CODEC = StreamCodec.composite( + ByteBufCodecs.STRING_UTF8, SoundEvent::soundId, + ByteBufCodecs.FLOAT, SoundEvent::range, + SoundEvent::new + ); + StreamCodec> HOLDER_CODEC = HolderCodec.codec(SoundEventCodec.CODEC); + StreamCodec<@Nullable Holder> OPTIONAL_HOLDER_CODEC = ByteBufCodecs.optional(SoundEventCodec.HOLDER_CODEC); +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/TrimMaterialCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/TrimMaterialCodec.java new file mode 100644 index 00000000..fbcd9ad9 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/TrimMaterialCodec.java @@ -0,0 +1,34 @@ +package net.elytrium.limboapi.server.item.codec.data; + +import com.velocitypowered.api.network.ProtocolVersion; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Map; +import java.util.function.Function; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.Holder; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.TrimMaterial; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface TrimMaterialCodec { + + StreamCodec CODEC = StreamCodec.composite( + ByteBufCodecs.STRING_UTF8, TrimMaterial::assetName, + StreamCodec.lt(ProtocolVersion.MINECRAFT_1_21_5, ByteBufCodecs.VAR_INT, 0), TrimMaterial::ingredient, + StreamCodec.lt(ProtocolVersion.MINECRAFT_1_21_4, ByteBufCodecs.FLOAT, 0.0F), TrimMaterial::itemModelIndex, + StreamCodec.lt(ProtocolVersion.MINECRAFT_1_21_2, + ByteBufCodecs.>map(Object2ObjectOpenHashMap::new, + ByteBufCodecs.VAR_INT.map(Function.identity(), value -> value instanceof Number ? ((Number) value).intValue() : Integer.parseInt(String.valueOf(value))), + ByteBufCodecs.STRING_UTF8 + ), + ByteBufCodecs.>map(Object2ObjectOpenHashMap::new, + ByteBufCodecs.STRING_UTF8.map(Function.identity(), Object::toString), + ByteBufCodecs.STRING_UTF8 + ) + ), TrimMaterial::overrides, + ComponentHolderCodec.CODEC, TrimMaterial::description, + TrimMaterial::new + ); + StreamCodec> HOLDER_CODEC = HolderCodec.codec(TrimMaterialCodec.CODEC); +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/TrimPatternCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/TrimPatternCodec.java new file mode 100644 index 00000000..ad60b3fa --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/TrimPatternCodec.java @@ -0,0 +1,19 @@ +package net.elytrium.limboapi.server.item.codec.data; + +import com.velocitypowered.api.network.ProtocolVersion; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.TrimPattern; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface TrimPatternCodec { + + StreamCodec CODEC = StreamCodec.composite( + ByteBufCodecs.STRING_UTF8, TrimPattern::assetId, + StreamCodec.lt(ProtocolVersion.MINECRAFT_1_21_5, ByteBufCodecs.VAR_INT, 0), TrimPattern::templateItem, + ComponentHolderCodec.CODEC, TrimPattern::description, + ByteBufCodecs.BOOL, TrimPattern::decal, + TrimPattern::new + ); +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/TypedEntityDataCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/TypedEntityDataCodec.java new file mode 100644 index 00000000..b00a325d --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/TypedEntityDataCodec.java @@ -0,0 +1,17 @@ +package net.elytrium.limboapi.server.item.codec.data; + +import com.velocitypowered.api.network.ProtocolVersion; +import net.elytrium.limboapi.api.world.item.datacomponent.type.data.TypedEntityData; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface TypedEntityDataCodec { + + StreamCodec TYPED_ENTITY_DATA_CODEC = StreamCodec.composite( + StreamCodec.ge(ProtocolVersion.MINECRAFT_1_21_9, ByteBufCodecs.VAR_INT, 0), TypedEntityData::type, + ByteBufCodecs.COMPOUND_TAG, TypedEntityData::tag, + TypedEntityData::new + ); +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/Vector3Codec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/Vector3Codec.java new file mode 100644 index 00000000..597c26c9 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/Vector3Codec.java @@ -0,0 +1,17 @@ +package net.elytrium.limboapi.server.item.codec.data; + +import net.elytrium.limboapi.api.protocol.data.EntityData; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface Vector3Codec { + + StreamCodec CODEC = StreamCodec.composite( + ByteBufCodecs.FLOAT, EntityData.Vector3::x, + ByteBufCodecs.FLOAT, EntityData.Vector3::y, + ByteBufCodecs.FLOAT, EntityData.Vector3::z, + EntityData.Vector3::new + ); +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/VillagerDataCodec.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/VillagerDataCodec.java new file mode 100644 index 00000000..0393b00e --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/codec/data/VillagerDataCodec.java @@ -0,0 +1,17 @@ +package net.elytrium.limboapi.server.item.codec.data; + +import net.elytrium.limboapi.api.protocol.data.EntityData; +import net.elytrium.limboapi.protocol.codec.ByteBufCodecs; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public interface VillagerDataCodec { + + StreamCodec CODEC = StreamCodec.composite( + ByteBufCodecs.VAR_INT, EntityData.VillagerData::type, + ByteBufCodecs.VAR_INT, EntityData.VillagerData::profession, + ByteBufCodecs.VAR_INT, EntityData.VillagerData::level, + EntityData.VillagerData::new + ); +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/ComponentItemComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/ComponentItemComponent.java deleted file mode 100644 index 2a4e1f5a..00000000 --- a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/ComponentItemComponent.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package net.elytrium.limboapi.server.item.type; - -import com.velocitypowered.api.network.ProtocolVersion; -import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; -import io.netty.buffer.ByteBuf; -import net.kyori.adventure.text.Component; - -public class ComponentItemComponent extends WriteableItemComponent { - - public ComponentItemComponent(String name) { - super(name); - } - - @Override - public void write(ProtocolVersion version, ByteBuf buffer) { - new ComponentHolder(version, this.getValue()).write(buffer); - } -} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/ComponentsItemComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/ComponentsItemComponent.java deleted file mode 100644 index affaeb5f..00000000 --- a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/ComponentsItemComponent.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package net.elytrium.limboapi.server.item.type; - -import com.velocitypowered.api.network.ProtocolVersion; -import com.velocitypowered.proxy.protocol.ProtocolUtils; -import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; -import io.netty.buffer.ByteBuf; -import java.util.List; -import net.kyori.adventure.text.Component; - -public class ComponentsItemComponent extends WriteableItemComponent> { - - public ComponentsItemComponent(String name) { - super(name); - } - - @Override - public void write(ProtocolVersion version, ByteBuf buffer) { - ProtocolUtils.writeVarInt(buffer, this.getValue().size()); - for (Component component : this.getValue()) { - new ComponentHolder(version, component).write(buffer); - } - } -} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/DyedColorItemComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/DyedColorItemComponent.java deleted file mode 100644 index 3d2c3fca..00000000 --- a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/DyedColorItemComponent.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package net.elytrium.limboapi.server.item.type; - -import com.velocitypowered.api.network.ProtocolVersion; -import io.netty.buffer.ByteBuf; -import it.unimi.dsi.fastutil.Pair; - -public class DyedColorItemComponent extends WriteableItemComponent> { - - public DyedColorItemComponent(String name) { - super(name); - } - - @Override - public void write(ProtocolVersion version, ByteBuf buffer) { - buffer.writeInt(this.getValue().left()); - buffer.writeBoolean(this.getValue().right()); - } -} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/EmptyItemComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/EmptyItemComponent.java deleted file mode 100644 index b699d81e..00000000 --- a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/EmptyItemComponent.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package net.elytrium.limboapi.server.item.type; - -import com.velocitypowered.api.network.ProtocolVersion; -import io.netty.buffer.ByteBuf; -import net.kyori.adventure.nbt.BinaryTag; - -public class EmptyItemComponent extends WriteableItemComponent { - - public EmptyItemComponent(String name) { - super(name); - } - - @Override - public void write(ProtocolVersion version, ByteBuf buffer) { - - } -} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/EnchantmentsItemComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/EnchantmentsItemComponent.java deleted file mode 100644 index efafc318..00000000 --- a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/EnchantmentsItemComponent.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package net.elytrium.limboapi.server.item.type; - -import com.velocitypowered.api.network.ProtocolVersion; -import com.velocitypowered.proxy.protocol.ProtocolUtils; -import io.netty.buffer.ByteBuf; -import it.unimi.dsi.fastutil.Pair; -import java.util.List; - -public class EnchantmentsItemComponent extends WriteableItemComponent>, Boolean>> { - - public EnchantmentsItemComponent(String name) { - super(name); - } - - @Override - public void write(ProtocolVersion version, ByteBuf buffer) { - ProtocolUtils.writeVarInt(buffer, this.getValue().left().size()); - for (Pair enchantment : this.getValue().left()) { - ProtocolUtils.writeVarInt(buffer, enchantment.left()); - ProtocolUtils.writeVarInt(buffer, enchantment.right()); - } - buffer.writeBoolean(this.getValue().right()); - } -} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/GameProfileItemComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/GameProfileItemComponent.java deleted file mode 100644 index 5a9e51b8..00000000 --- a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/GameProfileItemComponent.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package net.elytrium.limboapi.server.item.type; - -import com.velocitypowered.api.network.ProtocolVersion; -import com.velocitypowered.api.util.GameProfile; -import com.velocitypowered.proxy.protocol.ProtocolUtils; -import io.netty.buffer.ByteBuf; -import java.util.UUID; - -public class GameProfileItemComponent extends WriteableItemComponent { - - private static final UUID ZERO = new UUID(0, 0); - - public GameProfileItemComponent(String name) { - super(name); - } - - @Override - public void write(ProtocolVersion version, ByteBuf buffer) { - buffer.writeBoolean(!this.getValue().getName().isEmpty()); - if (!this.getValue().getName().isEmpty()) { - ProtocolUtils.writeString(buffer, this.getValue().getName()); - } - - buffer.writeBoolean(!this.getValue().getId().equals(ZERO)); - if (!this.getValue().getId().equals(ZERO)) { - ProtocolUtils.writeUuid(buffer, this.getValue().getId()); - } - - ProtocolUtils.writeProperties(buffer, this.getValue().getProperties()); - } -} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/IntItemComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/IntItemComponent.java deleted file mode 100644 index 71df56db..00000000 --- a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/IntItemComponent.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package net.elytrium.limboapi.server.item.type; - -import com.velocitypowered.api.network.ProtocolVersion; -import io.netty.buffer.ByteBuf; - -public class IntItemComponent extends WriteableItemComponent { - - public IntItemComponent(String name) { - super(name); - } - - @Override - public void write(ProtocolVersion version, ByteBuf buffer) { - buffer.writeInt(this.getValue()); - } -} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/StringItemComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/StringItemComponent.java deleted file mode 100644 index e8893ab6..00000000 --- a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/StringItemComponent.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package net.elytrium.limboapi.server.item.type; - -import com.velocitypowered.api.network.ProtocolVersion; -import com.velocitypowered.proxy.protocol.ProtocolUtils; -import io.netty.buffer.ByteBuf; - -public class StringItemComponent extends WriteableItemComponent { - - public StringItemComponent(String name) { - super(name); - } - - @Override - public void write(ProtocolVersion version, ByteBuf buffer) { - ProtocolUtils.writeString(buffer, this.getValue()); - } -} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/StringsItemComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/StringsItemComponent.java deleted file mode 100644 index 43095d63..00000000 --- a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/StringsItemComponent.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package net.elytrium.limboapi.server.item.type; - -import com.velocitypowered.api.network.ProtocolVersion; -import com.velocitypowered.proxy.protocol.ProtocolUtils; -import io.netty.buffer.ByteBuf; -import java.util.List; - -public class StringsItemComponent extends WriteableItemComponent> { - - public StringsItemComponent(String name) { - super(name); - } - - @Override - public void write(ProtocolVersion version, ByteBuf buffer) { - ProtocolUtils.writeVarInt(buffer, this.getValue().size()); - for (String string : this.getValue()) { - ProtocolUtils.writeString(buffer, string); - } - } -} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/TagItemComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/TagItemComponent.java deleted file mode 100644 index 2866ec0d..00000000 --- a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/TagItemComponent.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package net.elytrium.limboapi.server.item.type; - -import com.velocitypowered.api.network.ProtocolVersion; -import com.velocitypowered.proxy.protocol.ProtocolUtils; -import io.netty.buffer.ByteBuf; -import net.kyori.adventure.nbt.BinaryTag; - -public class TagItemComponent extends WriteableItemComponent { - - public TagItemComponent(String name) { - super(name); - } - - @Override - public void write(ProtocolVersion version, ByteBuf buffer) { - ProtocolUtils.writeBinaryTag(buffer, version, this.getValue()); - } -} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/VarIntItemComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/VarIntItemComponent.java deleted file mode 100644 index a7c93311..00000000 --- a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/VarIntItemComponent.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package net.elytrium.limboapi.server.item.type; - -import com.velocitypowered.api.network.ProtocolVersion; -import com.velocitypowered.proxy.protocol.ProtocolUtils; -import io.netty.buffer.ByteBuf; - -public class VarIntItemComponent extends WriteableItemComponent { - - public VarIntItemComponent(String name) { - super(name); - } - - @Override - public void write(ProtocolVersion version, ByteBuf buffer) { - ProtocolUtils.writeVarInt(buffer, this.getValue()); - } -} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/WriteableItemComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/WriteableItemComponent.java deleted file mode 100644 index ee198987..00000000 --- a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/WriteableItemComponent.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package net.elytrium.limboapi.server.item.type; - -import com.velocitypowered.api.network.ProtocolVersion; -import io.netty.buffer.ByteBuf; -import net.elytrium.limboapi.api.protocol.item.ItemComponent; - -public abstract class WriteableItemComponent implements ItemComponent { - - private final String name; - private T value; - - public WriteableItemComponent(String name) { - this.name = name; - } - - public abstract void write(ProtocolVersion version, ByteBuf buffer); - - @Override - public String getName() { - return this.name; - } - - @Override - public ItemComponent setValue(T value) { - this.value = value; - return this; - } - - @Override - public T getValue() { - return this.value; - } -} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/world/Biome.java b/plugin/src/main/java/net/elytrium/limboapi/server/world/Biome.java new file mode 100644 index 00000000..edd987f1 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/world/Biome.java @@ -0,0 +1,381 @@ +/* + * Copyright (C) 2021 - 2025 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limboapi.server.world; + +import com.velocitypowered.api.network.ProtocolVersion; +import java.util.EnumMap; +import net.elytrium.limboapi.api.world.chunk.biome.BuiltInBiome; +import net.elytrium.limboapi.api.world.chunk.biome.VirtualBiome; +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.nbt.CompoundBinaryTag.Builder; +import net.kyori.adventure.nbt.ListBinaryTag; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +public enum Biome implements VirtualBiome { + + PLAINS( + BuiltInBiome.PLAINS, + "minecraft:plains", + 1, + new Element( + true, 0.125F, 0.8F, 0.05F, 0.4F, "plains", + Element.Effects.builder(0x78A7FF, 0x050533, 0xC0D8FF, 0x3F76E4) + .moodSound(new Element.Effects.MoodSound(6000, 2.0, 8, "minecraft:ambient.cave")) + .build() + ) + ), + SWAMP( + BuiltInBiome.SWAMP, + "minecraft:swamp", + 6, + new Element( + true, -0.2F, 0.8F, 0.1F, 0.9F, "swamp", + Element.Effects.builder(0x78A7FF, 0x232317, 0xC0D8FF, 0x617B64) + .foliageColor(0x6A7039) + .grassColorModifier("swamp") + .moodSound(new Element.Effects.MoodSound(6000, 2.0, 8, "minecraft:ambient.cave")) + .build() + ) + ), + SWAMP_HILLS( + BuiltInBiome.SWAMP_HILLS, + "minecraft:swamp_hills", + 134, + new Element( + true, -0.1F, 0.8F, 0.3F, 0.9F, "swamp", + Element.Effects.builder(0x78A7FF, 0x232317, 0xC0D8FF, 0x617B64) + .foliageColor(0x6A7039) + .grassColorModifier("swamp") + .moodSound(new Element.Effects.MoodSound(6000, 2.0, 8, "minecraft:ambient.cave")) + .build() + ) + ), + NETHER_WASTES( + BuiltInBiome.NETHER_WASTES, + "minecraft:nether_wastes", + 8, + new Element( + false, 0.1F, 2.0F, 0.2F, 0.0F, "nether", + Element.Effects.builder(0x6EB1FF, 0x050533, 0x330808, 0x3F76E4) + .moodSound(new Element.Effects.MoodSound(6000, 2.0, 8, "minecraft:ambient.nether_wastes.mood")) + .build() + ) + ), + THE_END( + BuiltInBiome.THE_END, + "minecraft:the_end", + 9, + new Element( + false, 0.1F, 0.5F, 0.2F, 0.5F, "the_end", + Element.Effects.builder(0x000000, 0x050533, 0xA080A0, 0x3F76E4) + .moodSound(new Element.Effects.MoodSound(6000, 2.0, 8, "minecraft:ambient.cave")) + .build() + ) + ); + + public static final Biome[] VALUES = Biome.values(); + + private static final EnumMap BUILT_IN_BIOME_MAP = new EnumMap<>(BuiltInBiome.class); + + private final BuiltInBiome index; + private final String name; + private final int id; + private final Element element; + + Biome(BuiltInBiome index, String name, int id, Element element) { + this.index = index; + this.name = name; + this.id = id; + this.element = element; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public int getId() { + return this.id; + } + + public Element getElement() { + return this.element; + } + + public CompoundBinaryTag encode(ProtocolVersion version) { + return CompoundBinaryTag.builder() + .putString("name", this.name) + .putInt("id", this.id) + .put("element", this.element.encode(version)) + .build(); + } + + static { + for (Biome biome : Biome.VALUES) { + BUILT_IN_BIOME_MAP.put(biome.index, biome); + } + } + + public static Biome of(BuiltInBiome index) { + return Biome.BUILT_IN_BIOME_MAP.get(index); + } + + public static CompoundBinaryTag getRegistry(ProtocolVersion version) { + ListBinaryTag.Builder list = ListBinaryTag.builder(); + for (Biome biome : Biome.VALUES) { + list.add(biome.encode(version)); + } + + return CompoundBinaryTag.builder() + .putString("type", "minecraft:worldgen/biome") + .put("value", list.build()) + .build(); + } + + public record Element(boolean hasPrecipitation, float depth, float temperature, float scale, float downfall, String category, Effects effects) { + + public CompoundBinaryTag encode(ProtocolVersion version) { + Builder tagBuilder = CompoundBinaryTag.builder() + .putFloat("depth", this.depth) + .putFloat("temperature", this.temperature) + .putFloat("scale", this.scale) + .putFloat("downfall", this.downfall) + .putString("category", this.category) + .put("effects", this.effects.encode()); + + if (version.lessThan(ProtocolVersion.MINECRAFT_1_19_4)) { + tagBuilder.putString("precipitation", this.hasPrecipitation ? "rain" : "none"); + } else { + tagBuilder.putBoolean("has_precipitation", this.hasPrecipitation); + } + + return tagBuilder.build(); + } + + public record Effects(int skyColor, int waterFogColor, int fogColor, int waterColor, @Nullable Integer foliageColor, @Nullable String grassColorModifier, + Element.Effects.@Nullable Music music, @Nullable String ambientSound, Element.Effects.@Nullable AdditionsSound additionsSound, Element.Effects.@Nullable MoodSound moodSound, + Element.Effects.@Nullable Particle particle) { + + public CompoundBinaryTag encode() { + Builder result = CompoundBinaryTag.builder(); + + result.putInt("sky_color", this.skyColor); + result.putInt("water_fog_color", this.waterColor); + result.putInt("fog_color", this.fogColor); + result.putInt("water_color", this.waterColor); + + if (this.foliageColor != null) { + result.putInt("foliage_color", this.foliageColor); + } + + if (this.grassColorModifier != null) { + result.putString("grass_color_modifier", this.grassColorModifier); + } + + if (this.music != null) { + result.put("music", this.music.encode()); + } + + if (this.ambientSound != null) { + result.putString("ambient_sound", this.ambientSound); + } + + if (this.additionsSound != null) { + result.put("additions_sound", this.additionsSound.encode()); + } + + if (this.moodSound != null) { + result.put("mood_sound", this.moodSound.encode()); + } + + if (this.particle != null) { + result.put("particle", this.particle.encode()); + } + + return result.build(); + } + + public static EffectsBuilder builder(int skyColor, int waterFogColor, int fogColor, int waterColor) { + return new EffectsBuilder() + .skyColor(skyColor) + .waterFogColor(waterFogColor) + .fogColor(fogColor) + .waterColor(waterColor); + } + + public record MoodSound(int tickDelay, double offset, int blockSearchExtent, @NonNull String sound) { + + public CompoundBinaryTag encode() { + return CompoundBinaryTag.builder() + .putInt("tick_delay", this.tickDelay) + .putDouble("offset", this.offset) + .putInt("block_search_extent", this.blockSearchExtent) + .putString("sound", this.sound) + .build(); + } + } + + public record Music(boolean replaceCurrentMusic, @NonNull String sound, int maxDelay, int minDelay) { + + public CompoundBinaryTag encode() { + return CompoundBinaryTag.builder() + .putBoolean("replace_current_music", this.replaceCurrentMusic) + .putString("sound", this.sound) + .putInt("max_delay", this.maxDelay) + .putInt("min_delay", this.minDelay) + .build(); + } + } + + public record AdditionsSound(@NonNull String sound, double tickChance) { + + public CompoundBinaryTag encode() { + return CompoundBinaryTag.builder() + .putString("sound", this.sound) + .putDouble("tick_chance", this.tickChance) + .build(); + } + } + + public record Particle(float probability, Effects.Particle.@NonNull ParticleOptions options) { + + public CompoundBinaryTag encode() { + return CompoundBinaryTag.builder() + .putFloat("probability", this.probability) + .put("options", this.options.encode()) + .build(); + } + + public record ParticleOptions(@NonNull String type) { // TODO native particles api + option to add particle data + + public CompoundBinaryTag encode() { + return CompoundBinaryTag.builder() + .putString("type", this.type) + .build(); + } + } + } + + public static class EffectsBuilder { + + private int skyColor; + private int waterFogColor; + private int fogColor; + private int waterColor; + private Integer foliageColor; + private String grassColorModifier; + private Music music; + private String ambientSound; + private AdditionsSound additionsSound; + private MoodSound moodSound; + private Particle particle; + + public EffectsBuilder skyColor(int skyColor) { + this.skyColor = skyColor; + return this; + } + + public EffectsBuilder waterFogColor(int waterFogColor) { + this.waterFogColor = waterFogColor; + return this; + } + + public EffectsBuilder fogColor(int fogColor) { + this.fogColor = fogColor; + return this; + } + + public EffectsBuilder waterColor(int waterColor) { + this.waterColor = waterColor; + return this; + } + + public EffectsBuilder foliageColor(Integer foliageColor) { + this.foliageColor = foliageColor; + return this; + } + + public EffectsBuilder grassColorModifier(String grassColorModifier) { + this.grassColorModifier = grassColorModifier; + return this; + } + + public EffectsBuilder music(Music music) { + this.music = music; + return this; + } + + public EffectsBuilder ambientSound(String ambientSound) { + this.ambientSound = ambientSound; + return this; + } + + public EffectsBuilder additionsSound(AdditionsSound additionsSound) { + this.additionsSound = additionsSound; + return this; + } + + public EffectsBuilder moodSound(MoodSound moodSound) { + this.moodSound = moodSound; + return this; + } + + public EffectsBuilder particle(Particle particle) { + this.particle = particle; + return this; + } + + public Effects build() { + return new Effects( + this.skyColor, + this.waterFogColor, + this.fogColor, + this.waterColor, + this.foliageColor, + this.grassColorModifier, + this.music, + this.ambientSound, + this.additionsSound, + this.moodSound, + this.particle + ); + } + + @Override + public String toString() { + return "Biome.Element.Effects.EffectsBuilder{" + + "skyColor=" + this.skyColor + + ", waterFogColor=" + this.waterFogColor + + ", fogColor=" + this.fogColor + + ", waterColor=" + this.waterColor + + ", foliageColor=" + this.foliageColor + + ", grassColorModifier=" + this.grassColorModifier + + ", music=" + this.music + + ", ambientSound=" + this.ambientSound + + ", additionsSound=" + this.additionsSound + + ", moodSound=" + this.moodSound + + ", particle=" + this.particle + + "}"; + } + } + } + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/world/Particle.java b/plugin/src/main/java/net/elytrium/limboapi/server/world/Particle.java new file mode 100644 index 00000000..3b705b02 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/world/Particle.java @@ -0,0 +1,396 @@ +package net.elytrium.limboapi.server.world; + +import com.velocitypowered.api.network.ProtocolVersion; +import net.elytrium.limboapi.api.protocol.data.EntityData; +import net.elytrium.limboapi.protocol.codec.StreamCodec; +import net.elytrium.limboapi.server.item.codec.data.BlockStateCodec; +import net.elytrium.limboapi.server.item.codec.data.ItemStackCodec; +import net.elytrium.limboapi.server.item.codec.data.ParticleCodec; +import org.jetbrains.annotations.ApiStatus; + +public enum Particle { + + /** + * Present only in [1.13-1.20.3] + */ + @ApiStatus.Obsolete + AMBIENT_ENTITY_EFFECT, + ANGRY_VILLAGER, + /** + * @sinceMinecraft 1.16 + */ + ASH, + /** + * Present only in [1.13-1.17.1] + */ + @ApiStatus.Obsolete + BARRIER, + BLOCK(BlockStateCodec.CODEC), + /** + * @sinceMinecraft 1.21.2 + */ + BLOCK_CRUMBLE(BlockStateCodec.CODEC), + /** + * @sinceMinecraft 1.18 + */ + BLOCK_MARKER(BlockStateCodec.CODEC), + BUBBLE, + BUBBLE_COLUMN_UP, + BUBBLE_POP, + /** + * @sinceMinecraft 1.14 + */ + CAMPFIRE_COSY_SMOKE, + /** + * @sinceMinecraft 1.14 + */ + CAMPFIRE_SIGNAL_SMOKE, + /** + * @sinceMinecraft 1.20 + */ + CHERRY_LEAVES, + CLOUD, + /** + * @sinceMinecraft 1.14 + */ + COMPOSTER, + /** + * @sinceMinecraft 1.21.9 + */ + COPPER_FIRE_FLAME, + /** + * @sinceMinecraft 1.16 + */ + CRIMSON_SPORE, + CRIT, + CURRENT_DOWN, + DAMAGE_INDICATOR, + DOLPHIN, + DRAGON_BREATH(ParticleCodec.ParticleDataCodecs.POWER_CODEC), + /** + * Present only in [1.19.4-1.19.4] + * + * @sinceMinecraft 1.19.4 + */ + @ApiStatus.Obsolete + DRIPPING_CHERRY_LEAVES, + /** + * @sinceMinecraft 1.17 + */ + DRIPPING_DRIPSTONE_LAVA, + /** + * @sinceMinecraft 1.17 + */ + DRIPPING_DRIPSTONE_WATER, + /** + * @sinceMinecraft 1.15 + */ + DRIPPING_HONEY, + DRIPPING_LAVA, + /** + * @sinceMinecraft 1.16 + */ + DRIPPING_OBSIDIAN_TEAR, + DRIPPING_WATER, + DUST(ParticleCodec.ParticleDataCodecs.DUST_PARTICLE_CODEC), + /** + * @sinceMinecraft 1.17 + */ + DUST_COLOR_TRANSITION(ParticleCodec.ParticleDataCodecs.DUST_COLOR_TRANSITION_CODEC), + /** + * @sinceMinecraft 1.20.5 + */ + DUST_PILLAR(BlockStateCodec.CODEC), + /** + * @sinceMinecraft 1.20.3 + */ + DUST_PLUME, + EFFECT(StreamCodec.ge(ProtocolVersion.MINECRAFT_1_21_9, ParticleCodec.ParticleDataCodecs.COLOR_CODEC, new EntityData.Particle.ColorParticleOption(-1))), + /** + * @sinceMinecraft 1.20 + */ + EGG_CRACK, + ELDER_GUARDIAN, + /** + * @sinceMinecraft 1.17 + */ + ELECTRIC_SPARK, + ENCHANT, + ENCHANTED_HIT, + END_ROD, + ENTITY_EFFECT(StreamCodec.ge(ProtocolVersion.MINECRAFT_1_20_5, ParticleCodec.ParticleDataCodecs.COLOR_CODEC, new EntityData.Particle.ColorParticleOption(0))), + EXPLOSION, + EXPLOSION_EMITTER, + /** + * Present only in [1.19.4-1.19.4] + * + * @sinceMinecraft 1.19.4 + */ + @ApiStatus.Obsolete + FALLING_CHERRY_LEAVES, + /** + * @sinceMinecraft 1.17 + */ + FALLING_DRIPSTONE_LAVA, + /** + * @sinceMinecraft 1.17 + */ + FALLING_DRIPSTONE_WATER, + FALLING_DUST(BlockStateCodec.CODEC), + /** + * @sinceMinecraft 1.15 + */ + FALLING_HONEY, + /** + * @sinceMinecraft 1.14 + */ + FALLING_LAVA, + /** + * @sinceMinecraft 1.15 + */ + FALLING_NECTAR, + /** + * @sinceMinecraft 1.16 + */ + FALLING_OBSIDIAN_TEAR, + /** + * @sinceMinecraft 1.17 + */ + FALLING_SPORE_BLOSSOM, + /** + * @sinceMinecraft 1.14 + */ + FALLING_WATER, + /** + * @sinceMinecraft 1.21.5 + */ + FIREFLY, + FIREWORK, + FISHING, + FLAME, + /** + * @sinceMinecraft 1.14 + */ + FLASH(Particle.EFFECT.codec()), + /** + * @sinceMinecraft 1.17 + */ + GLOW, + /** + * @sinceMinecraft 1.17 + */ + GLOW_SQUID_INK, + /** + * @sinceMinecraft 1.20.3 + */ + GUST, + /** + * Present only in [1.20.3-1.20.3] + * + * @sinceMinecraft 1.20.3 + */ + @ApiStatus.Obsolete + GUST_DUST, + /** + * Present only in [1.20.3-1.20.3] + * + * @sinceMinecraft 1.20.3 + */ + @ApiStatus.Obsolete + GUST_EMITTER, + /** + * @sinceMinecraft 1.20.5 + */ + GUST_EMITTER_LARGE, + /** + * @sinceMinecraft 1.20.5 + */ + GUST_EMITTER_SMALL, + HAPPY_VILLAGER, + HEART, + /** + * @sinceMinecraft 1.20.5 + */ + INFESTED, + INSTANT_EFFECT(ParticleCodec.ParticleDataCodecs.SPELL_CODEC), + ITEM(ItemStackCodec.CODEC), + /** + * @sinceMinecraft 1.20.5 + */ + ITEM_COBWEB, + ITEM_SLIME, + ITEM_SNOWBALL, + /** + * Present only in 1.19.4 + * + * @sinceMinecraft 1.19.4 + */ + @ApiStatus.Obsolete + LANDING_CHERRY_LEAVES, + /** + * @sinceMinecraft 1.15 + */ + LANDING_HONEY, + /** + * @sinceMinecraft 1.14 + */ + LANDING_LAVA, + /** + * @sinceMinecraft 1.16 + */ + LANDING_OBSIDIAN_TEAR, + LARGE_SMOKE, + LAVA, + /** + * Present only in [1.17-1.17.1] + * + * @sinceMinecraft 1.17 + */ + @ApiStatus.Obsolete + LIGHT, + MYCELIUM, + NAUTILUS, + NOTE, + /** + * @sinceMinecraft 1.20.5 + */ + OMINOUS_SPAWNING, + /** + * @sinceMinecraft 1.21.4 + */ + PALE_OAK_LEAVES, + POOF, + PORTAL, + /** + * @sinceMinecraft 1.20.5 + */ + RAID_OMEN, + RAIN, + /** + * @sinceMinecraft 1.16 + */ + REVERSE_PORTAL, + /** + * @sinceMinecraft 1.17 + */ + SCRAPE, + /** + * @sinceMinecraft 1.19 + */ + SCULK_CHARGE(ParticleCodec.ParticleDataCodecs.SCULK_CHARGE), + /** + * @sinceMinecraft 1.19 + */ + SCULK_CHARGE_POP, + /** + * @sinceMinecraft 1.19 + */ + SCULK_SOUL, + /** + * @sinceMinecraft 1.19 + */ + SHRIEK(ParticleCodec.ParticleDataCodecs.SHRIEK_CODEC), + /** + * @sinceMinecraft 1.17 + */ + SMALL_FLAME, + /** + * @sinceMinecraft 1.20.5 + */ + SMALL_GUST, + SMOKE, + /** + * @sinceMinecraft 1.14 + */ + SNEEZE, + /** + * @sinceMinecraft 1.17 + */ + SNOWFLAKE, + /** + * @sinceMinecraft 1.19 + */ + SONIC_BOOM, + /** + * @sinceMinecraft 1.16 + */ + SOUL, + /** + * @sinceMinecraft 1.16 + */ + SOUL_FIRE_FLAME, + SPIT, + SPLASH, + /** + * @sinceMinecraft 1.17 + */ + SPORE_BLOSSOM_AIR, + SQUID_INK, + SWEEP_ATTACK, + /** + * @sinceMinecraft 1.21.5 + */ + TINTED_LEAVES(ParticleCodec.ParticleDataCodecs.COLOR_CODEC), + TOTEM_OF_UNDYING, + /** + * @sinceMinecraft 1.21.2 + */ + TRAIL(ParticleCodec.ParticleDataCodecs.TRAIL_CODEC), + /** + * @sinceMinecraft 1.20.5 + */ + TRIAL_OMEN, + /** + * @sinceMinecraft 1.20.3 + */ + TRIAL_SPAWNER_DETECTION, + /** + * @sinceMinecraft 1.20.5 + */ + TRIAL_SPAWNER_DETECTION_OMINOUS, + UNDERWATER, + /** + * @sinceMinecraft 1.20.5 + */ + VAULT_CONNECTION, + /** + * @sinceMinecraft 1.17 + */ + VIBRATION(ParticleCodec.ParticleDataCodecs.VIBRATION_CODEC), + /** + * @sinceMinecraft 1.16 + */ + WARPED_SPORE, + /** + * @sinceMinecraft 1.17 + */ + WAX_OFF, + /** + * @sinceMinecraft 1.17 + */ + WAX_ON, + /** + * @sinceMinecraft 1.16 + */ + WHITE_ASH, + /** + * @sinceMinecraft 1.20.3 + */ + WHITE_SMOKE, + WITCH; + + private final StreamCodec codec; + + Particle() { + this.codec = null; + } + + @SuppressWarnings("unchecked") + Particle(StreamCodec codec) { + this.codec = (StreamCodec) codec; + } + + public StreamCodec codec() { + return this.codec; + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/world/SimpleBlock.java b/plugin/src/main/java/net/elytrium/limboapi/server/world/SimpleBlock.java index 335d3e65..3fefa9e9 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/server/world/SimpleBlock.java +++ b/plugin/src/main/java/net/elytrium/limboapi/server/world/SimpleBlock.java @@ -17,206 +17,87 @@ package net.elytrium.limboapi.server.world; -import com.google.gson.Gson; import com.google.gson.internal.LinkedTreeMap; import com.velocitypowered.api.network.ProtocolVersion; -import io.netty.util.collection.ShortObjectHashMap; -import io.netty.util.collection.ShortObjectMap; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ShortOpenHashMap; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.shorts.Short2ShortOpenHashMap; +import java.util.Collections; import java.util.EnumMap; -import java.util.HashMap; -import java.util.HashSet; import java.util.Locale; import java.util.Map; -import java.util.Objects; -import java.util.Set; import net.elytrium.limboapi.LimboAPI; -import net.elytrium.limboapi.api.chunk.VirtualBlock; -import net.elytrium.limboapi.api.material.WorldVersion; +import net.elytrium.limboapi.api.world.chunk.block.VirtualBlock; +import net.elytrium.limboapi.api.world.WorldVersion; +import net.elytrium.limboapi.utils.JsonUtil; import org.checkerframework.checker.nullness.qual.NonNull; -public class SimpleBlock implements VirtualBlock { - - private static final Gson GSON = new Gson(); - private static final ShortObjectHashMap LEGACY_BLOCK_STATE_IDS_MAP = new ShortObjectHashMap<>(); - private static final Map> MODERN_BLOCK_STATE_IDS_MAP = new EnumMap<>(ProtocolVersion.class); - private static final ShortObjectHashMap MODERN_BLOCK_STATE_PROTOCOL_ID_MAP = new ShortObjectHashMap<>(); - private static final Map, Short>> MODERN_BLOCK_STATE_STRING_MAP = new HashMap<>(); - private static final Map MODERN_BLOCK_STRING_MAP = new HashMap<>(); - private static final ShortObjectHashMap> LEGACY_BLOCK_IDS_MAP = new ShortObjectHashMap<>(); - private static final Map> DEFAULT_PROPERTIES_MAP = new HashMap<>(); - private static final Map MODERN_ID_REMAP = new HashMap<>(); - - public static final SimpleBlock AIR = new SimpleBlock(false, true, false, "minecraft:air", (short) 0, (short) 0); - - @SuppressWarnings("unchecked") - public static void init() { - LinkedTreeMap blocks = GSON.fromJson( - new InputStreamReader( - Objects.requireNonNull(LimboAPI.class.getResourceAsStream("/mapping/blocks.json")), - StandardCharsets.UTF_8 - ), - LinkedTreeMap.class - ); - - blocks.forEach((modernId, protocolId) -> MODERN_BLOCK_STRING_MAP.put(modernId, Short.valueOf(protocolId))); - - LinkedTreeMap> blockVersionMapping = GSON.fromJson( - new InputStreamReader( - Objects.requireNonNull(LimboAPI.class.getResourceAsStream("/mapping/blocks_mapping.json")), - StandardCharsets.UTF_8 - ), - LinkedTreeMap.class - ); - - blockVersionMapping.forEach((protocolId, versionMap) -> { - EnumMap deserializedVersionMap = new EnumMap<>(WorldVersion.class); - versionMap.forEach((version, id) -> deserializedVersionMap.put(WorldVersion.parse(version), Short.valueOf(id))); - LEGACY_BLOCK_IDS_MAP.put(Short.valueOf(protocolId), deserializedVersionMap); - }); - - LinkedTreeMap blockStates = GSON.fromJson( - new InputStreamReader(Objects.requireNonNull(LimboAPI.class.getResourceAsStream("/mapping/blockstates.json")), StandardCharsets.UTF_8), - LinkedTreeMap.class - ); - blockStates.forEach((key, value) -> { - MODERN_BLOCK_STATE_PROTOCOL_ID_MAP.put(Short.valueOf(value), key); - - String[] stringIDArgs = key.split("\\["); - if (!MODERN_BLOCK_STATE_STRING_MAP.containsKey(stringIDArgs[0])) { - MODERN_BLOCK_STATE_STRING_MAP.put(stringIDArgs[0], new HashMap<>()); - } - - if (stringIDArgs.length == 1) { - MODERN_BLOCK_STATE_STRING_MAP.get(stringIDArgs[0]).put(null, Short.valueOf(value)); - } else { - stringIDArgs[1] = stringIDArgs[1].substring(0, stringIDArgs[1].length() - 1); - MODERN_BLOCK_STATE_STRING_MAP.get(stringIDArgs[0]).put(new HashSet<>(Arrays.asList(stringIDArgs[1].split(","))), Short.valueOf(value)); - } - }); - - LinkedTreeMap legacyBlocks = GSON.fromJson( - new InputStreamReader(Objects.requireNonNull(LimboAPI.class.getResourceAsStream("/mapping/legacyblocks.json")), StandardCharsets.UTF_8), - LinkedTreeMap.class - ); - legacyBlocks.forEach((legacyBlockID, modernID) - -> LEGACY_BLOCK_STATE_IDS_MAP.put(Short.valueOf(legacyBlockID), solid(Short.parseShort(modernID)))); - - LEGACY_BLOCK_STATE_IDS_MAP.put((short) 0, AIR); +/** + * @param motionBlocking Added in version 1.14 + */ +public record SimpleBlock(String modernId, short blockId, short blockStateId, boolean air, boolean solid, boolean motionBlocking) implements VirtualBlock { - LinkedTreeMap> modernMap = GSON.fromJson( - new InputStreamReader(Objects.requireNonNull(LimboAPI.class.getResourceAsStream("/mapping/blockstates_mapping.json")), StandardCharsets.UTF_8), - LinkedTreeMap.class - ); + private static final Short2ObjectOpenHashMap MODERN_BLOCK_STATE_PROTOCOL_ID_MAP; + private static final Object2ObjectOpenHashMap>> MODERN_BLOCK_STATE_STRING_MAP; + private static final EnumMap MODERN_BLOCK_STATE_IDS_MAP; + private static final Object2ShortOpenHashMap MODERN_BLOCK_STRING_MAP; + private static final Short2ObjectOpenHashMap> LEGACY_BLOCK_IDS_MAP; + private static final Short2ObjectOpenHashMap LEGACY_BLOCK_STATE_IDS_MAP; + private static final Object2ObjectOpenHashMap> DEFAULT_PROPERTIES_MAP; + private static final Object2ObjectOpenHashMap MODERN_ID_REMAP; - modernMap.forEach((modernID, versionMap) -> { - Short id = null; - for (ProtocolVersion version : ProtocolVersion.SUPPORTED_VERSIONS) { - id = Short.valueOf(versionMap.getOrDefault(version.toString(), String.valueOf(id))); - SimpleBlock.MODERN_BLOCK_STATE_IDS_MAP.computeIfAbsent(version, k -> new ShortObjectHashMap<>()).put(Short.parseShort(modernID), id); - } - }); + public static final SimpleBlock AIR = new SimpleBlock("minecraft:air", (short) 0, (short) 0, true, false, false); - LinkedTreeMap> properties = GSON.fromJson( - new InputStreamReader( - Objects.requireNonNull(LimboAPI.class.getResourceAsStream("/mapping/defaultblockproperties.json")), - StandardCharsets.UTF_8 - ), - LinkedTreeMap.class - ); - properties.forEach((key, value) -> DEFAULT_PROPERTIES_MAP.put(key, new HashMap<>(value))); - - LinkedTreeMap modernIdRemap = GSON.fromJson( - new InputStreamReader( - Objects.requireNonNull(LimboAPI.class.getResourceAsStream("/mapping/modern_block_id_remap.json")), - StandardCharsets.UTF_8 - ), - LinkedTreeMap.class - ); - MODERN_ID_REMAP.putAll(modernIdRemap); + public SimpleBlock(String modernId, Map properties, boolean air, boolean solid, boolean motionBlocking) { + this(modernId, SimpleBlock.transformId(modernId, properties), air, solid, motionBlocking); } - private final boolean solid; - private final boolean air; - private final boolean motionBlocking; // 1.14+ - private final String modernID; - private final short blockStateID; - private final short blockID; - - public SimpleBlock(boolean solid, boolean air, boolean motionBlocking, short blockStateID) { - this(solid, air, motionBlocking, MODERN_BLOCK_STATE_PROTOCOL_ID_MAP.get(blockStateID), blockStateID); + public SimpleBlock(short blockStateId, boolean air, boolean solid, boolean motionBlocking) { + this(SimpleBlock.MODERN_BLOCK_STATE_PROTOCOL_ID_MAP.get(blockStateId), blockStateId, air, solid, motionBlocking); } - public SimpleBlock(boolean solid, boolean air, boolean motionBlocking, String modernID, short blockStateID) { - this(solid, air, motionBlocking, modernID, blockStateID, findId(modernID)); + public SimpleBlock(String modernId, short blockStateId, boolean air, boolean solid, boolean motionBlocking) { + this(modernId, SimpleBlock.findBlockId(modernId), blockStateId, air, solid, motionBlocking); } - private static short findId(String modernID) { - String block = modernID.split("\\[")[0]; - Short id = MODERN_BLOCK_STRING_MAP.get(block); - if (id == null) { - throw new IllegalStateException("failed to find local id for specific block: " + block); + private static short findBlockId(String modernId) { + int bracketIndex = modernId.indexOf('['); + short id = SimpleBlock.MODERN_BLOCK_STRING_MAP.getShort(bracketIndex == -1 ? modernId : modernId.substring(0, bracketIndex)); + if (id == Short.MIN_VALUE) { + throw new IllegalStateException("failed to find local id for specific block: " + modernId); } return id; } - public SimpleBlock(boolean solid, boolean air, boolean motionBlocking, String modernID, short blockStateID, short blockID) { - this.solid = solid; - this.air = air; - this.motionBlocking = motionBlocking; - this.modernID = modernID; - this.blockStateID = blockStateID; - this.blockID = blockID; - } - - public SimpleBlock(boolean solid, boolean air, boolean motionBlocking, String modernID, Map properties) { - this(solid, air, motionBlocking, modernID, transformID(modernID, properties)); - } - - public SimpleBlock(boolean solid, boolean air, boolean motionBlocking, String modernID, Map properties, short blockID) { - this(solid, air, motionBlocking, modernID, transformID(modernID, properties), blockID); + public SimpleBlock(String modernID, short blockId, Map properties, boolean air, boolean solid, boolean motionBlocking) { + this(modernID, blockId, SimpleBlock.transformId(modernID, properties), air, solid, motionBlocking); } public SimpleBlock(SimpleBlock block) { - this.solid = block.solid; - this.air = block.air; - this.motionBlocking = block.motionBlocking; - this.modernID = block.modernID; - this.blockStateID = block.blockStateID; - this.blockID = block.blockID; + this(block.modernId, block.blockId, block.blockStateId, block.air, block.solid, block.motionBlocking); } @Override - public short getModernID() { - return this.blockStateID; + public short blockStateId(ProtocolVersion version) { + return SimpleBlock.MODERN_BLOCK_STATE_IDS_MAP.get(version).getOrDefault(this.blockStateId, this.blockStateId); } @Override - public String getModernStringID() { - return this.modernID; + public short blockId(WorldVersion version) { + return SimpleBlock.LEGACY_BLOCK_IDS_MAP.get(this.blockId).getOrDefault(version, this.blockId); } @Override - public short getID(ProtocolVersion version) { - return this.getBlockStateID(version); - } - - @Override - public short getBlockID(WorldVersion version) { - return LEGACY_BLOCK_IDS_MAP.get(this.blockID).get(version); - } - - @Override - public short getBlockID(ProtocolVersion version) { - return this.getBlockID(WorldVersion.from(version)); + public short blockId(ProtocolVersion version) { + return this.blockId(WorldVersion.from(version)); } @Override public boolean isSupportedOn(WorldVersion version) { - return LEGACY_BLOCK_IDS_MAP.get(this.blockID).containsKey(version); + return SimpleBlock.LEGACY_BLOCK_IDS_MAP.get(this.blockId).containsKey(version); } @Override @@ -224,52 +105,94 @@ public boolean isSupportedOn(ProtocolVersion version) { return this.isSupportedOn(WorldVersion.from(version)); } - @Override - public short getBlockStateID(ProtocolVersion version) { - return MODERN_BLOCK_STATE_IDS_MAP.get(version).getOrDefault(this.blockStateID, this.blockStateID); - } + static { + var blockStates = JsonUtil.parse(LimboAPI.class.getResourceAsStream("/mappings/block_states.json")); + MODERN_BLOCK_STATE_PROTOCOL_ID_MAP = new Short2ObjectOpenHashMap<>(blockStates.size()); + MODERN_BLOCK_STATE_STRING_MAP = new Object2ObjectOpenHashMap<>(blockStates.size()); + blockStates.forEach((blockState, number) -> { + short value = number.shortValue(); + SimpleBlock.MODERN_BLOCK_STATE_PROTOCOL_ID_MAP.put(value, blockState); + int index = blockState.indexOf('['); + if (index == -1) { + SimpleBlock.MODERN_BLOCK_STATE_STRING_MAP.computeIfAbsent(blockState, key -> { + var map = new Object2ShortOpenHashMap>(); + map.defaultReturnValue(Short.MIN_VALUE); + return map; + }).put(null, value); + } else { + SimpleBlock.MODERN_BLOCK_STATE_STRING_MAP.computeIfAbsent(blockState.substring(0, index), key -> { + var map = new Object2ShortOpenHashMap>(); + map.defaultReturnValue(Short.MIN_VALUE); + return map; + }).put(new ObjectOpenHashSet<>(blockState.substring(index + 1, blockState.length() - 1).split(",")), value); + } + }); - @Override - public boolean isSolid() { - return this.solid; - } + var blocksMappings = JsonUtil.>parse(LimboAPI.class.getResourceAsStream("/mappings/blocks_mappings.json")); + MODERN_BLOCK_STRING_MAP = new Object2ShortOpenHashMap<>(blocksMappings.size()); + MODERN_BLOCK_STRING_MAP.defaultReturnValue(Short.MIN_VALUE); + LEGACY_BLOCK_IDS_MAP = new Short2ObjectOpenHashMap<>(blocksMappings.size()); + String maximumVersionProtocol = Integer.toString(ProtocolVersion.MAXIMUM_VERSION.getProtocol()); + blocksMappings.forEach((modernId, versions) -> { + short modernProtocolId = versions.get(maximumVersionProtocol).shortValue(); + SimpleBlock.MODERN_BLOCK_STRING_MAP.put(modernId, modernProtocolId); + EnumMap deserializedVersionMap = new EnumMap<>(WorldVersion.class); + versions.forEach((version, id) -> deserializedVersionMap.put(WorldVersion.from(ProtocolVersion.getProtocolVersion(Integer.parseInt(version))), id.shortValue())); + SimpleBlock.LEGACY_BLOCK_IDS_MAP.put(modernProtocolId, deserializedVersionMap); + }); - @Override - public boolean isAir() { - return this.air; - } + var blockStatesMappings = JsonUtil.>parse(LimboAPI.class.getResourceAsStream("/mappings/block_states_mappings.json")); + MODERN_BLOCK_STATE_IDS_MAP = new EnumMap<>(ProtocolVersion.class); + blockStatesMappings.forEach((modernId, versions) -> { + Short id = null; + for (ProtocolVersion version : ProtocolVersion.SUPPORTED_VERSIONS) { + id = versions.getOrDefault(Integer.toString(version.getProtocol()), id).shortValue(); + SimpleBlock.MODERN_BLOCK_STATE_IDS_MAP.computeIfAbsent(version, key -> new Short2ShortOpenHashMap()).put(Short.parseShort(modernId), id.shortValue()); + } + }); - @Override - public boolean isMotionBlocking() { - return this.motionBlocking; + var properties = JsonUtil.>parse(LimboAPI.class.getResourceAsStream("/mappings/default_block_properties.json")); + DEFAULT_PROPERTIES_MAP = new Object2ObjectOpenHashMap<>(properties.size()); + properties.forEach((key, value) -> SimpleBlock.DEFAULT_PROPERTIES_MAP.put(key, new Object2ObjectOpenHashMap<>(value))); + + MODERN_ID_REMAP = new Object2ObjectOpenHashMap<>(JsonUtil.parse(LimboAPI.class.getResourceAsStream("/mappings/modern_block_id_remap.json"))); + + var legacyBlocks = JsonUtil.parse(LimboAPI.class.getResourceAsStream("/mappings/legacy_blocks.json")); + LEGACY_BLOCK_STATE_IDS_MAP = new Short2ObjectOpenHashMap<>(legacyBlocks.size() + 1); + legacyBlocks.forEach((legacy, modern) -> SimpleBlock.LEGACY_BLOCK_STATE_IDS_MAP.put(Short.parseShort(legacy), SimpleBlock.solid(modern.shortValue()))); + SimpleBlock.LEGACY_BLOCK_STATE_IDS_MAP.put((short) 0, SimpleBlock.AIR); + + SimpleBlock.MODERN_BLOCK_STATE_STRING_MAP.trim(); + SimpleBlock.MODERN_BLOCK_STATE_STRING_MAP.values().forEach(Object2ShortOpenHashMap::trim); + SimpleBlock.MODERN_BLOCK_STATE_IDS_MAP.values().forEach(Short2ShortOpenHashMap::trim); } - public static VirtualBlock fromModernID(String modernID) { - String[] deserializedModernId = modernID.split("[\\[\\]]"); + public static VirtualBlock fromModernId(String modernId) { + String[] deserializedModernId = modernId.split("[\\[\\]]"); if (deserializedModernId.length < 2) { - return fromModernID(modernID, Map.of()); + return SimpleBlock.fromModernId(modernId, Collections.emptyMap()); } else { - Map properties = new HashMap<>(); + Object2ObjectOpenHashMap properties = new Object2ObjectOpenHashMap<>(); for (String property : deserializedModernId[1].split(",")) { String[] propertyKeyValue = property.split("="); properties.put(propertyKeyValue[0], propertyKeyValue[1]); } - return fromModernID(deserializedModernId[0], properties); + return SimpleBlock.fromModernId(deserializedModernId[0], properties); } } - public static VirtualBlock fromModernID(String modernID, Map properties) { - modernID = remapModernID(modernID); - return solid(modernID, transformID(modernID, properties)); + public static VirtualBlock fromModernId(String modernId, Map properties) { + modernId = SimpleBlock.remapModernId(modernId); + return SimpleBlock.solid(modernId, SimpleBlock.transformId(modernId, properties)); } - private static short transformID(String modernID, Map properties) { - Map defaultProperties = DEFAULT_PROPERTIES_MAP.get(modernID); + private static short transformId(String modernId, Map properties) { + var defaultProperties = SimpleBlock.DEFAULT_PROPERTIES_MAP.get(modernId); if (defaultProperties == null || defaultProperties.isEmpty()) { - return transformID(modernID, (Set) null); + return SimpleBlock.transformId(modernId, (ObjectOpenHashSet) null); } else { - Set propertiesSet = new HashSet<>(); + ObjectOpenHashSet propertiesSet = new ObjectOpenHashSet<>(); defaultProperties.forEach((key, value) -> { if (properties != null) { value = properties.getOrDefault(key, value); @@ -277,100 +200,84 @@ private static short transformID(String modernID, Map properties propertiesSet.add(key + "=" + value.toLowerCase(Locale.ROOT)); }); - return transformID(modernID, propertiesSet); + return SimpleBlock.transformId(modernId, propertiesSet); } } - private static short transformID(String modernID, Set properties) { - Map, Short> blockInfo = MODERN_BLOCK_STATE_STRING_MAP.get(modernID); - + private static short transformId(String modernId, ObjectOpenHashSet properties) { + var blockInfo = SimpleBlock.MODERN_BLOCK_STATE_STRING_MAP.get(modernId); if (blockInfo == null) { - LimboAPI.getLogger().warn("Block " + modernID + " is not supported, and was replaced with air."); - return AIR.getModernID(); + LimboAPI.getLogger().warn("Block {} is not supported, and was replaced with air.", modernId); + return SimpleBlock.AIR.blockStateId; } - Short id; - if (properties == null || properties.isEmpty()) { - id = blockInfo.get(null); - } else { - id = blockInfo.get(properties); - } - - if (id == null) { - LimboAPI.getLogger().warn("Block " + modernID + " is not supported with " + properties + " properties, and was replaced with air."); - return AIR.getModernID(); + short id = properties == null || properties.isEmpty() ? blockInfo.getShort(null) : blockInfo.getShort(properties); + if (id == Short.MIN_VALUE) { + LimboAPI.getLogger().warn("Block {} is not supported with {} properties, and was replaced with air.", modernId, properties); + return SimpleBlock.AIR.blockStateId; } return id; } - private static String remapModernID(String modernID) { - String strippedID = modernID.split("\\[")[0]; - String remappedID = MODERN_ID_REMAP.get(strippedID); - if (remappedID != null) { - modernID = remappedID + modernID.substring(strippedID.length()); - } - - return modernID; - } - @NonNull - public static SimpleBlock solid(short id) { - return solid(true, MODERN_BLOCK_STATE_PROTOCOL_ID_MAP.get(id), id); + public static SimpleBlock solid(short blockStateId) { + return SimpleBlock.solid(SimpleBlock.MODERN_BLOCK_STATE_PROTOCOL_ID_MAP.get(blockStateId), blockStateId, true); } @NonNull - public static SimpleBlock solid(String modernID, short id) { - return solid(true, remapModernID(modernID), id); + public static SimpleBlock solid(String modernId, short blockStateId) { + return SimpleBlock.solid(SimpleBlock.remapModernId(modernId), blockStateId, true); } @NonNull - public static SimpleBlock solid(boolean motionBlocking, short id) { - return new SimpleBlock(true, false, motionBlocking, MODERN_BLOCK_STATE_PROTOCOL_ID_MAP.get(id), id); + public static SimpleBlock solid(short blockStateId, boolean motionBlocking) { + return new SimpleBlock(SimpleBlock.MODERN_BLOCK_STATE_PROTOCOL_ID_MAP.get(blockStateId), blockStateId, false, true, motionBlocking); } @NonNull - public static SimpleBlock solid(boolean motionBlocking, String modernID, short id) { - return new SimpleBlock(true, false, motionBlocking, remapModernID(modernID), id); + public static SimpleBlock solid(String modernId, short blockStateId, boolean motionBlocking) { + return new SimpleBlock(SimpleBlock.remapModernId(modernId), blockStateId, false, true, motionBlocking); } @NonNull - public static SimpleBlock nonSolid(short id) { - return nonSolid(true, MODERN_BLOCK_STATE_PROTOCOL_ID_MAP.get(id), id); + public static SimpleBlock nonSolid(short blockStateId) { + return nonSolid(SimpleBlock.MODERN_BLOCK_STATE_PROTOCOL_ID_MAP.get(blockStateId), blockStateId, true); } @NonNull - public static SimpleBlock nonSolid(String modernID, short id) { - return nonSolid(true, remapModernID(modernID), id); + public static SimpleBlock nonSolid(String modernId, short blockStateId) { + return nonSolid(SimpleBlock.remapModernId(modernId), blockStateId, true); } @NonNull - public static SimpleBlock nonSolid(boolean motionBlocking, short id) { - return new SimpleBlock(false, false, motionBlocking, MODERN_BLOCK_STATE_PROTOCOL_ID_MAP.get(id), id); + public static SimpleBlock nonSolid(short blockStateId, boolean motionBlocking) { + return new SimpleBlock(SimpleBlock.MODERN_BLOCK_STATE_PROTOCOL_ID_MAP.get(blockStateId), blockStateId, false, false, motionBlocking); } @NonNull - public static SimpleBlock nonSolid(boolean motionBlocking, String modernID, short id) { - return new SimpleBlock(false, false, motionBlocking, remapModernID(modernID), id); + public static SimpleBlock nonSolid(String modernId, short blockStateId, boolean motionBlocking) { + return new SimpleBlock(SimpleBlock.remapModernId(modernId), blockStateId, false, false, motionBlocking); } @NonNull - public static SimpleBlock fromLegacyID(short id) { - if (LEGACY_BLOCK_STATE_IDS_MAP.containsKey(id)) { - return LEGACY_BLOCK_STATE_IDS_MAP.get(id); + public static SimpleBlock fromLegacyId(short legacyId) { + SimpleBlock block = SimpleBlock.LEGACY_BLOCK_STATE_IDS_MAP.get(legacyId); + if (block == null) { + LimboAPI.getLogger().warn("Block #{} is not supported, and was replaced with air", legacyId); + return SimpleBlock.AIR; } else { - LimboAPI.getLogger().warn("Block #" + id + " is not supported, and was replaced with air."); - return AIR; + return block; } } - @Override - public String toString() { - return "SimpleBlock{" - + "solid=" + this.solid - + ", air=" + this.air - + ", motionBlocking=" + this.motionBlocking - + ", id=" + this.blockStateID - + "}"; + private static String remapModernId(String modernId) { + int index = modernId.indexOf('['); + String remappedId = SimpleBlock.MODERN_ID_REMAP.get(index == -1 ? modernId : modernId.substring(0, index)); + return remappedId == null + ? modernId + : index == -1 + ? remappedId + : remappedId + modernId.substring(index); } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/world/SimpleBlockEntity.java b/plugin/src/main/java/net/elytrium/limboapi/server/world/SimpleBlockEntity.java index 1c27fbed..83b49d62 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/server/world/SimpleBlockEntity.java +++ b/plugin/src/main/java/net/elytrium/limboapi/server/world/SimpleBlockEntity.java @@ -17,90 +17,311 @@ package net.elytrium.limboapi.server.world; -import com.google.gson.Gson; import com.google.gson.internal.LinkedTreeMap; import com.velocitypowered.api.network.ProtocolVersion; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Collections; import java.util.EnumMap; -import java.util.HashMap; +import java.util.Locale; import java.util.Map; -import java.util.Objects; +import java.util.UUID; import net.elytrium.limboapi.LimboAPI; -import net.elytrium.limboapi.api.chunk.BlockEntityVersion; -import net.elytrium.limboapi.api.chunk.VirtualBlockEntity; +import net.elytrium.limboapi.api.world.chunk.blockentity.BlockEntityVersion; +import net.elytrium.limboapi.api.world.chunk.block.VirtualBlock; +import net.elytrium.limboapi.api.world.chunk.blockentity.VirtualBlockEntity; +import net.elytrium.limboapi.api.world.chunk.VirtualChunk; +import net.elytrium.limboapi.api.world.item.Item; +import net.elytrium.limboapi.api.world.WorldVersion; +import net.elytrium.limboapi.server.item.codec.data.ComponentHolderCodec; +import net.elytrium.limboapi.utils.JsonUtil; +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.BinaryTagTypes; import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.nbt.IntArrayBinaryTag; +import net.kyori.adventure.nbt.IntBinaryTag; +import net.kyori.adventure.nbt.ListBinaryTag; +import net.kyori.adventure.nbt.StringBinaryTag; +import org.checkerframework.checker.nullness.qual.Nullable; public class SimpleBlockEntity implements VirtualBlockEntity { - private static final Gson GSON = new Gson(); - - private static final Map MODERN_ID_MAP = new HashMap<>(); + private static final Object2ObjectOpenHashMap MODERN_ID_MAP = new Object2ObjectOpenHashMap<>(); + private final EnumMap versionIds = new EnumMap<>(BlockEntityVersion.class); private final String modernId; - private final Map versionIDs = new EnumMap<>(BlockEntityVersion.class); + private final String legacyId; - public SimpleBlockEntity(String modernId) { + private SimpleBlockEntity(String modernId, String legacyId) { this.modernId = modernId; + this.legacyId = legacyId; } @Override - public int getID(ProtocolVersion version) { - return this.getID(BlockEntityVersion.from(version)); + public String getModernId() { + return this.modernId; } @Override - public int getID(BlockEntityVersion version) { - return this.versionIDs.get(version); + public String getLegacyId() { + return this.legacyId == null ? this.modernId : this.legacyId; } @Override - public boolean isSupportedOn(ProtocolVersion version) { - return this.versionIDs.containsKey(BlockEntityVersion.from(version)); + public int getId(ProtocolVersion version) { + return this.getId(BlockEntityVersion.from(version)); } @Override - public boolean isSupportedOn(BlockEntityVersion version) { - return this.versionIDs.containsKey(version); + public int getId(BlockEntityVersion version) { + return this.versionIds.getOrDefault(version, Integer.MIN_VALUE); } @Override - public String getModernID() { - return this.modernId; + public boolean isSupportedOn(ProtocolVersion version) { + return this.versionIds.containsKey(BlockEntityVersion.from(version)); } @Override - public VirtualBlockEntity.Entry getEntry(int posX, int posY, int posZ, CompoundBinaryTag nbt) { - return new Entry(posX, posY, posZ, nbt); + public boolean isSupportedOn(BlockEntityVersion version) { + return this.versionIds.containsKey(version); } - @SuppressWarnings("unchecked") - public static void init() { - LinkedTreeMap> blockEntitiesMapping = GSON.fromJson( - new InputStreamReader( - Objects.requireNonNull(LimboAPI.class.getResourceAsStream("/mapping/blockentities_mapping.json")), StandardCharsets.UTF_8 - ), - LinkedTreeMap.class - ); + @Override + public VirtualBlockEntity.Entry createEntry(@Nullable VirtualChunk chunk, int posX, int posY, int posZ, @Nullable CompoundBinaryTag nbt) { + return new Entry(chunk, posX, posY, posZ, nbt); + } + + static { + var blockEntitiesMapping = JsonUtil.>parse(LimboAPI.class.getResourceAsStream("/mappings/block_entity_types_mappings.json")); + blockEntitiesMapping.forEach((modernId, versions) -> { + String v1_9Id = SimpleBlockEntity.modern2v1_9(modernId); + SimpleBlockEntity simpleBlockEntity = new SimpleBlockEntity(modernId, v1_9Id); + versions.forEach((version, entity) -> simpleBlockEntity.versionIds.put(BlockEntityVersion.from(ProtocolVersion.getProtocolVersion(Integer.parseInt(version))), entity.intValue())); + if (v1_9Id != null) { + simpleBlockEntity.versionIds.put(BlockEntityVersion.MINECRAFT_1_9, SimpleBlockEntity.protocolIdFromV1_9(v1_9Id)); + putLegacy: { + int legacyId; + switch (v1_9Id) { + case "MobSpawner" -> legacyId = 1; + case "Control" -> legacyId = 2; + case "Beacon" -> legacyId = 3; + case "Skull" -> legacyId = 4; + case "FlowerPot" -> legacyId = 5; + case "Banner" -> legacyId = 6; + case "UNKNOWN" -> legacyId = 7; + case "EndGateway" -> legacyId = 8; + case "Sign" -> legacyId = 9; + default -> { + break putLegacy; + } + } + + simpleBlockEntity.versionIds.put(BlockEntityVersion.LEGACY, legacyId); + } + } - blockEntitiesMapping.forEach((modernId, protocols) -> { - SimpleBlockEntity simpleBlockEntity = new SimpleBlockEntity(modernId); - protocols.forEach((key, value) -> simpleBlockEntity.versionIDs.put(BlockEntityVersion.parse(key), Integer.parseInt(value))); - MODERN_ID_MAP.put(modernId, simpleBlockEntity); + SimpleBlockEntity.MODERN_ID_MAP.put(modernId, simpleBlockEntity); }); + + /* + try { + Field field = BlockEntityType.class.getDeclaredField("validBlocks"); + field.setAccessible(true); + BuiltInRegistries.BLOCK_ENTITY_TYPE.asHolderIdMap().forEach(holder -> { + try { + String key = holder.unwrapKey().orElseThrow().location().toString(); + List validBlocks = ((Set) field.get(holder.value())).stream().map(block -> BuiltInRegistries.BLOCK.getKey(block).toString()).filter(block -> !block.equals(key)).toList(); + if (!validBlocks.isEmpty()) { + System.out.println("\"" + key + "\",\n\"" + String.join("\", \"", validBlocks) + "\""); + } + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + }); + } catch (NoSuchFieldException e) { + throw new RuntimeException(e); + } + */ + SimpleBlockEntity.registerAliases("minecraft:chest", + "minecraft:copper_chest", "minecraft:exposed_copper_chest", "minecraft:oxidized_copper_chest", "minecraft:waxed_copper_chest", "minecraft:waxed_exposed_copper_chest", + "minecraft:waxed_oxidized_copper_chest", "minecraft:waxed_weathered_copper_chest", "minecraft:weathered_copper_chest" + ); + SimpleBlockEntity.registerAliases("minecraft:sign", + "minecraft:acacia_sign", "minecraft:acacia_wall_sign", "minecraft:bamboo_sign", "minecraft:bamboo_wall_sign", "minecraft:birch_sign", "minecraft:birch_wall_sign", + "minecraft:cherry_sign", "minecraft:cherry_wall_sign", "minecraft:crimson_sign", "minecraft:crimson_wall_sign", "minecraft:dark_oak_sign", "minecraft:dark_oak_wall_sign", + "minecraft:jungle_sign", "minecraft:jungle_wall_sign", "minecraft:mangrove_sign", "minecraft:mangrove_wall_sign", "minecraft:oak_sign", "minecraft:oak_wall_sign", + "minecraft:pale_oak_sign", "minecraft:pale_oak_wall_sign", "minecraft:spruce_sign", "minecraft:spruce_wall_sign", "minecraft:warped_sign", "minecraft:warped_wall_sign" + ); + SimpleBlockEntity.registerAliases("minecraft:hanging_sign", + "minecraft:acacia_hanging_sign", "minecraft:acacia_wall_hanging_sign", "minecraft:bamboo_hanging_sign", "minecraft:bamboo_wall_hanging_sign", + "minecraft:birch_hanging_sign", "minecraft:birch_wall_hanging_sign", "minecraft:cherry_hanging_sign", "minecraft:cherry_wall_hanging_sign", + "minecraft:crimson_hanging_sign", "minecraft:crimson_wall_hanging_sign", "minecraft:dark_oak_hanging_sign", "minecraft:dark_oak_wall_hanging_sign", + "minecraft:jungle_hanging_sign", "minecraft:jungle_wall_hanging_sign", "minecraft:mangrove_hanging_sign", "minecraft:mangrove_wall_hanging_sign", + "minecraft:oak_hanging_sign", "minecraft:oak_wall_hanging_sign", "minecraft:pale_oak_hanging_sign", "minecraft:pale_oak_wall_hanging_sign", + "minecraft:spruce_hanging_sign", "minecraft:spruce_wall_hanging_sign", "minecraft:warped_hanging_sign", "minecraft:warped_wall_hanging_sign" + ); + SimpleBlockEntity.registerAliases("minecraft:mob_spawner", "minecraft:spawner"); + SimpleBlockEntity.registerAliases("minecraft:piston", "minecraft:moving_piston"); + SimpleBlockEntity.registerAliases("minecraft:skull", + "minecraft:creeper_head", "minecraft:creeper_wall_head", "minecraft:dragon_head", "minecraft:dragon_wall_head", "minecraft:piglin_head", + "minecraft:piglin_wall_head", "minecraft:player_head", "minecraft:player_wall_head", "minecraft:skeleton_skull", "minecraft:skeleton_wall_skull", + "minecraft:wither_skeleton_skull", "minecraft:wither_skeleton_wall_skull", "minecraft:zombie_head", "minecraft:zombie_wall_head" + ); + SimpleBlockEntity.registerAliases("minecraft:banner", + "minecraft:black_banner", "minecraft:black_wall_banner", "minecraft:blue_banner", "minecraft:blue_wall_banner", "minecraft:brown_banner", "minecraft:brown_wall_banner", + "minecraft:cyan_banner", "minecraft:cyan_wall_banner", "minecraft:gray_banner", "minecraft:gray_wall_banner", "minecraft:green_banner", "minecraft:green_wall_banner", + "minecraft:light_blue_banner", "minecraft:light_blue_wall_banner", "minecraft:light_gray_banner", "minecraft:light_gray_wall_banner", "minecraft:lime_banner", "minecraft:lime_wall_banner", + "minecraft:magenta_banner", "minecraft:magenta_wall_banner", "minecraft:orange_banner", "minecraft:orange_wall_banner", "minecraft:pink_banner", "minecraft:pink_wall_banner", + "minecraft:purple_banner", "minecraft:purple_wall_banner", "minecraft:red_banner", "minecraft:red_wall_banner", "minecraft:white_banner", "minecraft:white_wall_banner", + "minecraft:yellow_banner", "minecraft:yellow_wall_banner" + ); + SimpleBlockEntity.registerAliases("minecraft:command_block", "minecraft:chain_command_block", "minecraft:repeating_command_block"); + SimpleBlockEntity.registerAliases("minecraft:shulker_box", + "minecraft:black_shulker_box", "minecraft:blue_shulker_box", "minecraft:brown_shulker_box", "minecraft:cyan_shulker_box", "minecraft:gray_shulker_box", "minecraft:green_shulker_box", + "minecraft:light_blue_shulker_box", "minecraft:light_gray_shulker_box", "minecraft:lime_shulker_box", "minecraft:magenta_shulker_box", "minecraft:orange_shulker_box", + "minecraft:pink_shulker_box", "minecraft:purple_shulker_box", "minecraft:red_shulker_box", "minecraft:white_shulker_box", "minecraft:yellow_shulker_box" + ); + SimpleBlockEntity.registerAliases("minecraft:bed", + "minecraft:black_bed", "minecraft:blue_bed", "minecraft:brown_bed", "minecraft:cyan_bed", "minecraft:gray_bed", "minecraft:green_bed", "minecraft:light_blue_bed", "minecraft:light_gray_bed", + "minecraft:lime_bed", "minecraft:magenta_bed", "minecraft:orange_bed", "minecraft:pink_bed", "minecraft:purple_bed", "minecraft:red_bed", "minecraft:white_bed", "minecraft:yellow_bed" + ); + SimpleBlockEntity.registerAliases("minecraft:campfire", "minecraft:soul_campfire"); + SimpleBlockEntity.registerAliases("minecraft:beehive", "minecraft:bee_nest"); + SimpleBlockEntity.registerAliases("minecraft:shelf", + "minecraft:acacia_shelf", "minecraft:bamboo_shelf", "minecraft:birch_shelf", "minecraft:cherry_shelf", "minecraft:crimson_shelf", "minecraft:dark_oak_shelf", + "minecraft:jungle_shelf", "minecraft:mangrove_shelf", "minecraft:oak_shelf", "minecraft:pale_oak_shelf", "minecraft:spruce_shelf", "minecraft:warped_shelf" + ); + SimpleBlockEntity.registerAliases("minecraft:brushable_block", "minecraft:suspicious_sand", "minecraft:suspicious_gravel"); + SimpleBlockEntity.registerAliases("minecraft:copper_golem_statue", + "minecraft:exposed_copper_golem_statue", "minecraft:oxidized_copper_golem_statue", "minecraft:waxed_copper_golem_statue", "minecraft:waxed_exposed_copper_golem_statue", + "minecraft:waxed_oxidized_copper_golem_statue", "minecraft:waxed_weathered_copper_golem_statue", "minecraft:weathered_copper_golem_statue" + ); + SimpleBlockEntity.MODERN_ID_MAP.trim(); } - public static SimpleBlockEntity fromModernID(String id) { - return MODERN_ID_MAP.get(id); + private static void registerAliases(String target, String... aliases) { + SimpleBlockEntity targetBlockEntity = SimpleBlockEntity.MODERN_ID_MAP.get(target); + for (String alias : aliases) { + SimpleBlockEntity.MODERN_ID_MAP.put(alias, targetBlockEntity); + } + } + + public static SimpleBlockEntity fromModernId(String modernId) { + SimpleBlockEntity entity = SimpleBlockEntity.MODERN_ID_MAP.get(modernId); + if (entity == null) { + LimboAPI.getLogger().warn("BlockEntity {} is not supported, and will be omitted.", modernId); + return null; + } + + return entity; + } + + public static SimpleBlockEntity fromLegacyId(String legacyId) { + return SimpleBlockEntity.fromModernId(SimpleBlockEntity.legacy2modern(legacyId)); + } + + // https://github.com/ViaVersion/ViaVersion/blob/2aec3ce6d1b82672905c4b6e6a88bdea3c01f16f/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_11to1_10/rewriter/BlockEntityRewriter.java + private static String modern2v1_9(String modernId) { + return switch (modernId) { + case "minecraft:furnace" -> "Furnace"; + case "minecraft:chest" -> "Chest"; + case "minecraft:ender_chest" -> "EnderChest"; + case "minecraft:jukebox" -> "RecordPlayer"; + case "minecraft:dispenser" -> "Trap"; + case "minecraft:dropper" -> "Dropper"; + case "minecraft:sign" -> "Sign"; + case "minecraft:mob_spawner" -> "MobSpawner"; + case "minecraft:noteblock" -> "Music"; + case "minecraft:piston" -> "Piston"; + case "minecraft:brewing_stand" -> "Cauldron"; + case "minecraft:enchanting_table" -> "EnchantTable"; + case "minecraft:end_portal" -> "Airportal"; + case "minecraft:beacon" -> "Beacon"; + case "minecraft:skull" -> "Skull"; + case "minecraft:daylight_detector" -> "DLDetector"; + case "minecraft:hopper" -> "Hopper"; + case "minecraft:comparator" -> "Comparator"; + case "minecraft:flower_pot" -> "FlowerPot"; + case "minecraft:banner" -> "Banner"; + case "minecraft:structure_block" -> "Structure"; + case "minecraft:end_gateway" -> "EndGateway"; + case "minecraft:command_block" -> "Control"; + default -> null; + }; + } + + private static int protocolIdFromV1_9(String legacyId) { + return switch (legacyId) { + case "Furnace" -> 0; + case "Chest" -> 1; + case "EnderChest" -> 2; + case "RecordPlayer" -> 3; + case "Trap" -> 4; + case "Dropper" -> 5; + case "Sign" -> 6; + case "MobSpawner" -> 7; + case "Music" -> 8; + case "Piston" -> 9; + case "Cauldron" -> 10; + case "EnchantTable" -> 11; + case "Airportal" -> 12; + case "Beacon" -> 13; + case "Skull" -> 14; + case "DLDetector" -> 15; + case "Hopper" -> 16; + case "Comparator" -> 17; + case "FlowerPot" -> 18; + case "Banner" -> 19; + case "Structure" -> 20; + case "EndGateway" -> 21; + case "Control" -> 22; + default -> throw new IllegalStateException("Unexpected value: " + legacyId); + }; + } + + private static String legacy2modern(String legacyId) { + return switch (legacyId) { + case "Furnace" -> "minecraft:furnace"; + case "Chest" -> "minecraft:chest"; + case "EnderChest" -> "minecraft:ender_chest"; + case "RecordPlayer" -> "minecraft:jukebox"; + case "Trap" -> "minecraft:dispenser"; + case "Dropper" -> "minecraft:dropper"; + case "Sign" -> "minecraft:sign"; + case "MobSpawner" -> "minecraft:mob_spawner"; + case "Music" -> "minecraft:noteblock"; + case "Piston" -> "minecraft:piston"; + case "Cauldron" -> "minecraft:brewing_stand"; + case "EnchantTable" -> "minecraft:enchanting_table"; + case "Airportal" -> "minecraft:end_portal"; + case "Beacon" -> "minecraft:beacon"; + case "Skull" -> "minecraft:skull"; + case "DLDetector" -> "minecraft:daylight_detector"; + case "Hopper" -> "minecraft:hopper"; + case "Comparator" -> "minecraft:comparator"; + case "FlowerPot" -> "minecraft:flower_pot"; + case "Banner" -> "minecraft:banner"; + case "Structure" -> "minecraft:structure_block"; + case "EndGateway" -> "minecraft:end_gateway"; + case "Control" -> "minecraft:command_block"; + default -> legacyId; + }; } public class Entry implements VirtualBlockEntity.Entry { + + private static boolean warned; + + private final VirtualChunk chunk; private final int posX; private final int posY; private final int posZ; + @Nullable private final CompoundBinaryTag nbt; - public Entry(int posX, int posY, int posZ, CompoundBinaryTag nbt) { + public Entry(VirtualChunk chunk, int posX, int posY, int posZ, @Nullable CompoundBinaryTag nbt) { + this.chunk = chunk; this.posX = posX; this.posY = posY; this.posZ = posZ; @@ -127,29 +348,551 @@ public int getPosZ() { return this.posZ; } - @Override - public CompoundBinaryTag getNbt() { - return this.nbt; + private static BinaryTag convertSignTextToJson(ProtocolVersion version, BinaryTag tag) { + if (tag instanceof CompoundBinaryTag compoundBinaryTag) { + var builder = CompoundBinaryTag.builder().put(compoundBinaryTag); + if (compoundBinaryTag.get("messages") instanceof ListBinaryTag messages) { + builder.put("messages", messages.stream().map(binaryTag -> Entry.toJson(version, binaryTag)).collect(ListBinaryTag.toListTag())); + } + + if (compoundBinaryTag.get("filtered_messages") instanceof ListBinaryTag filteredMessages) { + builder.put("filtered_messages", filteredMessages.stream().map(binaryTag -> Entry.toJson(version, binaryTag)).collect(ListBinaryTag.toListTag())); + } + + return builder.build(); + } + + return tag; } - @Override - public int getID(ProtocolVersion version) { - return SimpleBlockEntity.this.getID(version); + private static StringBinaryTag toJson(ProtocolVersion version, BinaryTag tag) { + return StringBinaryTag.stringBinaryTag(ComponentHolderCodec.CODEC.component2Json(version, ComponentHolderCodec.CODEC.binaryTag2Component(version, tag))); } @Override - public int getID(BlockEntityVersion version) { - return SimpleBlockEntity.this.getID(version); + public CompoundBinaryTag getNbt(ProtocolVersion version) { // adventure-nbt is the worst api I ever used + CompoundBinaryTag.Builder nbt = null; + // TODO fix banners 1.20.5+ by adding patterns to the registry (but i'm not sure is it important thing and does someone will need it) + if (this.nbt != null && version.lessThan(ProtocolVersion.MINECRAFT_1_21_5)) { + if (!Entry.warned && this.nbt.get("SkullOwner") != null) { + LimboAPI.getLogger().warn("The world contains legacy block entities, it's recommended to update your schema to the latest version (>=1.21.5)."); + Entry.warned = true; + } + + // https://github.com/ViaVersion/ViaBackwards/blob/5.5.1/common/src/main/java/com/viaversion/viabackwards/protocol/v1_21_5to1_21_4/rewriter/BlockItemPacketRewriter1_21_5.java#L300 + { + BinaryTag customName = this.nbt.get("CustomName"); + if (customName != null) { + if (nbt == null) { + nbt = CompoundBinaryTag.builder().put(this.nbt); + } + + nbt.put("CustomName", Entry.toJson(version, customName)); + } + + // https://github.com/ViaVersion/ViaBackwards/blob/4.10.2/common/src/main/java/com/viaversion/viabackwards/protocol/protocol1_19_4to1_20/packets/BlockItemPackets1_20.java#L191 + int id = SimpleBlockEntity.this.getId(ProtocolVersion.MINECRAFT_1_20); + if (id == 7 || id == 8) { + BinaryTag frontText = this.nbt.get("front_text"); + BinaryTag backText = this.nbt.get("back_text"); + if (frontText != null || backText != null) { + if (nbt == null) { + nbt = CompoundBinaryTag.builder().put(this.nbt); + } + + frontText = Entry.convertSignTextToJson(version, frontText); + if (version.lessThan(ProtocolVersion.MINECRAFT_1_20)) { + nbt.remove("front_text"); + nbt.remove("back_text"); + if (version.greaterThan(ProtocolVersion.MINECRAFT_1_9_2) && frontText instanceof CompoundBinaryTag frontTextTag) { + if (frontTextTag.get("messages") instanceof ListBinaryTag messages) { + boolean noGreaterThan11 = version.noGreaterThan(ProtocolVersion.MINECRAFT_1_11_1); + int i = 0; + var serializer = ProtocolUtils.getJsonChatSerializer(version); + for (BinaryTag message : messages) { + nbt.put("Text" + ++i, noGreaterThan11 ? StringBinaryTag.stringBinaryTag(serializer.serialize(serializer.deserialize(((StringBinaryTag) message).value()))) : message); + } + } + + if (frontTextTag.get("filtered_messages") instanceof ListBinaryTag filteredMessages) { + int i = 0; + for (BinaryTag message : filteredMessages) { + nbt.put("FilteredText" + ++i, message); + } + } + + BinaryTag color = frontTextTag.get("color"); + if (color != null) { + nbt.put("Color", color); + } + + BinaryTag glowing = frontTextTag.get("has_glowing_text"); + if (glowing != null) { + nbt.put("GlowingText", glowing); + } + } + } else { + if (frontText != null) { + nbt.put("front_text", frontText); + } + backText = Entry.convertSignTextToJson(version, backText); + if (backText != null) { + nbt.put("back_text", backText); + } + } + } + } + } + + // https://github.com/ViaVersion/ViaBackwards/blob/4.10.2/common/src/main/java/com/viaversion/viabackwards/protocol/protocol1_20_3to1_20_5/rewriter/BlockItemPacketRewriter1_20_5.java#L190 + if (version.lessThan(ProtocolVersion.MINECRAFT_1_20_5)) { + String modernId = SimpleBlockEntity.this.getModernId(); + boolean lessThen16 = version.lessThan(ProtocolVersion.MINECRAFT_1_16); + boolean noGreaterThan12 = lessThen16 && version.noGreaterThan(ProtocolVersion.MINECRAFT_1_12_2); + + BinaryTag profile = this.nbt.get("profile"); + if (profile != null) { + if (nbt == null) { + nbt = CompoundBinaryTag.builder().put(this.nbt); + } + + nbt.remove("profile"); + if (profile instanceof StringBinaryTag) { + // https://github.com/ViaVersion/ViaBackwards/blob/4.10.2/common/src/main/java/com/viaversion/viabackwards/protocol/protocol1_15_2to1_16/packets/BlockItemPackets1_16.java#L270 + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) { + nbt.put("SkullOwner", profile); + } + } else if (profile instanceof CompoundBinaryTag compound) { + nbt.put(lessThen16 ? "Owner" : "SkullOwner", Entry.updateProfileTag(compound, version)); + } + + // https://github.com/ViaVersion/ViaBackwards/blob/4.10.2/common/src/main/java/com/viaversion/viabackwards/protocol/protocol1_12_2to1_13/block_entity_handlers/SkullHandler.java#L28 + if (noGreaterThan12 && this.chunk != null) { + int diff = this.getBlock().blockStateId(ProtocolVersion.MINECRAFT_1_13) - 5447/*SKULL_START*/; + nbt.putByte("SkullType", (byte) Math.floor(diff / 20.0F)); + int pos = diff % 20; + if (pos >= 4) { + nbt.putByte("Rot", (byte) ((pos - 4) & 0xFF)); + } + } + } + + if (this.nbt.get("patterns") instanceof ListBinaryTag patterns) { + int i = 0; + for (BinaryTag pattern : patterns) { + if (pattern instanceof CompoundBinaryTag patternTag) { + if (patternTag.get("color") instanceof StringBinaryTag colorTag) { + String legacy = Entry.patternIdModern2Legacy(patternTag.getString("pattern", "")); + if (legacy != null) { + int color = Entry.colorIdModern2Legacy(colorTag.value()); + patterns = patterns.set(i, CompoundBinaryTag.builder().put(patternTag) + .remove("pattern") + .remove("color") + .putString("Pattern", legacy) + .putInt("Color", noGreaterThan12 ? 15 - color : color) + .build(), null + ); + } + } + } + + ++i; + } + + if (nbt == null) { + nbt = CompoundBinaryTag.builder().put(this.nbt); + } + + nbt.remove("patterns"); + nbt.put("Patterns", patterns); + + if (noGreaterThan12 && this.chunk != null) { + short id = this.getBlock().blockStateId(ProtocolVersion.MINECRAFT_1_13); + if (id >= 6854/*BANNER_START*/ && id <= 7109/*BANNER_STOP*/) { + nbt.putInt("Base", 15 - ((id - 6854/*BANNER_START*/) >> 4)); + } else if (id >= 7110/*WALL_BANNER_START*/ && id <= 7173/*WALL_BANNER_STOP*/) { + nbt.putInt("Base", 15 - ((id - 7110/*WALL_BANNER_START*/) >> 2)); + } else { + LimboAPI.getLogger().warn("Why does this block have the banner block entity? nbt={}", this.nbt); + } + } + } + + if (version.lessThan(ProtocolVersion.MINECRAFT_1_20_2)) { + // https://github.com/ViaVersion/ViaBackwards/blob/4.10.2/common/src/main/java/com/viaversion/viabackwards/protocol/protocol1_20to1_20_2/rewriter/BlockItemPacketRewriter1_20_2.java#L397 + BinaryTag primaryEffect = this.nbt.get("primary_effect"); + BinaryTag secondaryEffect = this.nbt.get("secondary_effect"); + boolean primary = primaryEffect instanceof StringBinaryTag; + boolean secondary = secondaryEffect instanceof StringBinaryTag; + if (primary || secondary) { + if (nbt == null) { + nbt = CompoundBinaryTag.builder().put(this.nbt); + } + + if (primary) { + nbt.remove("primary_effect"); + nbt.putInt("Primary", Entry.potionEffectLegacyId(((StringBinaryTag) primaryEffect).value()) + 1/*Empty effect at 0*/); + } + + if (secondary) { + nbt.remove("secondary_effect"); + nbt.putInt("Secondary", Entry.potionEffectLegacyId(((StringBinaryTag) secondaryEffect).value()) + 1/*Empty effect at 0*/); + } + } + + // https://github.com/ViaVersion/ViaBackwards/blob/5.1.1/common/src/main/java/com/viaversion/viabackwards/protocol/v1_18to1_17_1/rewriter/BlockItemPacketRewriter1_18.java#249 + if (version.lessThan(ProtocolVersion.MINECRAFT_1_18)) { + if (nbt == null) { + nbt = CompoundBinaryTag.builder().put(this.nbt); + } + + { + nbt.put("x", IntBinaryTag.intBinaryTag(this.posX)); + nbt.put("y", IntBinaryTag.intBinaryTag(this.posY)); + nbt.put("z", IntBinaryTag.intBinaryTag(this.posZ)); + String id = version.noLessThan(ProtocolVersion.MINECRAFT_1_11) ? modernId : SimpleBlockEntity.this.getLegacyId(); + BinaryTag currentId = this.nbt.get("id"); + if (currentId == null || !((StringBinaryTag) currentId).value().equals(id)) { + nbt.putString("id", id); + } + } + + if (SimpleBlockEntity.this.getId(ProtocolVersion.MINECRAFT_1_17_1) == 8 && this.nbt.get("SpawnData") instanceof CompoundBinaryTag spawnData) { + CompoundBinaryTag entityData = spawnData.getCompound("entity"); + label: if (entityData != CompoundBinaryTag.empty()) { + // https://github.com/ViaVersion/ViaBackwards/blob/5.1.1/common/src/main/java/com/viaversion/viabackwards/protocol/v1_13to1_12_2/block_entity_handlers/SpawnerHandler.java + if (noGreaterThan12) { + String id = entityData.getString("id"); + String legacyId = switch (id) { + case "minecraft:command_block_minecart" -> "minecraft:commandblock_minecart"; + case "minecraft:end_crystal" -> "minecraft:ender_crystal"; + case "minecraft:evoker_fangs" -> "minecraft:evocation_fangs"; + case "minecraft:evoker" -> "minecraft:evocation_illager"; + case "minecraft:eye_of_ender" -> "minecraft:eye_of_ender_signal"; + case "minecraft:firework_rocket" -> "minecraft:fireworks_rocket"; + case "minecraft:illusioner" -> "minecraft:illusion_illager"; + case "minecraft:snow_golem" -> "minecraft:snowman"; + case "minecraft:iron_golem" -> "minecraft:villager_golem"; + case "minecraft:vindicator" -> "minecraft:vindication_illager"; + case "minecraft:experience_bottle" -> "minecraft:xp_bottle"; + case "minecraft:experience_orb" -> "minecraft:xp_orb"; + default -> id; + }; + if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_10)) { + legacyId = Entry.entityIdModern2Legacy(legacyId); + } + + if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_8)) { + nbt.remove("SpawnData"); + nbt.putString("EntityId", legacyId); + break label; + } + + entityData = entityData.putString("id", legacyId); + } + + nbt.put("SpawnData", entityData); + } + } + + // https://github.com/ViaVersion/ViaBackwards/blob/4.10.2/common/src/main/java/com/viaversion/viabackwards/protocol/protocol1_15_2to1_16/packets/BlockItemPackets1_16.java#L261 + if (lessThen16 && modernId.equals("minecraft:conduit") && this.nbt.get("Target") instanceof IntArrayBinaryTag targetUuid) { + nbt.remove("Target"); + nbt.put("target_uuid", Entry.uuid(version, targetUuid)); + } + + // https://github.com/ViaVersion/ViaBackwards/blob/5.1.1/common/src/main/java/com/viaversion/viabackwards/protocol/v1_13to1_12_2/block_entity_handlers/BedHandler.java + if (noGreaterThan12 && version.noLessThan(ProtocolVersion.MINECRAFT_1_12) && this.chunk != null && modernId.equals("minecraft:bed")) { + nbt.putInt("color", (this.getBlock().blockStateId(ProtocolVersion.MINECRAFT_1_13) - 748) >> 4); + } + + if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_7_6)) { + String itemId = this.nbt.getString("Item"); + if (!itemId.isEmpty()) { + try { + nbt.putInt("Item", Item.valueOf(itemId.substring(10).toUpperCase(Locale.ROOT)).getLegacyId()); + } catch (IllegalArgumentException e) { + nbt.putInt("Item", SimpleItem.fromModernId(itemId).itemId(WorldVersion.LEGACY)); + } + } + } + } + } + } + } + + return nbt == null ? this.nbt : nbt.build(); } - @Override - public boolean isSupportedOn(ProtocolVersion version) { - return SimpleBlockEntity.this.isSupportedOn(version); + private VirtualBlock getBlock() { + return this.chunk.getBlock(this.posX & 0x0F, this.posY, this.posZ & 0x0F); + } + + private static CompoundBinaryTag updateProfileTag(CompoundBinaryTag profileTag, ProtocolVersion version) { + CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder(profileTag.size()); + { + BinaryTag name = profileTag.get("name"); + if (name instanceof StringBinaryTag) { + builder.put("Name", name); + } + + BinaryTag id = profileTag.get("id"); + if (profileTag.get("properties") instanceof ListBinaryTag oldProperties) { + CompoundBinaryTag.Builder newPropertiesBuilder = CompoundBinaryTag.builder(oldProperties.size()); + for (BinaryTag oldPropertyTag : oldProperties) { + if (oldPropertyTag instanceof CompoundBinaryTag oldProperty) { + BinaryTag value = oldProperty.get("value"); + if (value == null) { + value = StringBinaryTag.stringBinaryTag(""); + } + + newPropertiesBuilder.put(oldProperty.getString("name", ""), ListBinaryTag.listBinaryTag(BinaryTagTypes.COMPOUND, Collections.singletonList( + CompoundBinaryTag.from(oldProperty.get("signature") instanceof StringBinaryTag signature + ? Map.of("Value", value, "Signature", signature) + : Collections.singletonMap("Value", value) + ) + ))); + } + } + CompoundBinaryTag newProperties = newPropertiesBuilder.build(); + builder.put("Properties", newProperties); + if (id instanceof IntArrayBinaryTag) { + // https://github.com/ViaVersion/ViaBackwards/blob/4.10.2/common/src/main/java/com/viaversion/viabackwards/protocol/protocol1_16_1to1_16_2/packets/BlockItemPackets1_16_2.java#L140 + BinaryTag firstValue; + builder.put("Id", + version.lessThan(ProtocolVersion.MINECRAFT_1_16_2) + && newProperties.get("textures") instanceof ListBinaryTag textures + && !textures.isEmpty() && textures.get(0) instanceof CompoundBinaryTag firstTexture && (firstValue = firstTexture.get("Value")) != null + ? Entry.uuid(version, firstValue.hashCode(), 0, 0, 0) + : Entry.uuid(version, (IntArrayBinaryTag) id) + ); + } + } else if (id instanceof IntArrayBinaryTag) { + builder.put("Id", id); + } + } + + return builder.build(); + } + + // https://github.com/ViaVersion/ViaBackwards/blob/4.10.2/common/src/main/java/com/viaversion/viabackwards/protocol/protocol1_15_2to1_16/packets/BlockItemPackets1_16.java#L268 + private static BinaryTag uuid(ProtocolVersion version, int... array) { + if (version.lessThan(ProtocolVersion.MINECRAFT_1_16)) { + return StringBinaryTag.stringBinaryTag(new UUID((long) array[0] << 32 | (array[1] & 0xFFFFFFFFL), (long) array[2] << 32 | (array[3] & 0xFFFFFFFFL)).toString()); + } + + return IntArrayBinaryTag.intArrayBinaryTag(array); + } + + private static BinaryTag uuid(ProtocolVersion version, IntArrayBinaryTag array) { + if (version.lessThan(ProtocolVersion.MINECRAFT_1_16)) { + if (array.size() != 4) { + return StringBinaryTag.stringBinaryTag("00000000-0000-0000-0000-000000000000"); + } + + return StringBinaryTag.stringBinaryTag(new UUID((long) array.get(0) << 32 | (array.get(1) & 0xFFFFFFFFL), (long) array.get(2) << 32 | (array.get(3) & 0xFFFFFFFFL)).toString()); + } + + return array; + } + + private static int potionEffectLegacyId(String modern) { + return switch (modern) { + case "minecraft:speed" -> 0; + case "minecraft:slowness" -> 1; + case "minecraft:haste" -> 2; + case "minecraft:mining_fatigue" -> 3; + case "minecraft:strength" -> 4; + case "minecraft:instant_health" -> 5; + case "minecraft:instant_damage" -> 6; + case "minecraft:jump_boost" -> 7; + case "minecraft:nausea" -> 8; + case "minecraft:regeneration" -> 9; + case "minecraft:resistance" -> 10; + case "minecraft:fire_resistance" -> 11; + case "minecraft:water_breathing" -> 12; + case "minecraft:invisibility" -> 13; + case "minecraft:blindness" -> 14; + case "minecraft:night_vision" -> 15; + case "minecraft:hunger" -> 16; + case "minecraft:weakness" -> 17; + case "minecraft:poison" -> 18; + case "minecraft:wither" -> 19; + case "minecraft:health_boost" -> 20; + case "minecraft:absorption" -> 21; + case "minecraft:saturation" -> 22; + case "minecraft:glowing" -> 23; + case "minecraft:levitation" -> 24; + case "minecraft:luck" -> 25; + case "minecraft:unluck" -> 26; + case "minecraft:slow_falling" -> 27; + case "minecraft:conduit_power" -> 28; + case "minecraft:dolphins_grace" -> 29; + case "minecraft:bad_omen" -> 30; + case "minecraft:hero_of_the_village" -> 31; + case "minecraft:darkness" -> 32; + default -> -1; + }; + } + + private static String patternIdModern2Legacy(String modern) { + return switch (modern) { + case "minecraft:base" -> "b"; + case "minecraft:square_bottom_left" -> "bl"; + case "minecraft:square_bottom_right" -> "br"; + case "minecraft:square_top_left" -> "tl"; + case "minecraft:square_top_right" -> "tr"; + case "minecraft:stripe_bottom" -> "bs"; + case "minecraft:stripe_top" -> "ts"; + case "minecraft:stripe_left" -> "ls"; + case "minecraft:stripe_right" -> "rs"; + case "minecraft:stripe_center" -> "cs"; + case "minecraft:stripe_middle" -> "ms"; + case "minecraft:stripe_downright" -> "drs"; + case "minecraft:stripe_downleft" -> "dls"; + case "minecraft:small_stripes" -> "ss"; + case "minecraft:cross" -> "cr"; + case "minecraft:straight_cross" -> "sc"; + case "minecraft:triangle_bottom" -> "bt"; + case "minecraft:triangle_top" -> "tt"; + case "minecraft:triangles_bottom" -> "bts"; + case "minecraft:triangles_top" -> "tts"; + case "minecraft:diagonal_left" -> "ld"; + case "minecraft:diagonal_up_right" -> "rd"; + case "minecraft:diagonal_up_left" -> "lud"; + case "minecraft:diagonal_right" -> "rud"; + case "minecraft:circle" -> "mc"; + case "minecraft:rhombus" -> "mr"; + case "minecraft:half_vertical" -> "vh"; + case "minecraft:half_horizontal" -> "hh"; + case "minecraft:half_vertical_right" -> "vhr"; + case "minecraft:half_horizontal_bottom" -> "hhb"; + case "minecraft:border" -> "bo"; + case "minecraft:curly_border" -> "cbo"; + case "minecraft:gradient" -> "gra"; + case "minecraft:gradient_up" -> "gru"; + case "minecraft:bricks" -> "bri"; + case "minecraft:globe" -> "glb"; + case "minecraft:creeper" -> "cre"; + case "minecraft:skull" -> "sku"; + case "minecraft:flower" -> "flo"; + case "minecraft:mojang" -> "moj"; + case "minecraft:piglin" -> "pig"; + default -> null; + }; + } + + private static int colorIdModern2Legacy(String color) { + return switch (color) { + case "orange" -> 1; + case "magenta" -> 2; + case "light_blue" -> 3; + case "yellow" -> 4; + case "lime" -> 5; + case "pink" -> 6; + case "gray" -> 7; + case "light_gray" -> 8; + case "cyan" -> 9; + case "purple" -> 10; + case "blue" -> 11; + case "brown" -> 12; + case "green" -> 13; + case "red" -> 14; + case "black" -> 15; + default -> 0; + }; + } + + private static String entityIdModern2Legacy(String entityId) { + return switch (entityId) { + case "minecraft:area_effect_cloud" -> "AreaEffectCloud"; + case "minecraft:armor_stand" -> "ArmorStand"; + case "minecraft:arrow" -> "Arrow"; + case "minecraft:bat" -> "Bat"; + case "minecraft:blaze" -> "Blaze"; + case "minecraft:boat" -> "Boat"; + case "minecraft:cave_spider" -> "CaveSpider"; + case "minecraft:chicken" -> "Chicken"; + case "minecraft:cow" -> "Cow"; + case "minecraft:creeper" -> "Creeper"; + case "minecraft:donkey" -> "Donkey"; + case "minecraft:dragon_fireball" -> "DragonFireball"; + case "minecraft:elder_guardian" -> "ElderGuardian"; + case "minecraft:ender_crystal" -> "EnderCrystal"; + case "minecraft:ender_dragon" -> "EnderDragon"; + case "minecraft:enderman" -> "Enderman"; + case "minecraft:endermite" -> "Endermite"; + case "minecraft:horse" -> "EntityHorse"; + case "minecraft:eye_of_ender_signal" -> "EyeOfEnderSignal"; + case "minecraft:falling_block" -> "FallingSand"; + case "minecraft:fireball" -> "Fireball"; + case "minecraft:fireworks_rocket" -> "FireworksRocketEntity"; + case "minecraft:ghast" -> "Ghast"; + case "minecraft:giant" -> "Giant"; + case "minecraft:guardian" -> "Guardian"; + case "minecraft:husk" -> "Husk"; + case "minecraft:item" -> "Item"; + case "minecraft:item_frame" -> "ItemFrame"; + case "minecraft:magma_cube" -> "LavaSlime"; + case "minecraft:leash_knot" -> "LeashKnot"; + case "minecraft:chest_minecart" -> "MinecartChest"; + case "minecraft:commandblock_minecart" -> "MinecartCommandBlock"; + case "minecraft:furnace_minecart" -> "MinecartFurnace"; + case "minecraft:hopper_minecart" -> "MinecartHopper"; + case "minecraft:minecart" -> "MinecartRideable"; + case "minecraft:spawner_minecart" -> "MinecartSpawner"; + case "minecraft:tnt_minecart" -> "MinecartTNT"; + case "minecraft:mule" -> "Mule"; + case "minecraft:mooshroom" -> "MushroomCow"; + case "minecraft:ocelot" -> "Ozelot"; + case "minecraft:painting" -> "Painting"; + case "minecraft:pig" -> "Pig"; + case "minecraft:zombie_pigman" -> "PigZombie"; + case "minecraft:polar_bear" -> "PolarBear"; + case "minecraft:tnt" -> "PrimedTnt"; + case "minecraft:rabbit" -> "Rabbit"; + case "minecraft:sheep" -> "Sheep"; + case "minecraft:shulker" -> "Shulker"; + case "minecraft:shulker_bullet" -> "ShulkerBullet"; + case "minecraft:silverfish" -> "Silverfish"; + case "minecraft:skeleton" -> "Skeleton"; + case "minecraft:skeleton_horse" -> "SkeletonHorse"; + case "minecraft:slime" -> "Slime"; + case "minecraft:small_fireball" -> "SmallFireball"; + case "minecraft:snowball" -> "Snowball"; + case "minecraft:snowman" -> "SnowMan"; + case "minecraft:spectral_arrow" -> "SpectralArrow"; + case "minecraft:spider" -> "Spider"; + case "minecraft:squid" -> "Squid"; + case "minecraft:stray" -> "Stray"; + case "minecraft:egg" -> "ThrownEgg"; + case "minecraft:ender_pearl" -> "ThrownEnderpearl"; + case "minecraft:xp_bottle" -> "ThrownExpBottle"; + case "minecraft:potion" -> "ThrownPotion"; + case "minecraft:villager" -> "Villager"; + case "minecraft:villager_golem" -> "VillagerGolem"; + case "minecraft:witch" -> "Witch"; + case "minecraft:wither" -> "WitherBoss"; + case "minecraft:wither_skeleton" -> "WitherSkeleton"; + case "minecraft:wither_skull" -> "WitherSkull"; + case "minecraft:wolf" -> "Wolf"; + case "minecraft:xp_orb" -> "XPOrb"; + case "minecraft:zombie" -> "Zombie"; + case "minecraft:zombie_horse" -> "ZombieHorse"; + case "minecraft:zombie_villager" -> "ZombieVillager"; + default -> entityId; + }; } @Override - public boolean isSupportedOn(BlockEntityVersion version) { - return SimpleBlockEntity.this.isSupportedOn(version); + public String toString() { + return "Entry{" + + "chunk=" + this.chunk + + ", posX=" + this.posX + + ", posY=" + this.posY + + ", posZ=" + this.posZ + + ", nbt=" + this.nbt + + "}"; } } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/world/SimpleItem.java b/plugin/src/main/java/net/elytrium/limboapi/server/world/SimpleItem.java index a4ccb3b4..309ece1e 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/server/world/SimpleItem.java +++ b/plugin/src/main/java/net/elytrium/limboapi/server/world/SimpleItem.java @@ -17,115 +17,117 @@ package net.elytrium.limboapi.server.world; -import com.google.gson.Gson; import com.google.gson.internal.LinkedTreeMap; import com.velocitypowered.api.network.ProtocolVersion; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap; import java.util.EnumMap; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; import net.elytrium.limboapi.LimboAPI; -import net.elytrium.limboapi.api.material.Item; -import net.elytrium.limboapi.api.material.VirtualItem; -import net.elytrium.limboapi.api.material.WorldVersion; +import net.elytrium.limboapi.api.world.item.Item; +import net.elytrium.limboapi.api.world.item.VirtualItem; +import net.elytrium.limboapi.api.world.WorldVersion; +import net.elytrium.limboapi.utils.JsonUtil; public class SimpleItem implements VirtualItem { - private static final Gson GSON = new Gson(); - - private static final Map MODERN_ID_MAP = new HashMap<>(); - private static final Map LEGACY_ID_MAP = new HashMap<>(); + private static final EnumMap> VERSION_2_ID_MAP = new EnumMap<>(WorldVersion.class); + private static final Object2ObjectOpenHashMap MODERN_ID_MAP; + private static final Short2ObjectOpenHashMap LEGACY_ID_MAP; + private final EnumMap versionIds = new EnumMap<>(WorldVersion.class); private final String modernId; - private final Map versionIDs = new EnumMap<>(WorldVersion.class); public SimpleItem(String modernId) { this.modernId = modernId; } - @Override - public short getID(ProtocolVersion version) { - return this.getID(WorldVersion.from(version)); + public String modernId() { + return this.modernId; } @Override - public short getID(WorldVersion version) { - return this.versionIDs.get(version); + public short itemId(WorldVersion version) { + Short result = this.versionIds.get(version); + if (result == null) { + throw new IllegalArgumentException("Item " + this.modernId + " does not exists on " + version); + } + + return result; } @Override - public boolean isSupportedOn(ProtocolVersion version) { - return this.isSupportedOn(WorldVersion.from(version)); + public short itemId(ProtocolVersion version) { + return this.itemId(WorldVersion.from(version)); } @Override public boolean isSupportedOn(WorldVersion version) { - return this.versionIDs.containsKey(version); + return this.versionIds.containsKey(version); } - public String getModernID() { - return this.modernId; + @Override + public boolean isSupportedOn(ProtocolVersion version) { + return this.isSupportedOn(WorldVersion.from(version)); } - @SuppressWarnings("unchecked") - public static void init() { - LinkedTreeMap> itemsMapping = GSON.fromJson( - new InputStreamReader( - Objects.requireNonNull(LimboAPI.class.getResourceAsStream("/mapping/items_mapping.json")), StandardCharsets.UTF_8 - ), - LinkedTreeMap.class - ); - - LinkedTreeMap modernItems = GSON.fromJson( - new InputStreamReader( - Objects.requireNonNull(LimboAPI.class.getResourceAsStream("/mapping/items.json")), StandardCharsets.UTF_8 - ), - LinkedTreeMap.class - ); - - LinkedTreeMap legacyItems = GSON.fromJson( - new InputStreamReader( - Objects.requireNonNull(LimboAPI.class.getResourceAsStream("/mapping/legacyitems.json")), StandardCharsets.UTF_8 - ), - LinkedTreeMap.class - ); - - LinkedTreeMap modernIdRemap = GSON.fromJson( - new InputStreamReader( - Objects.requireNonNull(LimboAPI.class.getResourceAsStream("/mapping/modern_item_id_remap.json")), StandardCharsets.UTF_8 - ), - LinkedTreeMap.class - ); - - modernItems.forEach((modernId, modernProtocolId) -> { + static { + var itemsMapping = JsonUtil.>parse(LimboAPI.class.getResourceAsStream("/mappings/items_mappings.json")); + var modernIdRemap = JsonUtil.parse(LimboAPI.class.getResourceAsStream("/mappings/modern_item_id_remap.json")); + MODERN_ID_MAP = new Object2ObjectOpenHashMap<>(itemsMapping.size() + modernIdRemap.size()); + String maxProtocol = Integer.toString(ProtocolVersion.MAXIMUM_VERSION.getProtocol()); + itemsMapping.forEach((modernId, versions) -> { SimpleItem simpleItem = new SimpleItem(modernId); - itemsMapping.get(modernProtocolId).forEach((key, value) -> simpleItem.versionIDs.put(WorldVersion.parse(key), Short.parseShort(value))); - MODERN_ID_MAP.put(modernId, simpleItem); - - String remapped = modernIdRemap.get(modernId); - if (remapped != null) { - if (MODERN_ID_MAP.containsKey(remapped)) { - throw new IllegalStateException("Remapped id " + remapped + " (from " + modernId + ") already exists"); + versions.forEach((version, item) -> { + WorldVersion worldVersion = WorldVersion.from(ProtocolVersion.getProtocolVersion(Integer.parseInt(version))); + short id = item.shortValue(); + simpleItem.versionIds.put(worldVersion, id); + SimpleItem.VERSION_2_ID_MAP.computeIfAbsent(worldVersion, key -> new Short2ObjectOpenHashMap<>(itemsMapping.size())).put(id, simpleItem); + }); + if (versions.containsKey(maxProtocol)) { + SimpleItem.MODERN_ID_MAP.put(modernId, simpleItem); + + String remapped = modernIdRemap.get(modernId); + if (remapped != null) { + if (SimpleItem.MODERN_ID_MAP.containsKey(remapped)) { + throw new IllegalStateException("Remapped id " + remapped + " (from " + modernId + ") already exists"); + } + + SimpleItem.MODERN_ID_MAP.put(remapped, simpleItem); } - - MODERN_ID_MAP.put(remapped, simpleItem); } }); + SimpleItem.MODERN_ID_MAP.trim(); + SimpleItem.VERSION_2_ID_MAP.values().forEach(Short2ObjectOpenHashMap::trim); + + var legacyItems = JsonUtil.parse(LimboAPI.class.getResourceAsStream("/mappings/legacy_items.json")); + LEGACY_ID_MAP = new Short2ObjectOpenHashMap<>(legacyItems.size()); + legacyItems.forEach((legacyProtocolId, modernId) -> { + short id = Short.parseShort(legacyProtocolId); + SimpleItem value = SimpleItem.MODERN_ID_MAP.get(modernId); + SimpleItem.LEGACY_ID_MAP.put(id, value); + if (value != null) { + value.versionIds.put(WorldVersion.LEGACY, id); + } + }); + } - legacyItems.forEach((legacyProtocolId, modernId) -> LEGACY_ID_MAP.put(Integer.parseInt(legacyProtocolId), MODERN_ID_MAP.get(modernId))); + public static SimpleItem fromModernId(String id) { + return SimpleItem.MODERN_ID_MAP.get(id); } - public static SimpleItem fromItem(Item item) { - return LEGACY_ID_MAP.get(item.getLegacyID()); + public static VirtualItem fromId(ProtocolVersion version, short id) { + return SimpleItem.fromId(WorldVersion.from(version), id); + } + + public static VirtualItem fromId(WorldVersion version, short id) { + return SimpleItem.VERSION_2_ID_MAP.get(version).get(id); } - public static SimpleItem fromLegacyID(int id) { - return LEGACY_ID_MAP.get(id); + public static SimpleItem fromItem(Item item) { + return SimpleItem.fromLegacyId(item.getLegacyId()); } - public static SimpleItem fromModernID(String id) { - return MODERN_ID_MAP.get(id); + public static SimpleItem fromLegacyId(short id) { + return SimpleItem.LEGACY_ID_MAP.get(id); } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/world/SimpleParticlesManager.java b/plugin/src/main/java/net/elytrium/limboapi/server/world/SimpleParticlesManager.java new file mode 100644 index 00000000..da68bad4 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/world/SimpleParticlesManager.java @@ -0,0 +1,31 @@ +package net.elytrium.limboapi.server.world; + +import com.google.gson.internal.LinkedTreeMap; +import com.velocitypowered.api.network.ProtocolVersion; +import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap; +import java.util.EnumMap; +import java.util.Locale; +import net.elytrium.limboapi.LimboAPI; +import net.elytrium.limboapi.utils.JsonUtil; + +// TODO legacy (<=1.12.2) + api +public class SimpleParticlesManager { + + private static final EnumMap> PROTOCOL_2_PARTICLE = new EnumMap<>(ProtocolVersion.class); + + static { + var particlesMappings = JsonUtil.>parse(LimboAPI.class.getResourceAsStream("/mappings/particle_types_mappings.json")); + particlesMappings.forEach((modernId, versions) -> { + Particle particle = Particle.valueOf(modernId.substring(10/*"minecraft:".length()*/).toUpperCase(Locale.US)); + versions.forEach((version, id) -> SimpleParticlesManager.PROTOCOL_2_PARTICLE.computeIfAbsent( + ProtocolVersion.getProtocolVersion(Integer.parseInt(version)), + key -> new Int2ReferenceOpenHashMap<>(versions.size()) + ).put(id.intValue(), particle)); + }); + SimpleParticlesManager.PROTOCOL_2_PARTICLE.values().forEach(Int2ReferenceOpenHashMap::trim); + } + + public static Particle fromProtocolId(ProtocolVersion version, int id) { + return SimpleParticlesManager.PROTOCOL_2_PARTICLE.get(version).get(id); + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/world/SimpleTagManager.java b/plugin/src/main/java/net/elytrium/limboapi/server/world/SimpleTagManager.java index ca030340..51f8a100 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/server/world/SimpleTagManager.java +++ b/plugin/src/main/java/net/elytrium/limboapi/server/world/SimpleTagManager.java @@ -17,87 +17,69 @@ package net.elytrium.limboapi.server.world; -import com.google.gson.Gson; import com.google.gson.internal.LinkedTreeMap; import com.velocitypowered.api.network.ProtocolVersion; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import java.util.Collections; import java.util.EnumMap; -import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; import net.elytrium.limboapi.LimboAPI; -import net.elytrium.limboapi.api.material.WorldVersion; +import net.elytrium.limboapi.api.world.WorldVersion; import net.elytrium.limboapi.protocol.packets.s2c.UpdateTagsPacket; +import net.elytrium.limboapi.utils.JsonUtil; public class SimpleTagManager { - private static final Map FLUIDS = new HashMap<>(); - private static final Map VERSION_MAP = new EnumMap<>(WorldVersion.class); + private static final Object2IntOpenHashMap FLUIDS; + private static final Map VERSION_MAP; - @SuppressWarnings("unchecked") - public static void init() { - Gson gson = new Gson(); - LinkedTreeMap fluids = gson.fromJson( - new InputStreamReader( - Objects.requireNonNull(LimboAPI.class.getResourceAsStream("/mapping/fluids.json")), - StandardCharsets.UTF_8 - ), - LinkedTreeMap.class - ); - - fluids.forEach((id, protocolId) -> FLUIDS.put(id, Integer.valueOf(protocolId))); - - LinkedTreeMap>> tags = gson.fromJson( - new InputStreamReader( - Objects.requireNonNull(LimboAPI.class.getResourceAsStream("/mapping/tags.json")), - StandardCharsets.UTF_8 - ), - LinkedTreeMap.class - ); + static { + var fluids = JsonUtil.parse(LimboAPI.class.getResourceAsStream("/mappings/fluids.json")); + FLUIDS = new Object2IntOpenHashMap<>(fluids.size()); + fluids.forEach((id, protocolId) -> SimpleTagManager.FLUIDS.put(id, protocolId.intValue())); + var tags = JsonUtil.>>parse(LimboAPI.class.getResourceAsStream("/mappings/tags.json")); + VERSION_MAP = new EnumMap<>(WorldVersion.class); for (WorldVersion version : WorldVersion.values()) { - VERSION_MAP.put(version, localGetTagsForVersion(tags, version)); + SimpleTagManager.VERSION_MAP.put(version, SimpleTagManager.createPacket(tags, version)); } } - public static UpdateTagsPacket getUpdateTagsPacket(ProtocolVersion version) { - return VERSION_MAP.get(WorldVersion.from(version)); + public static UpdateTagsPacket getUpdateTagsPacket(WorldVersion version) { + return SimpleTagManager.VERSION_MAP.get(version); } - public static UpdateTagsPacket getUpdateTagsPacket(WorldVersion version) { - return VERSION_MAP.get(version); + public static UpdateTagsPacket getUpdateTagsPacket(ProtocolVersion version) { + return SimpleTagManager.VERSION_MAP.get(WorldVersion.from(version)); } - private static UpdateTagsPacket localGetTagsForVersion(LinkedTreeMap>> defaultTags, - WorldVersion version) { - Map>> tags = new LinkedTreeMap<>(); + private static UpdateTagsPacket createPacket(LinkedTreeMap>> defaultTags, WorldVersion version) { + Object2ObjectOpenHashMap> tags = new Object2ObjectOpenHashMap<>(); defaultTags.forEach((tagType, defaultTagList) -> { - LinkedTreeMap> tagList = new LinkedTreeMap<>(); + Object2ObjectOpenHashMap tagList = new Object2ObjectOpenHashMap<>(); switch (tagType) { case "minecraft:block": { - defaultTagList.forEach((tagName, blockList) -> - tagList.put(tagName, blockList.stream() - .map(e -> SimpleBlock.fromModernID(e, Map.of())) - .filter(e -> e.isSupportedOn(version)) - .map(e -> (int) e.getBlockID(version)) - .collect(Collectors.toList()))); + defaultTagList.forEach((tagName, blockList) -> tagList.put(tagName, blockList.stream() + .map(modernId -> SimpleBlock.fromModernId(modernId, Collections.emptyMap())) + .filter(block -> block.isSupportedOn(version)) + .mapToInt(block -> block.blockId(version)) + .toArray() + )); break; } case "minecraft:fluid": { - defaultTagList.forEach((tagName, fluidList) -> - tagList.put(tagName, fluidList.stream().map(FLUIDS::get).collect(Collectors.toList()))); + defaultTagList.forEach((tagName, fluidList) -> tagList.put(tagName, fluidList.stream().mapToInt(SimpleTagManager.FLUIDS::getInt).toArray())); break; } case "minecraft:item": { - defaultTagList.forEach((tagName, itemList) -> - tagList.put(tagName, itemList.stream() - .map(SimpleItem::fromModernID) - .filter(item -> item.isSupportedOn(version)) - .map(item -> (int) item.getID(version)) - .collect(Collectors.toList()))); + defaultTagList.forEach((tagName, itemList) -> tagList.put(tagName, itemList.stream() + .map(SimpleItem::fromModernId) + .filter(item -> item.isSupportedOn(version)) + .mapToInt(item -> item.itemId(version)) + .toArray() + )); break; } default: { @@ -106,7 +88,7 @@ private static UpdateTagsPacket localGetTagsForVersion(LinkedTreeMap chunk.getBiome(posX, posY, posZ), () -> Biome.PLAINS); @@ -108,14 +107,14 @@ public void setBlockLight(int posX, int posY, int posZ, byte light) { } @Override - public void fillBlockLight(int level) { + public void fillBlockLight(byte level) { for (SimpleChunk chunk : this.chunks.values()) { chunk.fillBlockLight(level); } } @Override - public void fillSkyLight(int level) { + public void fillSkyLight(byte level) { for (SimpleChunk chunk : this.chunks.values()) { chunk.fillSkyLight(level); } @@ -123,7 +122,7 @@ public void fillSkyLight(int level) { @Override public List getChunks() { - return ImmutableList.copyOf(this.chunks.values()); + return List.copyOf(this.chunks.values()); } @Override @@ -145,13 +144,14 @@ public SimpleChunk getChunk(int posX, int posZ) { return this.chunks.get(getChunkIndex(getChunkXZ(posX), getChunkXZ(posZ))); } + @NotNull @Override public SimpleChunk getChunkOrNew(int posX, int posZ) { posX = getChunkXZ(posX); posZ = getChunkXZ(posZ); - // Modern Sodium versions don't load chunks if their "neighbours" are unloaded. - // We are fixing this problem there by generating all the "neighbours". + // Modern Sodium versions don't load chunks if their "neighbors" are unloaded + // We are fixing this problem there by generating all the "neighbors" for (int chunkX = posX - 1; chunkX <= posX + 1; ++chunkX) { for (int chunkZ = posZ - 1; chunkZ <= posZ + 1; ++chunkZ) { this.localCreateChunk(chunkX, chunkZ); @@ -169,7 +169,7 @@ private void localCreateChunk(int posX, int posZ) { this.chunks.put(index, chunk); int distance = this.getDistanceToSpawn(chunk); - for (int i = this.distanceChunkMap.size(); i <= distance; i++) { + for (int i = this.distanceChunkMap.size(); i <= distance; ++i) { this.distanceChunkMap.add(new LinkedList<>()); } @@ -210,11 +210,7 @@ public float getPitch() { private T chunkAction(int posX, int posZ, Function function, Supplier ifNull) { SimpleChunk chunk = this.getChunk(posX, posZ); - if (chunk == null) { - return ifNull.get(); - } - - return function.apply(chunk); + return chunk == null ? ifNull.get() : function.apply(chunk); } private static long getChunkIndex(int posX, int posZ) { @@ -226,6 +222,6 @@ private static int getChunkXZ(int pos) { } private static int getChunkCoordinate(int pos) { - return pos & 15; + return pos & 0x0F; } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/world/chunk/SimpleChunk.java b/plugin/src/main/java/net/elytrium/limboapi/server/world/chunk/SimpleChunk.java index 1ff0aaf8..983171d7 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/server/world/chunk/SimpleChunk.java +++ b/plugin/src/main/java/net/elytrium/limboapi/server/world/chunk/SimpleChunk.java @@ -20,13 +20,14 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import net.elytrium.limboapi.api.chunk.VirtualBiome; -import net.elytrium.limboapi.api.chunk.VirtualBlock; -import net.elytrium.limboapi.api.chunk.VirtualBlockEntity; -import net.elytrium.limboapi.api.chunk.VirtualChunk; -import net.elytrium.limboapi.api.chunk.data.ChunkSnapshot; -import net.elytrium.limboapi.api.chunk.data.LightSection; -import net.elytrium.limboapi.material.Biome; +import net.elytrium.limboapi.api.world.chunk.biome.VirtualBiome; +import net.elytrium.limboapi.api.world.chunk.block.VirtualBlock; +import net.elytrium.limboapi.api.world.chunk.blockentity.VirtualBlockEntity; +import net.elytrium.limboapi.api.world.chunk.VirtualChunk; +import net.elytrium.limboapi.api.world.chunk.data.BlockSection; +import net.elytrium.limboapi.api.world.chunk.ChunkSnapshot; +import net.elytrium.limboapi.api.world.chunk.data.LightSection; +import net.elytrium.limboapi.server.world.Biome; import net.elytrium.limboapi.server.world.SimpleBlock; import net.kyori.adventure.nbt.CompoundBinaryTag; import org.checkerframework.checker.nullness.qual.NonNull; @@ -43,7 +44,7 @@ public class SimpleChunk implements VirtualChunk { private final SimpleSection[] sections = new SimpleSection[16]; private final LightSection[] light = new LightSection[18]; - private final VirtualBiome[] biomes = new VirtualBiome[1024]; + private final VirtualBiome[] biomes = new VirtualBiome[16 * SimpleChunk.MAX_BIOMES_PER_SECTION]; private final List blockEntityEntries = new ArrayList<>(); public SimpleChunk(int posX, int posZ) { @@ -62,22 +63,21 @@ public SimpleChunk(int posX, int posZ, VirtualBiome defaultBiome) { } @Override - public void setBlock(int posX, int posY, int posZ, @Nullable VirtualBlock block) { - this.getSection(posY).setBlockAt(posX, posY & 15, posZ, block); + public void setBlock(@IntRange(from = 0, to = 15) int posX, int posY, @IntRange(from = 0, to = 15) int posZ, @Nullable VirtualBlock block) { + this.getSection(posY).setBlockAt(posX, posY & 0x0F, posZ, block); } @Override public void setBlockEntity(int posX, int posY, int posZ, @Nullable CompoundBinaryTag nbt, @Nullable VirtualBlockEntity blockEntity) { - if (blockEntity == null) { - this.blockEntityEntries.removeIf(entry -> entry.getPosX() == posX && entry.getPosY() == posY && entry.getPosZ() == posZ); - return; + this.blockEntityEntries.removeIf(entry -> entry.getPosX() == posX && entry.getPosY() == posY && entry.getPosZ() == posZ); + if (blockEntity != null) { + this.blockEntityEntries.add(blockEntity.createEntry(this, posX, posY, posZ, nbt)); } - - this.blockEntityEntries.add(blockEntity.getEntry(posX, posY, posZ, nbt)); } @Override public void setBlockEntity(VirtualBlockEntity.Entry blockEntityEntry) { + this.blockEntityEntries.removeIf(entry -> entry.getPosX() == blockEntityEntry.getPosX() && entry.getPosY() == blockEntityEntry.getPosY() && entry.getPosZ() == blockEntityEntry.getPosZ()); this.blockEntityEntries.add(blockEntityEntry); } @@ -85,8 +85,7 @@ private SimpleSection getSection(int posY) { int sectionIndex = getSectionIndex(posY); SimpleSection section = this.sections[sectionIndex]; if (section == null) { - section = new SimpleSection(); - this.sections[sectionIndex] = section; + this.sections[sectionIndex] = section = new SimpleSection(); } return section; @@ -94,51 +93,47 @@ private SimpleSection getSection(int posY) { @NonNull @Override - public VirtualBlock getBlock(int posX, int posY, int posZ) { + public VirtualBlock getBlock(@IntRange(from = 0, to = 15) int posX, int posY, @IntRange(from = 0, to = 15) int posZ) { SimpleSection section = this.sections[getSectionIndex(posY)]; - if (section == null) { - return SimpleBlock.AIR; - } else { - return section.getBlockAt(posX, posY & 15, posZ); - } + return section == null ? SimpleBlock.AIR : section.getBlockAt(posX, posY & 0x0F, posZ); } @Override - public void setBiome2D(int posX, int posZ, @NonNull VirtualBiome biome) { - for (int posY = 0; posY < 256; posY += 4) { + public void setBiome2D(@IntRange(from = 0, to = 15) int posX, @IntRange(from = 0, to = 15) int posZ, @NonNull VirtualBiome biome) { + for (int posY = 0; posY < 256; posY += 4) { // TODO check 1.17+ this.setBiome3D(posX, posY, posZ, biome); } } @Override - public void setBiome3D(int posX, int posY, int posZ, @NonNull VirtualBiome biome) { + public void setBiome3D(@IntRange(from = 0, to = 15) int posX, int posY, @IntRange(from = 0, to = 15) int posZ, @NonNull VirtualBiome biome) { this.biomes[getBiomeIndex(posX, posY, posZ)] = biome; } @NonNull @Override - public VirtualBiome getBiome(int posX, int posY, int posZ) { + public VirtualBiome getBiome(@IntRange(from = 0, to = 15) int posX, int posY, @IntRange(from = 0, to = 15) int posZ) { return this.biomes[getBiomeIndex(posX, posY, posZ)]; } @Override - public void setBlockLight(int posX, int posY, int posZ, byte light) { - this.getLightSection(posY).setBlockLight(posX, posY & 15, posZ, light); + public void setBlockLight(@IntRange(from = 0, to = 15) int posX, int posY, @IntRange(from = 0, to = 15) int posZ, @IntRange(from = 0, to = 15) byte light) { + this.getLightSection(posY).setBlockLight(posX, posY & 0x0F, posZ, light); } @Override - public byte getBlockLight(int posX, int posY, int posZ) { - return this.getLightSection(posY).getBlockLight(posX, posY & 15, posZ); + public byte getBlockLight(@IntRange(from = 0, to = 15) int posX, int posY, @IntRange(from = 0, to = 15) int posZ) { + return this.getLightSection(posY).getBlockLight(posX, posY & 0x0F, posZ); } @Override - public void setSkyLight(int posX, int posY, int posZ, byte light) { - this.getLightSection(posY).setSkyLight(posX, posY & 15, posZ, light); + public void setSkyLight(@IntRange(from = 0, to = 15) int posX, int posY, @IntRange(from = 0, to = 15) int posZ, @IntRange(from = 0, to = 15) byte light) { + this.getLightSection(posY).setSkyLight(posX, posY & 0x0F, posZ, light); } @Override - public byte getSkyLight(int posX, int posY, int posZ) { - return this.getLightSection(posY).getSkyLight(posX, posY & 15, posZ); + public byte getSkyLight(@IntRange(from = 0, to = 15) int posX, int posY, @IntRange(from = 0, to = 15) int posZ) { + return this.getLightSection(posY).getSkyLight(posX, posY & 0x0F, posZ); } private LightSection getLightSection(int posY) { @@ -146,14 +141,14 @@ private LightSection getLightSection(int posY) { } @Override - public void fillBlockLight(@IntRange(from = 0, to = 15) int level) { + public void fillBlockLight(@IntRange(from = 0, to = 15) byte level) { for (LightSection lightSection : this.light) { lightSection.getBlockLight().fill(level); } } @Override - public void fillSkyLight(@IntRange(from = 0, to = 15) int level) { + public void fillSkyLight(@IntRange(from = 0, to = 15) byte level) { for (LightSection lightSection : this.light) { lightSection.getSkyLight().fill(level); } @@ -170,36 +165,35 @@ public int getPosZ() { } @Override - public ChunkSnapshot getFullChunkSnapshot() { - return this.createSnapshot(true, 0); + public ChunkSnapshot createSnapshot(boolean full) { + return new SimpleChunkSnapshot(this.posX, this.posZ, full, this.createBlockSectionSnapshot(), this.createLightSnapshot(), this.biomes.clone(), this.blockEntityEntries.toArray(VirtualBlockEntity.Entry[]::new)); } @Override - public ChunkSnapshot getPartialChunkSnapshot(long previousUpdate) { - return this.createSnapshot(false, previousUpdate); - } - - private ChunkSnapshot createSnapshot(boolean full, long previousUpdate) { - SimpleSection[] sectionsSnapshot = new SimpleSection[this.sections.length]; - for (int i = 0; i < this.sections.length; ++i) { - if (this.sections[i] != null && this.sections[i].getLastUpdate() > previousUpdate) { - sectionsSnapshot[i] = this.sections[i].getSnapshot(); + public BlockSection[] createBlockSectionSnapshot() { + SimpleSection[] snapshot = new SimpleSection[this.sections.length]; + for (int i = 0; i < snapshot.length; ++i) { + SimpleSection section = this.sections[i]; + if (section != null) { + snapshot[i] = section.copy(); } } - LightSection[] lightSnapshot = new LightSection[this.light.length]; - for (int i = 0; i < lightSnapshot.length; ++i) { - if (this.light[i].getLastUpdate() > previousUpdate) { - lightSnapshot[i] = this.light[i].copy(); - } + return snapshot; + } + + @Override + public LightSection[] createLightSnapshot() { + LightSection[] snapshot = new LightSection[this.light.length]; + for (int i = 0; i < snapshot.length; ++i) { + snapshot[i] = this.light[i].copy(); } - return new SimpleChunkSnapshot(this.posX, this.posZ, full, sectionsSnapshot, lightSnapshot, - Arrays.copyOf(this.biomes, this.biomes.length), List.copyOf(this.blockEntityEntries)); + return snapshot; } private static int getBiomeIndex(int posX, int posY, int posZ) { - return (posY >> 2 & 63) << 4 | (posZ >> 2 & 3) << 2 | posX >> 2 & 3; + return (posY >> 2 & 0b0000111111) << 4 | (posZ >> 2 & 0b11) << 2 | posX >> 2 & 0b11; } private static int getSectionIndex(int posY) { diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/world/chunk/SimpleChunkSnapshot.java b/plugin/src/main/java/net/elytrium/limboapi/server/world/chunk/SimpleChunkSnapshot.java index e7f8c6b3..06398bb6 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/server/world/chunk/SimpleChunkSnapshot.java +++ b/plugin/src/main/java/net/elytrium/limboapi/server/world/chunk/SimpleChunkSnapshot.java @@ -17,73 +17,20 @@ package net.elytrium.limboapi.server.world.chunk; -import java.util.List; -import net.elytrium.limboapi.api.chunk.VirtualBiome; -import net.elytrium.limboapi.api.chunk.VirtualBlock; -import net.elytrium.limboapi.api.chunk.VirtualBlockEntity; -import net.elytrium.limboapi.api.chunk.data.ChunkSnapshot; -import net.elytrium.limboapi.api.chunk.data.LightSection; +import net.elytrium.limboapi.api.world.chunk.biome.VirtualBiome; +import net.elytrium.limboapi.api.world.chunk.block.VirtualBlock; +import net.elytrium.limboapi.api.world.chunk.blockentity.VirtualBlockEntity; +import net.elytrium.limboapi.api.world.chunk.data.BlockSection; +import net.elytrium.limboapi.api.world.chunk.ChunkSnapshot; +import net.elytrium.limboapi.api.world.chunk.data.LightSection; import net.elytrium.limboapi.server.world.SimpleBlock; -public class SimpleChunkSnapshot implements ChunkSnapshot { - - private final int posX; - private final int posZ; - private final boolean fullChunk; - private final SimpleSection[] sections; - private final LightSection[] light; - private final VirtualBiome[] biomes; - private final List blockEntityEntries; - - public SimpleChunkSnapshot(int posX, int posZ, boolean fullChunk, SimpleSection[] sections, LightSection[] light, - VirtualBiome[] biomes, List blockEntityEntries) { - this.posX = posX; - this.posZ = posZ; - this.fullChunk = fullChunk; - this.sections = sections; - this.light = light; - this.biomes = biomes; - this.blockEntityEntries = blockEntityEntries; - } +public record SimpleChunkSnapshot(int posX, int posZ, boolean fullChunk, + BlockSection[] sections, LightSection[] light, VirtualBiome[] biomes, VirtualBlockEntity.Entry[] blockEntityEntries) implements ChunkSnapshot { @Override public VirtualBlock getBlock(int posX, int posY, int posZ) { - SimpleSection section = this.sections[posY >> 4]; - return section == null ? SimpleBlock.AIR : section.getBlockAt(posX, posY & 15, posZ); - } - - @Override - public int getPosX() { - return this.posX; - } - - @Override - public int getPosZ() { - return this.posZ; - } - - @Override - public boolean isFullChunk() { - return this.fullChunk; - } - - @Override - public SimpleSection[] getSections() { - return this.sections; - } - - @Override - public LightSection[] getLight() { - return this.light; - } - - @Override - public VirtualBiome[] getBiomes() { - return this.biomes; - } - - @Override - public List getBlockEntityEntries() { - return this.blockEntityEntries; + BlockSection section = this.sections[posY >> 4]; + return section == null ? SimpleBlock.AIR : section.getBlockAt(posX & 0x0F, posY & 0x0F, posZ & 0x0F); } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/world/chunk/SimpleLightSection.java b/plugin/src/main/java/net/elytrium/limboapi/server/world/chunk/SimpleLightSection.java index 4f8a2672..6bf08657 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/server/world/chunk/SimpleLightSection.java +++ b/plugin/src/main/java/net/elytrium/limboapi/server/world/chunk/SimpleLightSection.java @@ -18,26 +18,24 @@ package net.elytrium.limboapi.server.world.chunk; import com.google.common.base.Preconditions; -import net.elytrium.limboapi.api.chunk.data.LightSection; -import net.elytrium.limboapi.api.mcprotocollib.NibbleArray3D; +import net.elytrium.limboapi.api.world.chunk.data.LightSection; +import net.elytrium.limboapi.api.world.chunk.util.NibbleArray3D; public class SimpleLightSection implements LightSection { private static final NibbleArray3D NO_LIGHT = new NibbleArray3D(SimpleChunk.MAX_BLOCKS_PER_SECTION); - private static final NibbleArray3D ALL_LIGHT = new NibbleArray3D(SimpleChunk.MAX_BLOCKS_PER_SECTION, 15); + private static final NibbleArray3D ALL_LIGHT = new NibbleArray3D(SimpleChunk.MAX_BLOCKS_PER_SECTION, (byte) 15); private NibbleArray3D blockLight; private NibbleArray3D skyLight; - private long lastUpdate; public SimpleLightSection() { - this(NO_LIGHT, ALL_LIGHT, System.nanoTime()); + this(NO_LIGHT, ALL_LIGHT); } - private SimpleLightSection(NibbleArray3D blockLight, NibbleArray3D skyLight, long lastUpdate) { + private SimpleLightSection(NibbleArray3D blockLight, NibbleArray3D skyLight) { this.blockLight = blockLight; this.skyLight = skyLight; - this.lastUpdate = lastUpdate; } @Override @@ -50,7 +48,6 @@ public void setBlockLight(int posX, int posY, int posZ, byte light) { } this.blockLight.set(posX, posY, posZ, light); - this.lastUpdate = System.nanoTime(); } @Override @@ -74,7 +71,6 @@ public void setSkyLight(int posX, int posY, int posZ, byte light) { } this.skyLight.set(posX, posY, posZ, light); - this.lastUpdate = System.nanoTime(); } @Override @@ -98,15 +94,10 @@ private boolean checkIndex(int pos) { return pos >= 0 && pos <= 15; } - @Override - public long getLastUpdate() { - return this.lastUpdate; - } - @Override public SimpleLightSection copy() { NibbleArray3D skyLight = this.skyLight == ALL_LIGHT ? ALL_LIGHT : this.skyLight.copy(); NibbleArray3D blockLight = this.blockLight == NO_LIGHT ? NO_LIGHT : this.blockLight.copy(); - return new SimpleLightSection(blockLight, skyLight, this.lastUpdate); + return new SimpleLightSection(blockLight, skyLight); } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/world/chunk/SimpleSection.java b/plugin/src/main/java/net/elytrium/limboapi/server/world/chunk/SimpleSection.java index 7bd3d618..05593ada 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/server/world/chunk/SimpleSection.java +++ b/plugin/src/main/java/net/elytrium/limboapi/server/world/chunk/SimpleSection.java @@ -19,9 +19,9 @@ import com.google.common.base.Preconditions; import com.velocitypowered.api.network.ProtocolVersion; -import net.elytrium.limboapi.api.chunk.VirtualBlock; -import net.elytrium.limboapi.api.chunk.data.BlockSection; -import net.elytrium.limboapi.api.chunk.data.BlockStorage; +import net.elytrium.limboapi.api.world.chunk.block.VirtualBlock; +import net.elytrium.limboapi.api.world.chunk.data.BlockSection; +import net.elytrium.limboapi.api.world.chunk.data.BlockStorage; import net.elytrium.limboapi.protocol.data.BlockStorage19; import net.elytrium.limboapi.server.world.SimpleBlock; import org.checkerframework.checker.nullness.qual.Nullable; @@ -30,8 +30,6 @@ public class SimpleSection implements BlockSection { private final BlockStorage blocks; - private long lastUpdate = System.nanoTime(); - public SimpleSection() { this(new BlockStorage19(ProtocolVersion.MINECRAFT_1_17)); } @@ -40,41 +38,30 @@ public SimpleSection(BlockStorage blocks) { this.blocks = blocks; } - public SimpleSection(BlockStorage blocks, long lastUpdate) { - this.blocks = blocks; - this.lastUpdate = lastUpdate; - } - @Override public void setBlockAt(int posX, int posY, int posZ, @Nullable VirtualBlock block) { - this.checkIndexes(posX, posY, posZ); + SimpleSection.checkIndexes(posX, posY, posZ); this.blocks.set(posX, posY, posZ, block == null ? SimpleBlock.AIR : block); - this.lastUpdate = System.nanoTime(); } @Override public VirtualBlock getBlockAt(int posX, int posY, int posZ) { - this.checkIndexes(posX, posY, posZ); + SimpleSection.checkIndexes(posX, posY, posZ); return this.blocks.get(posX, posY, posZ); } - private void checkIndexes(int posX, int posY, int posZ) { - Preconditions.checkArgument(this.checkIndex(posX), "x should be between 0 and 15"); - Preconditions.checkArgument(this.checkIndex(posY), "y should be between 0 and 15"); - Preconditions.checkArgument(this.checkIndex(posZ), "z should be between 0 and 15"); - } - - private boolean checkIndex(int pos) { - return pos >= 0 && pos <= 15; + @Override + public SimpleSection copy() { + return new SimpleSection(this.blocks.copy()); } - @Override - public SimpleSection getSnapshot() { - return new SimpleSection(this.blocks.copy(), this.lastUpdate); + private static void checkIndexes(int posX, int posY, int posZ) { + Preconditions.checkArgument(SimpleSection.checkIndex(posX), "x should be between 0 and 15"); + Preconditions.checkArgument(SimpleSection.checkIndex(posY), "y should be between 0 and 15"); + Preconditions.checkArgument(SimpleSection.checkIndex(posZ), "z should be between 0 and 15"); } - @Override - public long getLastUpdate() { - return this.lastUpdate; + private static boolean checkIndex(int pos) { + return pos >= 0 && pos <= 15; } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/utils/Hashing.java b/plugin/src/main/java/net/elytrium/limboapi/utils/Hashing.java new file mode 100644 index 00000000..906e5c7b --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/utils/Hashing.java @@ -0,0 +1,33 @@ +package net.elytrium.limboapi.utils; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class Hashing { + + private static ThreadLocal SHA1; + + public static byte[] sha1(String input) { + return Hashing.sha1(input.getBytes(StandardCharsets.UTF_8)); + } + + public static byte[] sha1(byte[] input) { + if (Hashing.SHA1 == null) { + Hashing.SHA1 = Hashing.messageDigest("SHA-1"); + } + + return Hashing.SHA1.get().digest(input); + } + + @SuppressWarnings("SameParameterValue") + private static ThreadLocal messageDigest(String algorithm) { + return ThreadLocal.withInitial(() -> { + try { + return MessageDigest.getInstance(algorithm); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + }); + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/utils/Hex.java b/plugin/src/main/java/net/elytrium/limboapi/utils/Hex.java new file mode 100644 index 00000000..ef6f827e --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/utils/Hex.java @@ -0,0 +1,35 @@ +package net.elytrium.limboapi.utils; + +public class Hex { + + public static final byte[] BYTE_TABLE = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' + }; + public static final char[] CHAR_TABLE = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' + }; + + public static byte[] encodeBytes(byte[] data) { + byte[] result = new byte[data.length << 1]; + for (int from = 0, to = 0; from < data.length; ++from) { + result[to++] = Hex.BYTE_TABLE[(data[from] & 0xF0) >>> 4]; + result[to++] = Hex.BYTE_TABLE[data[from] & 0x0F]; + } + + return result; + } + + public static char[] encodeChars(byte[] data) { + char[] result = new char[data.length << 1]; + for (int from = 0, to = 0; from < data.length; ++from) { + result[to++] = Hex.CHAR_TABLE[(data[from] & 0xF0) >>> 4]; + result[to++] = Hex.CHAR_TABLE[data[from] & 0x0F]; + } + + return result; + } + + public static String encodeString(byte[] data) { + return new String(Hex.encodeChars(data)); + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/utils/JsonUtil.java b/plugin/src/main/java/net/elytrium/limboapi/utils/JsonUtil.java new file mode 100644 index 00000000..f8db1e8c --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/utils/JsonUtil.java @@ -0,0 +1,24 @@ +package net.elytrium.limboapi.utils; + +import com.google.gson.Gson; +import com.google.gson.internal.LinkedTreeMap; +import com.google.gson.reflect.TypeToken; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; + +public class JsonUtil { + + private static final Gson GSON = new Gson(); + private static final TypeToken TOKEN = TypeToken.get(LinkedTreeMap.class); + + @SuppressWarnings("unchecked") + public static LinkedTreeMap parse(InputStream inputStream) { + try (InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) { + return (LinkedTreeMap) JsonUtil.GSON.fromJson(reader, JsonUtil.TOKEN); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/utils/LambdaUtil.java b/plugin/src/main/java/net/elytrium/limboapi/utils/LambdaUtil.java deleted file mode 100644 index a4d8d42c..00000000 --- a/plugin/src/main/java/net/elytrium/limboapi/utils/LambdaUtil.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package net.elytrium.limboapi.utils; - -import java.lang.invoke.LambdaMetafactory; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; -import java.lang.reflect.Field; -import java.util.function.BiConsumer; -import java.util.function.Function; - -public final class LambdaUtil { - private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); - - public static Function getterOf(Field field) throws Throwable { - MethodHandle handle = LOOKUP.unreflectGetter(field); - MethodType type = handle.type(); - //noinspection unchecked - return (Function) LambdaMetafactory.metafactory( - LOOKUP, - "apply", - MethodType.methodType(Function.class, MethodHandle.class), - type.generic(), - MethodHandles.exactInvoker(type), - type - ).getTarget().invokeExact(handle); - } - - public static BiConsumer setterOf(Field f) throws Throwable { - MethodHandle handle = LOOKUP.unreflectSetter(f); - MethodType type = handle.type(); - //noinspection unchecked - return (BiConsumer) LambdaMetafactory.metafactory( - LOOKUP, - "accept", - MethodType.methodType(BiConsumer.class, MethodHandle.class), - type.generic().changeReturnType(void.class), - MethodHandles.exactInvoker(type), - type - ).getTarget().invokeExact(handle); - } -} diff --git a/plugin/src/main/java/net/elytrium/limboapi/utils/LibLoader.java b/plugin/src/main/java/net/elytrium/limboapi/utils/LibLoader.java new file mode 100644 index 00000000..a37a7cc5 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/utils/LibLoader.java @@ -0,0 +1,117 @@ +package net.elytrium.limboapi.utils; + +import com.velocitypowered.api.proxy.ProxyServer; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.Arrays; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class LibLoader { + + private static final Logger LOGGER = LoggerFactory.getLogger(LibLoader.class); + + private static final String REPOSITORY = "https://repo.maven.apache.org/maven2/"; + + private static final long TWO_WEEKS_MILLIS = 2/*weeks*/ * 7/*days*/ * 24/*hours*/ * 60/*minutes*/ * 60/*seconds*/ * 1000/*millis*/; + private static final int SHA1_HEX_LENGTH = 20 << 1; + + private static final int MAX_ATTEMPTS = 3; + + private static boolean fastutil_loaded; + + public static void resolveAndLoad(Object plugin, ProxyServer server, String[] indexes) { // TODO versioning + try { + final Path librariesDirectory = Path.of("libraries"); + for (final String library : indexes) { + //final String library = BuildConfig.LIBRARIES[index]; + //// Make sure this library won't be loaded again + //if (library == null) { + // continue; + //} + //BuildConfig.LIBRARIES[index] = null; + + final Path jarPath = librariesDirectory.resolve(library); + final Path sha1Path = librariesDirectory.resolve(library + ".sha1"); + boolean notExists = Files.notExists(jarPath); + if (notExists || Files.notExists(sha1Path) + || System.currentTimeMillis() - sha1Path.toFile().lastModified() >= LibLoader.TWO_WEEKS_MILLIS + || LibLoader.notMatches(Files.readAllBytes(sha1Path), jarPath)) { + final String jarUrl = LibLoader.REPOSITORY + library; + final String sha1Url = LibLoader.REPOSITORY + library + ".sha1"; + + Files.createDirectories(jarPath.getParent()); + + LibLoader.LOGGER.info("Fetching {}", sha1Url); + byte[] expectedHash; + try (InputStream inputStream = new URI(sha1Url).toURL().openStream()) { + expectedHash = inputStream.readNBytes(LibLoader.SHA1_HEX_LENGTH); + Files.deleteIfExists(sha1Path); + Files.write(sha1Path, expectedHash); + } catch (Throwable t) { + if (t instanceof FileNotFoundException) { + LibLoader.LOGGER.warn("Couldn't fetch file from {}, no repositories left, shutting down the server", LibLoader.REPOSITORY); + server.shutdown(); + return; + } else { + LibLoader.LOGGER.warn("Unable to fetch {}", sha1Url); + expectedHash = null; + } + } + + if (notExists || LibLoader.notMatches(expectedHash, jarPath)) { + LibLoader.LOGGER.info("Downloading {}", jarUrl); + int attempt = 0; + do { + if (attempt == LibLoader.MAX_ATTEMPTS) { + LibLoader.LOGGER.error("Download failed after " + LibLoader.MAX_ATTEMPTS + " times, shutting down the server"); + server.shutdown(); + return; + } else if (attempt != 0) { + LibLoader.LOGGER.info("Trying again"); + } + + try (InputStream inputStream = new URI(jarUrl).toURL().openStream()) { + Files.copy(inputStream, jarPath, StandardCopyOption.REPLACE_EXISTING); + } catch (Throwable t) { + if (t instanceof FileNotFoundException) { + LibLoader.LOGGER.warn("Couldn't find file in {}, no repositories left, shutting down the server", LibLoader.REPOSITORY); + server.shutdown(); + return; + } else { + LibLoader.LOGGER.error("Failed to download", t); + } + } + + ++attempt; + } while (LibLoader.notMatches(expectedHash, jarPath)); + } + } + + if (library.startsWith("it/unimi/dsi/fastutil")) { + // We're doing it because velocity already shades some fastutil classes, but it does so in a way that serves its own needs + if (!LibLoader.fastutil_loaded) { + ClassLoader classLoader = server.getClass().getClassLoader(); + Reflection.findVirtualVoid(classLoader.getClass(), "appendToClassPathForInstrumentation", String.class).invoke(classLoader, jarPath.toString()); + LibLoader.fastutil_loaded = true; + } + } else { + server.getPluginManager().addToClasspath(plugin, jarPath); + } + + LibLoader.LOGGER.info("Loaded library {}", jarPath.toAbsolutePath()); + } + } catch (Throwable t) { + throw new RuntimeException("An exception has occurred whilst loading libraries", t); + } + } + + private static boolean notMatches(byte[] expectedHash, Path jarPath) throws IOException { + return expectedHash != null && !Arrays.equals(expectedHash, Hex.encodeBytes(Hashing.sha1(Files.readAllBytes(jarPath)))); + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/utils/OverlayIntObjectMap.java b/plugin/src/main/java/net/elytrium/limboapi/utils/OverlayIntObjectMap.java deleted file mode 100644 index aaffcf2c..00000000 --- a/plugin/src/main/java/net/elytrium/limboapi/utils/OverlayIntObjectMap.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package net.elytrium.limboapi.utils; - -import com.google.common.collect.Iterables; -import com.google.common.collect.Streams; -import io.netty.util.collection.IntObjectMap; -import java.util.Collection; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; -import net.elytrium.limboapi.api.utils.OverlayMap; -import org.jetbrains.annotations.NotNull; - -public class OverlayIntObjectMap extends OverlayMap implements IntObjectMap { - - public OverlayIntObjectMap(Map parent, Map overlay) { - super(parent, overlay); - } - - @Override - public K get(int key) { - return super.get(key); - } - - @Override - public K put(int key, K value) { - return super.put(key, value); - } - - @Override - public K remove(int key) { - return super.remove(key); - } - - @Override - public Iterable> entries() { - return Iterables.concat(((IntObjectMap) parent).entries(), ((IntObjectMap) overlay).entries()); - } - - @Override - public boolean containsKey(int key) { - return super.containsKey(key); - } - - @Override - public Set keySet() { - return Streams.concat(this.parent.keySet().stream(), this.overlay.keySet().stream()).collect(Collectors.toSet()); - } - - @NotNull - @Override - public Collection values() { - return Streams.concat(this.parent.values().stream(), this.overlay.values().stream()).collect(Collectors.toList()); - } - - @NotNull - @Override - public Set> entrySet() { - return Streams.concat(this.parent.entrySet().stream(), this.overlay.entrySet().stream()).collect(Collectors.toSet()); - } -} diff --git a/plugin/src/main/java/net/elytrium/limboapi/utils/ProtocolTools.java b/plugin/src/main/java/net/elytrium/limboapi/utils/ProtocolTools.java deleted file mode 100644 index 149e0ece..00000000 --- a/plugin/src/main/java/net/elytrium/limboapi/utils/ProtocolTools.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package net.elytrium.limboapi.utils; - -import com.velocitypowered.api.network.ProtocolVersion; -import com.velocitypowered.proxy.protocol.ProtocolUtils; -import io.netty.buffer.ByteBuf; - -public class ProtocolTools { - - public static void writeContainerId(ByteBuf buf, ProtocolVersion version, int id) { - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) { - ProtocolUtils.writeVarInt(buf, id); - } else { - buf.writeByte(id); - } - } - - public static int readContainerId(ByteBuf buf, ProtocolVersion version) { - if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) { - return ProtocolUtils.readVarInt(buf); - } else { - return buf.readUnsignedByte(); - } - } -} diff --git a/plugin/src/main/java/net/elytrium/limboapi/utils/Reflection.java b/plugin/src/main/java/net/elytrium/limboapi/utils/Reflection.java new file mode 100644 index 00000000..8a9de4d7 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/utils/Reflection.java @@ -0,0 +1,198 @@ +package net.elytrium.limboapi.utils; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.invoke.VarHandle; +import java.lang.reflect.Field; +import net.elytrium.commons.utils.reflection.ReflectionException; +import sun.misc.Unsafe; + +public class Reflection { + + private static final MethodType VOID = MethodType.methodType(void.class); + + public static final Unsafe UNSAFE; + public static final MethodHandles.Lookup LOOKUP; + + public static MethodHandle findVirtual(Class clazz, String name, Class returnType) { + try { + return Reflection.LOOKUP.findVirtual(clazz, name, MethodType.methodType(returnType)); + } catch (NoSuchMethodException | IllegalAccessException e) { + throw new ReflectionException(e); + } + } + + public static MethodHandle findVirtual(Class clazz, String name, Class returnType, Class... parameters) { + try { + return Reflection.LOOKUP.findVirtual(clazz, name, MethodType.methodType(returnType, parameters)); + } catch (NoSuchMethodException | IllegalAccessException e) { + throw new ReflectionException(e); + } + } + + public static MethodHandle findVirtualVoid(Class clazz, String name) { + try { + return Reflection.LOOKUP.findVirtual(clazz, name, Reflection.VOID); + } catch (NoSuchMethodException | IllegalAccessException e) { + throw new ReflectionException(e); + } + } + + public static MethodHandle findVirtualVoid(Class clazz, String name, Class... parameters) { + try { + return Reflection.LOOKUP.findVirtual(clazz, name, MethodType.methodType(void.class, parameters)); + } catch (NoSuchMethodException | IllegalAccessException e) { + throw new ReflectionException(e); + } + } + + public static MethodHandle findStatic(Class clazz, String name, Class returnType) { + try { + return Reflection.LOOKUP.findStatic(clazz, name, MethodType.methodType(returnType)); + } catch (NoSuchMethodException | IllegalAccessException e) { + throw new ReflectionException(e); + } + } + + public static MethodHandle findStatic(Class clazz, String name, Class returnType, Class... parameters) { + try { + return Reflection.LOOKUP.findStatic(clazz, name, MethodType.methodType(returnType, parameters)); + } catch (NoSuchMethodException | IllegalAccessException e) { + throw new ReflectionException(e); + } + } + + public static MethodHandle findStaticVoid(Class clazz, String name) { + try { + return Reflection.LOOKUP.findStatic(clazz, name, Reflection.VOID); + } catch (NoSuchMethodException | IllegalAccessException e) { + throw new ReflectionException(e); + } + } + + public static MethodHandle findStaticVoid(Class clazz, String name, Class... parameters) { + try { + return Reflection.LOOKUP.findStatic(clazz, name, MethodType.methodType(void.class, parameters)); + } catch (NoSuchMethodException | IllegalAccessException e) { + throw new ReflectionException(e); + } + } + + public static MethodHandle findConstructor(Class clazz) { + try { + return Reflection.LOOKUP.findConstructor(clazz, Reflection.VOID); + } catch (NoSuchMethodException | IllegalAccessException e) { + throw new ReflectionException(e); + } + } + + public static MethodHandle findConstructor(Class clazz, Class... parameters) { + try { + return Reflection.LOOKUP.findConstructor(clazz, MethodType.methodType(void.class, parameters)); + } catch (NoSuchMethodException | IllegalAccessException e) { + throw new ReflectionException(e); + } + } + + public static MethodHandle findGetter(Class clazz, String name, Class type) { + try { + return Reflection.LOOKUP.findGetter(clazz, name, type); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new ReflectionException(e); + } + } + + public static MethodHandle findStaticGetter(Class clazz, String name, Class type) { + try { + return Reflection.LOOKUP.findStaticGetter(clazz, name, type); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new ReflectionException(e); + } + } + + public static MethodHandle findSetter(Class clazz, String name, Class type) { + try { + return Reflection.LOOKUP.findSetter(clazz, name, type); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new ReflectionException(e); + } + } + + public static MethodHandle findStaticSetter(Class clazz, String name, Class type) { + try { + return Reflection.LOOKUP.findStaticSetter(clazz, name, type); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new ReflectionException(e); + } + } + + public static VarHandle findVarHandle(Class clazz, String name, Class type) { + try { + return Reflection.LOOKUP.findVarHandle(clazz, name, type); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new ReflectionException(e); + } + } + + public static VarHandle findStaticVarHandle(Class clazz, String name, Class type) { + try { + return Reflection.LOOKUP.findStaticVarHandle(clazz, name, type); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new ReflectionException(e); + } + } + /* + + @SuppressWarnings("unchecked") + public static Function getterOf(Class clazz, String fieldName) { + try { + Field field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + MethodHandle handle = LOOKUP.unreflectSetter(field); + MethodType type = handle.type(); + return (Function) LambdaMetafactory.metafactory( + LOOKUP, "apply", MethodType.methodType(Function.class, MethodHandle.class), type.erase(), MethodHandles.exactInvoker(type), type + ).getTarget().invokeExact(handle); + } catch (Throwable e) { + throw new ReflectionException(e); + } + } + + @SuppressWarnings("unchecked") + public static BiConsumer setterOf(Class clazz, String fieldName) { + try { + Field field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + MethodHandle handle = LOOKUP.unreflectSetter(field); + MethodType type = handle.type(); + return (BiConsumer) LambdaMetafactory.metafactory( + LOOKUP, "accept", MethodType.methodType(BiConsumer.class, MethodHandle.class), type.erase(), MethodHandles.exactInvoker(type), type + ).getTarget().invokeExact(handle); + } catch (Throwable e) { + throw new ReflectionException(e); + } + } + */ + + public static Class findClass(String className) { + try { + return Class.forName(className); + } catch (ClassNotFoundException e) { + throw new ReflectionException(e); + } + } + + static { + try { + Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); + theUnsafe.setAccessible(true); + var unsafe = UNSAFE = (Unsafe) theUnsafe.get(null); + + Field implLookupField = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP"); + LOOKUP = (MethodHandles.Lookup) unsafe.getObject(unsafe.staticFieldBase(implLookupField), unsafe.staticFieldOffset(implLookupField)); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new ReflectionException(e); + } + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/utils/SetIsObjectSet.java b/plugin/src/main/java/net/elytrium/limboapi/utils/SetIsObjectSet.java deleted file mode 100644 index deefaca1..00000000 --- a/plugin/src/main/java/net/elytrium/limboapi/utils/SetIsObjectSet.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 2021 - 2025 Elytrium - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package net.elytrium.limboapi.utils; - -import it.unimi.dsi.fastutil.objects.ObjectIterator; -import it.unimi.dsi.fastutil.objects.ObjectSet; -import java.util.Collection; -import java.util.Iterator; -import java.util.Set; -import org.checkerframework.checker.nullness.qual.NonNull; - -public class SetIsObjectSet implements ObjectSet { - - private final Set set; - - public SetIsObjectSet(Set set) { - this.set = set; - } - - @Override - public int size() { - return this.set.size(); - } - - @Override - public boolean isEmpty() { - return this.set.isEmpty(); - } - - @Override - public boolean contains(Object o) { - return this.set.contains(o); - } - - @Override - public ObjectIterator iterator() { - return new IteratorIsObjectIterator<>(this.set.iterator()); - } - - @NonNull - @Override - public Object[] toArray() { - return this.set.toArray(); - } - - @NonNull - @Override - public T[] toArray(T @NonNull [] ts) { - return this.set.toArray(ts); - } - - @Override - public boolean add(K k) { - return this.set.add(k); - } - - @Override - public boolean remove(Object o) { - return this.set.remove(o); - } - - @Override - public boolean containsAll(@NonNull Collection collection) { - return this.set.containsAll(collection); - } - - @Override - public boolean addAll(@NonNull Collection collection) { - return this.set.addAll(collection); - } - - @Override - public boolean removeAll(@NonNull Collection collection) { - return this.set.removeAll(collection); - } - - @Override - public boolean retainAll(@NonNull Collection collection) { - return this.set.retainAll(collection); - } - - @Override - public void clear() { - this.set.clear(); - } - - private static final class IteratorIsObjectIterator implements ObjectIterator { - - private final Iterator iterator; - - private IteratorIsObjectIterator(Iterator iterator) { - this.iterator = iterator; - } - - @Override - public boolean hasNext() { - return this.iterator.hasNext(); - } - - @Override - public K next() { - return this.iterator.next(); - } - } -} diff --git a/plugin/src/main/java/net/elytrium/limboapi/utils/Unit.java b/plugin/src/main/java/net/elytrium/limboapi/utils/Unit.java new file mode 100644 index 00000000..aed2b7b5 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/utils/Unit.java @@ -0,0 +1,10 @@ +package net.elytrium.limboapi.utils; + +import net.elytrium.limboapi.protocol.codec.StreamCodec; + +public enum Unit { + + INSTANCE; + + public static final StreamCodec CODEC = StreamCodec.unit(Unit.INSTANCE); +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/utils/functions/Function10.java b/plugin/src/main/java/net/elytrium/limboapi/utils/functions/Function10.java new file mode 100644 index 00000000..368dc8e1 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/utils/functions/Function10.java @@ -0,0 +1,7 @@ +package net.elytrium.limboapi.utils.functions; + +@FunctionalInterface +public interface Function10 { + + R apply(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8, T9 t9, T10 t10); +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/utils/functions/Function11.java b/plugin/src/main/java/net/elytrium/limboapi/utils/functions/Function11.java new file mode 100644 index 00000000..b350004c --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/utils/functions/Function11.java @@ -0,0 +1,7 @@ +package net.elytrium.limboapi.utils.functions; + +@FunctionalInterface +public interface Function11 { + + R apply(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8, T9 t9, T10 t10, T11 t11); +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/utils/functions/Function3.java b/plugin/src/main/java/net/elytrium/limboapi/utils/functions/Function3.java new file mode 100644 index 00000000..0a8954e8 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/utils/functions/Function3.java @@ -0,0 +1,7 @@ +package net.elytrium.limboapi.utils.functions; + +@FunctionalInterface +public interface Function3 { + + R apply(T1 t1, T2 t2, T3 t3); +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/utils/functions/Function4.java b/plugin/src/main/java/net/elytrium/limboapi/utils/functions/Function4.java new file mode 100644 index 00000000..224f04cc --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/utils/functions/Function4.java @@ -0,0 +1,7 @@ +package net.elytrium.limboapi.utils.functions; + +@FunctionalInterface +public interface Function4 { + + R apply(T1 t1, T2 t2, T3 t3, T4 t4); +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/utils/functions/Function5.java b/plugin/src/main/java/net/elytrium/limboapi/utils/functions/Function5.java new file mode 100644 index 00000000..e03a260a --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/utils/functions/Function5.java @@ -0,0 +1,7 @@ +package net.elytrium.limboapi.utils.functions; + +@FunctionalInterface +public interface Function5 { + + R apply(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5); +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/utils/functions/Function6.java b/plugin/src/main/java/net/elytrium/limboapi/utils/functions/Function6.java new file mode 100644 index 00000000..6528cd81 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/utils/functions/Function6.java @@ -0,0 +1,7 @@ +package net.elytrium.limboapi.utils.functions; + +@FunctionalInterface +public interface Function6 { + + R apply(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6); +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/utils/functions/Function7.java b/plugin/src/main/java/net/elytrium/limboapi/utils/functions/Function7.java new file mode 100644 index 00000000..6d832f7b --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/utils/functions/Function7.java @@ -0,0 +1,7 @@ +package net.elytrium.limboapi.utils.functions; + +@FunctionalInterface +public interface Function7 { + + R apply(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7); +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/utils/functions/Function8.java b/plugin/src/main/java/net/elytrium/limboapi/utils/functions/Function8.java new file mode 100644 index 00000000..28baeed6 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/utils/functions/Function8.java @@ -0,0 +1,7 @@ +package net.elytrium.limboapi.utils.functions; + +@FunctionalInterface +public interface Function8 { + + R apply(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8); +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/utils/functions/Function9.java b/plugin/src/main/java/net/elytrium/limboapi/utils/functions/Function9.java new file mode 100644 index 00000000..9e75a802 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/utils/functions/Function9.java @@ -0,0 +1,7 @@ +package net.elytrium.limboapi.utils.functions; + +@FunctionalInterface +public interface Function9 { + + R apply(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8, T9 t9); +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/utils/overlay/OverlayIntObjectMap.java b/plugin/src/main/java/net/elytrium/limboapi/utils/overlay/OverlayIntObjectMap.java new file mode 100644 index 00000000..e1af5db7 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/utils/overlay/OverlayIntObjectMap.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2021 - 2024 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limboapi.utils.overlay; + +import com.google.common.collect.Iterables; +import io.netty.util.collection.IntObjectMap; +import java.util.Map; + +public class OverlayIntObjectMap extends OverlayMap implements IntObjectMap { + + public OverlayIntObjectMap(Map parent, Map overlay) { + super(parent, overlay); + } + + @Override + public V get(int key) { + V value = ((IntObjectMap) this.overlay).get(key); + if (value != null) { + return value; + } + + return this.override ? null : this.parent.get(key); + } + + @Override + public V put(int key, V value) { + return ((IntObjectMap) this.overlay).put(key, value); + } + + @Override + public V remove(int key) { + return ((IntObjectMap) this.overlay).remove(key); + } + + @Override + public Iterable> entries() { + if (this.override) { + return ((IntObjectMap) this.overlay).entries(); + } + + return Iterables.concat(((IntObjectMap) this.overlay).entries(), ((IntObjectMap) this.parent).entries()); + } + + @Override + public boolean containsKey(int key) { + if (this.override) { + return ((IntObjectMap) this.overlay).containsKey(key); + } + + return ((IntObjectMap) this.overlay).containsKey(key) || ((IntObjectMap) this.parent).containsKey(key); + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/utils/overlay/OverlayMap.java b/plugin/src/main/java/net/elytrium/limboapi/utils/overlay/OverlayMap.java new file mode 100644 index 00000000..cda1c075 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/utils/overlay/OverlayMap.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2021 - 2024 Elytrium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package net.elytrium.limboapi.utils.overlay; + +import com.google.common.collect.Sets; +import java.util.Collection; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public abstract class OverlayMap implements Map { + + protected final Map parent; + protected final Map overlay; + + protected boolean override = false; + + public OverlayMap(Map parent, Map overlay) { + this.parent = parent; + this.overlay = overlay; + } + + @Override + public int size() { + if (this.override) { + return this.overlay.size(); + } + + return this.overlay.size() + this.parent.size(); + } + + @Override + public boolean isEmpty() { + if (this.override) { + return this.overlay.isEmpty(); + } + + return this.overlay.isEmpty() && this.parent.isEmpty(); + } + + @Override + public boolean containsKey(Object o) { + if (this.override) { + return this.overlay.containsKey(o); + } + + return this.overlay.containsKey(o) || this.parent.containsKey(o); + } + + @Override + public boolean containsValue(Object o) { + if (this.override) { + return this.overlay.containsValue(o); + } + + return this.overlay.containsValue(o) || this.parent.containsValue(o); + } + + @Override + public V get(Object o) { + V value = this.overlay.get(o); + if (value != null) { + return value; + } + + return this.override ? null : this.parent.get(o); + } + + @Override + public V put(K k, V v) { + return this.overlay.put(k, v); + } + + @Override + public V remove(Object o) { + return this.overlay.remove(o); + } + + @Override + public void putAll(Map map) { + this.overlay.putAll(map); + } + + @Override + public void clear() { + this.overlay.clear(); + } + + @Override + public Set keySet() { + if (this.override) { + return this.overlay.keySet(); + } + + return Sets.union(this.overlay.keySet(), this.parent.keySet()); + } + + @Override + public Collection values() { + if (this.override) { + return this.overlay.values(); + } + + return Stream.concat(this.overlay.values().stream(), this.parent.values().stream()).collect(Collectors.toList()); + } + + @Override + public Set> entrySet() { + if (this.override) { + return this.overlay.entrySet(); + } + + return Sets.union(this.overlay.entrySet(), this.parent.entrySet()); + } + + public boolean isOverride() { + return this.override; + } + + public void setOverride(boolean override) { + this.override = override; + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/utils/OverlayObject2IntMap.java b/plugin/src/main/java/net/elytrium/limboapi/utils/overlay/OverlayObject2IntMap.java similarity index 53% rename from plugin/src/main/java/net/elytrium/limboapi/utils/OverlayObject2IntMap.java rename to plugin/src/main/java/net/elytrium/limboapi/utils/overlay/OverlayObject2IntMap.java index 6f0f2b42..b5913433 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/utils/OverlayObject2IntMap.java +++ b/plugin/src/main/java/net/elytrium/limboapi/utils/overlay/OverlayObject2IntMap.java @@ -15,16 +15,17 @@ * along with this program. If not, see . */ -package net.elytrium.limboapi.utils; +package net.elytrium.limboapi.utils.overlay; -import com.google.common.collect.Streams; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntCollection; import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.ObjectArraySet; import it.unimi.dsi.fastutil.objects.ObjectSet; +import java.util.Map; import java.util.stream.Collectors; -import net.elytrium.limboapi.api.utils.OverlayMap; -import org.jetbrains.annotations.NotNull; +import java.util.stream.IntStream; +import java.util.stream.Stream; public class OverlayObject2IntMap extends OverlayMap implements Object2IntMap { @@ -33,6 +34,12 @@ public OverlayObject2IntMap(Object2IntMap parent, Object2IntMap overlay) { overlay.defaultReturnValue(parent.defaultReturnValue()); } + @Override + public Integer get(Object o) { + int value = this.getInt(o); + return value == this.defaultReturnValue() ? null : value; + } + @Override public int getInt(Object key) { int value = ((Object2IntMap) this.overlay).getInt(key); @@ -59,32 +66,54 @@ public int defaultReturnValue() { } @Override - public ObjectSet> object2IntEntrySet() { - return new SetIsObjectSet<>( - Streams - .concat(((Object2IntMap) this.parent).object2IntEntrySet().stream(), ((Object2IntMap) this.overlay).object2IntEntrySet().stream()) - .collect(Collectors.toSet())); + public ObjectSet keySet() { + if (this.override) { + return ((Object2IntMap) this.overlay).keySet(); + } + + return Stream + .concat(((Object2IntMap) this.overlay).keySet().stream(), ((Object2IntMap) this.parent).keySet().stream()) + .collect(Collectors.toCollection(ObjectArraySet::new)); } - @NotNull @Override - public ObjectSet keySet() { - return new SetIsObjectSet<>( - Streams - .concat(((Object2IntMap) this.parent).keySet().stream(), ((Object2IntMap) this.overlay).keySet().stream()) - .collect(Collectors.toSet())); + public IntCollection values() { + if (this.override) { + return ((Object2IntMap) this.overlay).values(); + } + + return IntStream + .concat(((Object2IntMap) this.overlay).values().intStream(), ((Object2IntMap) this.parent).values().intStream()) + .collect(IntArrayList::new, IntArrayList::add, IntArrayList::addAll); } @Override - public IntCollection values() { - return Streams - .concat(((Object2IntMap) this.parent).values().intStream(), ((Object2IntMap) this.overlay).values().intStream()) - .boxed() - .collect(Collectors.toCollection(IntArrayList::new)); + @SuppressWarnings("deprecation") + public ObjectSet> entrySet() { + if (this.override) { + return ((Object2IntMap) this.overlay).entrySet(); + } + + return Stream.concat(this.overlay.entrySet().stream(), this.parent.entrySet().stream()).collect(Collectors.toCollection(ObjectArraySet::new)); + } + + @Override + public ObjectSet> object2IntEntrySet() { + if (this.override) { + return ((Object2IntMap) this.overlay).object2IntEntrySet(); + } + + return Stream + .concat(((Object2IntMap) this.overlay).object2IntEntrySet().stream(), ((Object2IntMap) this.parent).object2IntEntrySet().stream()) + .collect(Collectors.toCollection(ObjectArraySet::new)); } @Override public boolean containsValue(int value) { - return false; + if (this.override) { + return ((Object2IntMap) this.overlay).containsValue(value); + } + + return ((Object2IntMap) this.parent).containsValue(value) || ((Object2IntMap) this.overlay).containsValue(value); } } diff --git a/plugin/src/main/resources/META-INF/services/net.elytrium.limboapi.api.protocol.data.ComponentHolder$Codec b/plugin/src/main/resources/META-INF/services/net.elytrium.limboapi.api.protocol.data.ComponentHolder$Codec new file mode 100644 index 00000000..ba8ea094 --- /dev/null +++ b/plugin/src/main/resources/META-INF/services/net.elytrium.limboapi.api.protocol.data.ComponentHolder$Codec @@ -0,0 +1 @@ +net.elytrium.limboapi.server.item.codec.data.ComponentHolderCodec diff --git a/plugin/src/main/resources/mapping/fluids.json b/plugin/src/main/resources/mapping/fluids.json deleted file mode 100644 index 1d95c69a..00000000 --- a/plugin/src/main/resources/mapping/fluids.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "minecraft:empty": "0", - "minecraft:flowing_water": "1", - "minecraft:water": "2", - "minecraft:flowing_lava": "3", - "minecraft:lava": "4" -} \ No newline at end of file diff --git a/plugin/src/main/resources/mapping/modern_block_id_remap.json b/plugin/src/main/resources/mapping/modern_block_id_remap.json deleted file mode 100644 index b281fc06..00000000 --- a/plugin/src/main/resources/mapping/modern_block_id_remap.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "minecraft:grass": "minecraft:short_grass" -} \ No newline at end of file diff --git a/plugin/src/main/resources/mapping/chat_type_1_19.nbt b/plugin/src/main/resources/mappings/chat_type_1_19.nbt similarity index 100% rename from plugin/src/main/resources/mapping/chat_type_1_19.nbt rename to plugin/src/main/resources/mappings/chat_type_1_19.nbt diff --git a/plugin/src/main/resources/mapping/chat_type_1_19_1.nbt b/plugin/src/main/resources/mappings/chat_type_1_19_1.nbt similarity index 100% rename from plugin/src/main/resources/mapping/chat_type_1_19_1.nbt rename to plugin/src/main/resources/mappings/chat_type_1_19_1.nbt diff --git a/plugin/src/main/resources/mapping/colors_main_map b/plugin/src/main/resources/mappings/colors_main_map similarity index 100% rename from plugin/src/main/resources/mapping/colors_main_map rename to plugin/src/main/resources/mappings/colors_main_map diff --git a/plugin/src/main/resources/mapping/colors_minecraft_1_12_map b/plugin/src/main/resources/mappings/colors_minecraft_1_12_map similarity index 100% rename from plugin/src/main/resources/mapping/colors_minecraft_1_12_map rename to plugin/src/main/resources/mappings/colors_minecraft_1_12_map diff --git a/plugin/src/main/resources/mapping/colors_minecraft_1_16_map b/plugin/src/main/resources/mappings/colors_minecraft_1_16_map similarity index 100% rename from plugin/src/main/resources/mapping/colors_minecraft_1_16_map rename to plugin/src/main/resources/mappings/colors_minecraft_1_16_map diff --git a/plugin/src/main/resources/mapping/colors_minecraft_1_17_map b/plugin/src/main/resources/mappings/colors_minecraft_1_17_map similarity index 100% rename from plugin/src/main/resources/mapping/colors_minecraft_1_17_map rename to plugin/src/main/resources/mappings/colors_minecraft_1_17_map diff --git a/plugin/src/main/resources/mapping/colors_minecraft_1_8_map b/plugin/src/main/resources/mappings/colors_minecraft_1_8_map similarity index 100% rename from plugin/src/main/resources/mapping/colors_minecraft_1_8_map rename to plugin/src/main/resources/mappings/colors_minecraft_1_8_map diff --git a/plugin/src/main/resources/mapping/colors_minimum_version_map b/plugin/src/main/resources/mappings/colors_minimum_version_map similarity index 100% rename from plugin/src/main/resources/mapping/colors_minimum_version_map rename to plugin/src/main/resources/mappings/colors_minimum_version_map diff --git a/plugin/src/main/resources/mapping/damage_type_1_19_4.nbt b/plugin/src/main/resources/mappings/damage_type_1_19_4.nbt similarity index 100% rename from plugin/src/main/resources/mapping/damage_type_1_19_4.nbt rename to plugin/src/main/resources/mappings/damage_type_1_19_4.nbt diff --git a/plugin/src/main/resources/mapping/damage_type_1_20.nbt b/plugin/src/main/resources/mappings/damage_type_1_20.nbt similarity index 100% rename from plugin/src/main/resources/mapping/damage_type_1_20.nbt rename to plugin/src/main/resources/mappings/damage_type_1_20.nbt diff --git a/plugin/src/main/resources/mappings/fluids.json b/plugin/src/main/resources/mappings/fluids.json new file mode 100644 index 00000000..c52b7159 --- /dev/null +++ b/plugin/src/main/resources/mappings/fluids.json @@ -0,0 +1,7 @@ +{ + "minecraft:empty": 0, + "minecraft:flowing_water": 1, + "minecraft:water": 2, + "minecraft:flowing_lava": 3, + "minecraft:lava": 4 +} \ No newline at end of file diff --git a/plugin/src/main/resources/mapping/legacyitems.json b/plugin/src/main/resources/mappings/legacy_items.json similarity index 99% rename from plugin/src/main/resources/mapping/legacyitems.json rename to plugin/src/main/resources/mappings/legacy_items.json index 3fd95d48..ae476bd0 100644 --- a/plugin/src/main/resources/mapping/legacyitems.json +++ b/plugin/src/main/resources/mappings/legacy_items.json @@ -333,4 +333,4 @@ "2265": "minecraft:music_disc_ward", "2266": "minecraft:music_disc_11", "2267": "minecraft:music_disc_wait" -} +} \ No newline at end of file diff --git a/plugin/src/main/resources/mappings/modern_block_id_remap.json b/plugin/src/main/resources/mappings/modern_block_id_remap.json new file mode 100644 index 00000000..a29bcb74 --- /dev/null +++ b/plugin/src/main/resources/mappings/modern_block_id_remap.json @@ -0,0 +1,4 @@ +{ + "minecraft:grass": "minecraft:short_grass", + "minecraft:chain": "minecraft:iron_chain" +} \ No newline at end of file diff --git a/plugin/src/main/resources/mapping/modern_item_id_remap.json b/plugin/src/main/resources/mappings/modern_item_id_remap.json similarity index 100% rename from plugin/src/main/resources/mapping/modern_item_id_remap.json rename to plugin/src/main/resources/mappings/modern_item_id_remap.json diff --git a/settings.gradle b/settings.gradle index c705d205..2ae3031e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,3 +1,3 @@ -getRootProject().setName("limboapi") +this.rootProject.setName("limboapi") -include("api", "plugin") +this.include("api", "plugin")