diff --git a/config/checkstyle/checkstyle.xml b/.config/checkstyle/checkstyle.xml similarity index 99% rename from config/checkstyle/checkstyle.xml rename to .config/checkstyle/checkstyle.xml index a78b3719..a5b2db07 100644 --- a/config/checkstyle/checkstyle.xml +++ b/.config/checkstyle/checkstyle.xml @@ -37,7 +37,7 @@ - + diff --git a/config/checkstyle/suppressions.xml b/.config/checkstyle/suppressions.xml similarity index 100% rename from config/checkstyle/suppressions.xml rename to .config/checkstyle/suppressions.xml diff --git a/config/spotbugs/suppressions.xml b/.config/spotbugs/suppressions.xml similarity index 100% rename from config/spotbugs/suppressions.xml rename to .config/spotbugs/suppressions.xml 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 2d8299e6..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@v3.0.0 - - name: Set up JDK - uses: actions/setup-java@v3.0.0 - with: - distribution: adopt - java-version: 21 - - name: Build LimboAPI - run: ./gradlew build - - name: Upload LimboAPI - uses: actions/upload-artifact@v3.0.0 - 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..8712fdbb --- /dev/null +++ b/.github/workflows/gradle.yml @@ -0,0 +1,26 @@ +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.1.4 + - name: Setup Java + uses: actions/setup-java@v4.2.1 + with: + distribution: temurin + java-version: 17 + - 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.3.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..38fc7527 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,26 @@ +name: Publish to Modrinth + +on: + push: + branches: + - master + +jobs: + publish: + if: github.repository_owner == 'LimboAPI' + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4.1.4 + - name: Setup Java + uses: actions/setup-java@v4.2.1 + with: + distribution: temurin + java-version: 17 + check-latest: true + - 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 1ea17700..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@v3.0.0 - - name: Set up JDK - uses: actions/setup-java@v3.0.0 - with: - distribution: adopt - java-version: 21 - - name: Build LimboAPI - run: ./gradlew build - - name: Upload LimboAPI - uses: actions/upload-artifact@v3.0.0 - 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/HEADER.txt b/HEADER.txt index d543d1ed..21d1055b 100644 --- a/HEADER.txt +++ b/HEADER.txt @@ -1,14 +1,17 @@ -Copyright (C) 2021 - 2024 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 b2bbad41..29b3c73a 100644 --- a/HEADER_MIXED.txt +++ b/HEADER_MIXED.txt @@ -1,31 +1,34 @@ -Copyright (C) 2021 - 2024 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 701d63bc..2f42957a 100644 --- a/api/HEADER.txt +++ b/api/HEADER.txt @@ -1,4 +1,7 @@ -Copyright (C) 2021 - 2024 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..007867d6 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -1,52 +1,43 @@ -//file:noinspection GroovyAssignabilityCheck +//file:noinspection VulnerableLibrariesLocal plugins { - id("java-library") id("maven-publish") } -compileJava() { - getOptions().getRelease().set(17) - getOptions().setEncoding("UTF-8") +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.16.0") - compileOnly("com.github.spotbugs:spotbugs-annotations:$spotbugsVersion") -} - -license() { - matching(includes: ["**/mcprotocollib/**"]) { - header = rootProject.file("HEADER_MCPROTOCOLLIB.txt") - } + 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") - 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.getLinks().addAll("https://docs.oracle.com/en/java/javase/17/docs/api/", "https://jd.papermc.io/velocity/3.3.0/", "https://jd.advntr.dev/nbt/4.16.0/") + options.tags("sinceMinecraft:a:Since Minecraft:") } publishing() { @@ -57,33 +48,13 @@ publishing() { setPassword(System.getenv("ELYTRIUM_MAVEN_PASSWORD")) } - setName("elytrium-repo") - setUrl("https://maven.elytrium.net/repo/") + name = "elytrium" + url = "https://maven.elytrium.net/repo/" } } - publications.create("publication", MavenPublication) { + publications.create("maven", 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 5760ab98..8fb06557 100644 --- a/api/src/main/java/net/elytrium/limboapi/api/Limbo.java +++ b/api/src/main/java/net/elytrium/limboapi/api/Limbo.java @@ -11,7 +11,6 @@ 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.protocol.PacketDirection; import net.elytrium.limboapi.api.protocol.packets.PacketMapping; @@ -46,7 +45,15 @@ public interface Limbo { Limbo setMaxSuppressPacketLength(int maxSuppressPacketLength); - Limbo registerCommand(LimboCommandMeta commandMeta); + /** + * @deprecated Use {@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 2722457b..880de4c5 100644 --- a/api/src/main/java/net/elytrium/limboapi/api/LimboFactory.java +++ b/api/src/main/java/net/elytrium/limboapi/api/LimboFactory.java @@ -29,226 +29,250 @@ import net.elytrium.limboapi.api.protocol.item.ItemComponentMap; import net.elytrium.limboapi.api.protocol.packets.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. + * Creates new virtual chunk * You need to provide the chunk location, you can get it using ({@code block_coordinate >> 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 a thread after 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. + * Creates new virtual item from Item enum * - * @param item Item from item enum. + * @param item Item from item enum * - * @return new virtual item. + * @return new virtual item */ VirtualItem getItem(Item item); /** - * Creates new virtual item from Item enum. + * Creates new virtual item from Item enum * - * @param itemID Modern item identifier. + * @param modernId Modern item identifier * - * @return new virtual item. + * @return new virtual item */ - VirtualItem getItem(String itemID); + VirtualItem getItem(String modernId); /** - * Creates new virtual item from Item enum. + * Creates new virtual item from Item enum * - * @param itemLegacyID Legacy item ID + * @param legacyId Legacy item ID * - * @return new virtual item. + * @return new virtual item */ - VirtualItem getLegacyItem(int itemLegacyID); + VirtualItem getLegacyItem(int legacyId); /** - * Creates new item component map. - * * @return new item component map */ ItemComponentMap createItemComponentMap(); - VirtualBlockEntity getBlockEntity(String entityID); + @Deprecated(forRemoval = true) + default VirtualBlockEntity getBlockEntity(String entityId) { + return this.getBlockEntityFromModernId(entityId); + } + + /** + * @param modernId Should be prefixed with minecraft namespace + */ + @Nullable + VirtualBlockEntity getBlockEntityFromModernId(String modernId); + + /** + * @return Block entity using legacy id or null (used in 1.9-1.10, e.g., DLDetector instead of daylight_detector) + */ + @Nullable + VirtualBlockEntity getBlockEntityFromLegacyId(String legacyId); /** - * A factory to instantiate Minecraft packet objects. + * A factory to instantiate Minecraft packet objects */ PacketFactory getPacketFactory(); 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 62be952e..a1464a47 100644 --- a/api/src/main/java/net/elytrium/limboapi/api/LimboSessionHandler.java +++ b/api/src/main/java/net/elytrium/limboapi/api/LimboSessionHandler.java @@ -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 index efd474a8..79cf384e 100644 --- a/api/src/main/java/net/elytrium/limboapi/api/chunk/BlockEntityVersion.java +++ b/api/src/main/java/net/elytrium/limboapi/api/chunk/BlockEntityVersion.java @@ -8,40 +8,36 @@ 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.Set; 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)), + + 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.of(ProtocolVersion.MINECRAFT_1_20)), - MINECRAFT_1_20_2(EnumSet.of(ProtocolVersion.MINECRAFT_1_20_2)), + 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_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.MAXIMUM_VERSION)); private static final EnumMap MC_VERSION_TO_ITEM_VERSIONS = new EnumMap<>(ProtocolVersion.class); 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; } @@ -54,22 +50,6 @@ public Set getVersions() { } } - public static BlockEntityVersion parse(String from) { - return switch (from) { - case "1.19" -> MINECRAFT_1_19; - case "1.19.1" -> MINECRAFT_1_19_1; - case "1.19.3" -> MINECRAFT_1_19_3; - case "1.19.4" -> MINECRAFT_1_19_4; - case "1.20" -> MINECRAFT_1_20; - case "1.20.2" -> MINECRAFT_1_20_2; - case "1.20.3" -> MINECRAFT_1_20_3; - case "1.20.5" -> MINECRAFT_1_20_5; - case "1.21" -> MINECRAFT_1_21; - case "1.21.2" -> MINECRAFT_1_21_2; - default -> 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/Dimension.java b/api/src/main/java/net/elytrium/limboapi/api/chunk/Dimension.java index ec541d3b..ce3b045a 100644 --- a/api/src/main/java/net/elytrium/limboapi/api/chunk/Dimension.java +++ b/api/src/main/java/net/elytrium/limboapi/api/chunk/Dimension.java @@ -14,18 +14,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 +33,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/chunk/VirtualBiome.java b/api/src/main/java/net/elytrium/limboapi/api/chunk/VirtualBiome.java index b5c37746..5758b2e6 100644 --- a/api/src/main/java/net/elytrium/limboapi/api/chunk/VirtualBiome.java +++ b/api/src/main/java/net/elytrium/limboapi/api/chunk/VirtualBiome.java @@ -11,5 +11,10 @@ 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/chunk/VirtualBlock.java b/api/src/main/java/net/elytrium/limboapi/api/chunk/VirtualBlock.java index 77d5eb71..f07950ca 100644 --- a/api/src/main/java/net/elytrium/limboapi/api/chunk/VirtualBlock.java +++ b/api/src/main/java/net/elytrium/limboapi/api/chunk/VirtualBlock.java @@ -12,26 +12,76 @@ public interface VirtualBlock { - short getModernID(); + @Deprecated(forRemoval = true) + default String getModernStringID() { + return this.modernId(); + } - String getModernStringID(); + String modernId(); - @Deprecated - short getID(ProtocolVersion version); + @Deprecated(forRemoval = true) + default short getModernID() { + return this.blockStateId(); + } - short getBlockID(WorldVersion version); + /** + * @return Latest supported version block state id + */ + short blockStateId(); - short getBlockID(ProtocolVersion version); + @Deprecated(forRemoval = true) + default short getID(ProtocolVersion version) { + return this.getBlockStateID(version); + } - boolean isSupportedOn(ProtocolVersion version); + @Deprecated(forRemoval = true) + default short getBlockStateID(ProtocolVersion version) { + return this.blockStateId(version); + } - boolean isSupportedOn(WorldVersion 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); - short getBlockStateID(ProtocolVersion version); + @Deprecated(forRemoval = true) + default boolean isSolid() { + return this.solid(); + } - boolean isSolid(); + boolean solid(); - boolean isAir(); + @Deprecated(forRemoval = true) + default boolean isAir() { + return this.air(); + } - boolean isMotionBlocking(); + 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/chunk/VirtualBlockEntity.java b/api/src/main/java/net/elytrium/limboapi/api/chunk/VirtualBlockEntity.java index d7e74afe..38f8b30e 100644 --- a/api/src/main/java/net/elytrium/limboapi/api/chunk/VirtualBlockEntity.java +++ b/api/src/main/java/net/elytrium/limboapi/api/chunk/VirtualBlockEntity.java @@ -9,39 +9,86 @@ import com.velocitypowered.api.network.ProtocolVersion; import net.kyori.adventure.nbt.CompoundBinaryTag; +import org.checkerframework.checker.nullness.qual.Nullable; public interface VirtualBlockEntity { - int getID(ProtocolVersion version); + @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); + } - int getID(BlockEntityVersion version); + /** + * @return Protocol id, or {@link Integer#MIN_VALUE} if not exists + */ + int getId(BlockEntityVersion version); boolean isSupportedOn(ProtocolVersion version); boolean isSupportedOn(BlockEntityVersion version); - String getModernID(); + @Deprecated(forRemoval = true) + default Entry getEntry(int posX, int posY, int posZ, CompoundBinaryTag nbt) { + return this.createEntry(null, posX, posY, posZ, nbt); + } - Entry getEntry(int posX, int posY, int posZ, CompoundBinaryTag 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, CompoundBinaryTag nbt); interface Entry { VirtualBlockEntity getBlockEntity(); - int getPosX(); + @Deprecated(forRemoval = true) + default int getID(ProtocolVersion version) { + return this.getBlockEntity().getId(version); + } - int getPosY(); + @Deprecated(forRemoval = true) + default int getID(BlockEntityVersion version) { + return this.getBlockEntity().getId(version); + } - int getPosZ(); + @Deprecated(forRemoval = true) + default boolean isSupportedOn(ProtocolVersion version) { + return this.getBlockEntity().isSupportedOn(version); + } - CompoundBinaryTag getNbt(); + @Deprecated(forRemoval = true) + default boolean isSupportedOn(BlockEntityVersion version) { + return this.getBlockEntity().isSupportedOn(version); + } - int getID(ProtocolVersion version); + int getPosX(); - int getID(BlockEntityVersion version); + int getPosY(); - boolean isSupportedOn(ProtocolVersion version); + int getPosZ(); - boolean isSupportedOn(BlockEntityVersion version); + CompoundBinaryTag getNbt(ProtocolVersion 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 index 40713bf8..2b7843a2 100644 --- a/api/src/main/java/net/elytrium/limboapi/api/chunk/VirtualChunk.java +++ b/api/src/main/java/net/elytrium/limboapi/api/chunk/VirtualChunk.java @@ -7,7 +7,9 @@ package net.elytrium.limboapi.api.chunk; +import net.elytrium.limboapi.api.chunk.data.BlockSection; import net.elytrium.limboapi.api.chunk.data.ChunkSnapshot; +import net.elytrium.limboapi.api.chunk.data.LightSection; import net.kyori.adventure.nbt.CompoundBinaryTag; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -15,12 +17,20 @@ public interface VirtualChunk { + /** + * @param posX Must be within range [0,15] + * @param posZ Must be within range [0,15] + */ 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); + /** + * @param posX Must be within range [0,15] + * @param posZ Must be within range [0,15] + */ @NonNull VirtualBlock getBlock(int posX, int posY, int posZ); @@ -47,7 +57,19 @@ public interface VirtualChunk { int getPosZ(); - ChunkSnapshot getFullChunkSnapshot(); + @Deprecated(forRemoval = true) + default ChunkSnapshot getFullChunkSnapshot() { + return this.createSnapshot(true); + } - ChunkSnapshot getPartialChunkSnapshot(long previousUpdate); + @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/VirtualWorld.java b/api/src/main/java/net/elytrium/limboapi/api/chunk/VirtualWorld.java index ff44f392..043b7926 100644 --- a/api/src/main/java/net/elytrium/limboapi/api/chunk/VirtualWorld.java +++ b/api/src/main/java/net/elytrium/limboapi/api/chunk/VirtualWorld.java @@ -17,6 +17,8 @@ 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); + @NonNull VirtualBlock getBlock(int posX, int posY, int posZ); @@ -55,6 +57,4 @@ public interface VirtualWorld { 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 index 82569ca3..70eda03e 100644 --- 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 @@ -16,7 +16,15 @@ public interface BlockSection { VirtualBlock getBlockAt(int posX, int posY, int posZ); - BlockSection getSnapshot(); + @Deprecated(forRemoval = true) + default BlockSection getSnapshot() { + return this.copy(); + } - long getLastUpdate(); + BlockSection copy(); + + @Deprecated(forRemoval = true) + default long getLastUpdate() { + return 0; + } } 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 index c395ba5f..5035d09e 100644 --- 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 @@ -13,7 +13,10 @@ public interface BlockStorage { - void write(Object byteBufObject, ProtocolVersion version, int pass); + /** + * @param pass Used only in 1.7-1.8 storage + */ + void write(Object buf, ProtocolVersion version, int pass); void set(int posX, int posY, int posZ, @NonNull VirtualBlock block); 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 index 6e53acd7..357b37f4 100644 --- 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 @@ -7,7 +7,10 @@ package net.elytrium.limboapi.api.chunk.data; +import com.velocitypowered.api.network.ProtocolVersion; +import java.util.Arrays; import java.util.List; +import java.util.stream.Stream; import net.elytrium.limboapi.api.chunk.VirtualBiome; import net.elytrium.limboapi.api.chunk.VirtualBlock; import net.elytrium.limboapi.api.chunk.VirtualBlockEntity; @@ -16,17 +19,65 @@ public interface ChunkSnapshot { VirtualBlock getBlock(int posX, int posY, int posZ); - int getPosX(); + @Deprecated(forRemoval = true) + default int getPosX() { + return this.posX(); + } - int getPosZ(); + int posX(); - boolean isFullChunk(); + @Deprecated(forRemoval = true) + default int getPosZ() { + return this.posZ(); + } - BlockSection[] getSections(); + int posZ(); - LightSection[] getLight(); + @Deprecated(forRemoval = true) + default boolean isFullChunk() { + return this.fullChunk(); + } - VirtualBiome[] getBiomes(); + boolean fullChunk(); - List getBlockEntityEntries(); + @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/data/LightSection.java b/api/src/main/java/net/elytrium/limboapi/api/chunk/data/LightSection.java index 73fd1798..8ca1d160 100644 --- 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 @@ -23,7 +23,10 @@ public interface LightSection { byte getSkyLight(int posX, int posY, int posZ); - long getLastUpdate(); + @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/chunk/util/CompactStorage.java index f45ed986..720bf6e5 100644 --- a/api/src/main/java/net/elytrium/limboapi/api/chunk/util/CompactStorage.java +++ b/api/src/main/java/net/elytrium/limboapi/api/chunk/util/CompactStorage.java @@ -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/command/LimboCommandMeta.java b/api/src/main/java/net/elytrium/limboapi/api/command/LimboCommandMeta.java index b5ca7ad1..3efc5507 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 @@ -16,13 +16,14 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +@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) { 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 20c46096..ac634773 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,25 @@ package net.elytrium.limboapi.api.event; -import com.google.common.base.Preconditions; 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 */ 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 +44,6 @@ public Queue getOnJoinCallbacks() { return this.onJoinCallbacks; } - public Function getOnKickCallback() { return this.onKickCallback; } @@ -55,7 +55,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/file/BuiltInWorldFileType.java b/api/src/main/java/net/elytrium/limboapi/api/file/BuiltInWorldFileType.java index 5345202b..f19fef17 100644 --- a/api/src/main/java/net/elytrium/limboapi/api/file/BuiltInWorldFileType.java +++ b/api/src/main/java/net/elytrium/limboapi/api/file/BuiltInWorldFileType.java @@ -8,6 +8,7 @@ package net.elytrium.limboapi.api.file; public enum BuiltInWorldFileType { + SCHEMATIC, WORLDEDIT_SCHEM, STRUCTURE diff --git a/api/src/main/java/net/elytrium/limboapi/api/material/Block.java b/api/src/main/java/net/elytrium/limboapi/api/material/Block.java index d478743b..90bbef42 100644 --- a/api/src/main/java/net/elytrium/limboapi/api/material/Block.java +++ b/api/src/main/java/net/elytrium/limboapi/api/material/Block.java @@ -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/material/Item.java b/api/src/main/java/net/elytrium/limboapi/api/material/Item.java index 7ac214a1..af7e90f9 100644 --- a/api/src/main/java/net/elytrium/limboapi/api/material/Item.java +++ b/api/src/main/java/net/elytrium/limboapi/api/material/Item.java @@ -352,12 +352,17 @@ public enum Item { this.id = id; } - @Deprecated + @Deprecated(forRemoval = true) public int getID() { return this.id; } + @Deprecated(forRemoval = true) public int getLegacyID() { return this.id; } + + public int getLegacyId() { + return this.id; + } } 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 index 5b060b86..e964fbce 100644 --- a/api/src/main/java/net/elytrium/limboapi/api/material/VirtualItem.java +++ b/api/src/main/java/net/elytrium/limboapi/api/material/VirtualItem.java @@ -11,13 +11,28 @@ public interface VirtualItem { - short getID(ProtocolVersion version); + @Deprecated(forRemoval = true) + default String getModernID() { + return this.modernId(); + } - short getID(WorldVersion version); + String modernId(); - boolean isSupportedOn(ProtocolVersion version); + @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); - String getModernID(); + boolean isSupportedOn(ProtocolVersion version); } 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 index 83e8d892..2de50a0f 100644 --- a/api/src/main/java/net/elytrium/limboapi/api/material/WorldVersion.java +++ b/api/src/main/java/net/elytrium/limboapi/api/material/WorldVersion.java @@ -8,25 +8,26 @@ 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.List; import java.util.Set; 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_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(ProtocolVersion.MINECRAFT_1_16, ProtocolVersion.MINECRAFT_1_16_1), + 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(ProtocolVersion.MINECRAFT_1_19_3), + 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(ProtocolVersion.MINECRAFT_1_20_3), + 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.MAXIMUM_VERSION)); @@ -34,18 +35,10 @@ public enum WorldVersion { 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; } @@ -58,26 +51,6 @@ public Set getVersions() { } } - public static WorldVersion parse(String from) { - return switch (from) { - case "1.13" -> MINECRAFT_1_13; - case "1.13.2" -> MINECRAFT_1_13_2; - case "1.14" -> MINECRAFT_1_14; - case "1.15" -> MINECRAFT_1_15; - case "1.16" -> MINECRAFT_1_16; - case "1.16.2" -> MINECRAFT_1_16_2; - case "1.17" -> MINECRAFT_1_17; - case "1.19" -> MINECRAFT_1_19; - case "1.19.3" -> MINECRAFT_1_19_3; - case "1.19.4" -> MINECRAFT_1_19_4; - case "1.20" -> MINECRAFT_1_20; - case "1.20.3" -> MINECRAFT_1_20_3; - case "1.20.5" -> MINECRAFT_1_20_5; - case "1.21.2" -> MINECRAFT_1_21_2; - default -> 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/mcprotocollib/NibbleArray3D.java b/api/src/main/java/net/elytrium/limboapi/api/mcprotocollib/NibbleArray3D.java index a804c4b2..3d682a35 100644 --- a/api/src/main/java/net/elytrium/limboapi/api/mcprotocollib/NibbleArray3D.java +++ b/api/src/main/java/net/elytrium/limboapi/api/mcprotocollib/NibbleArray3D.java @@ -51,7 +51,7 @@ public byte[] getData() { public int get(int posX, int posY, 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) { @@ -60,11 +60,7 @@ public void set(int posX, int posY, int posZ, int value) { public void set(int key, int val) { 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 | val & 0x0F : this.data[index] & 0x0F | (val & 0x0F) << 4); } public void fill(int value) { diff --git a/api/src/main/java/net/elytrium/limboapi/api/player/GameMode.java b/api/src/main/java/net/elytrium/limboapi/api/player/GameMode.java index bda70f31..15edd08a 100644 --- a/api/src/main/java/net/elytrium/limboapi/api/player/GameMode.java +++ b/api/src/main/java/net/elytrium/limboapi/api/player/GameMode.java @@ -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/player/LimboPlayer.java b/api/src/main/java/net/elytrium/limboapi/api/player/LimboPlayer.java index b709e871..2ebc4df2 100644 --- a/api/src/main/java/net/elytrium/limboapi/api/player/LimboPlayer.java +++ b/api/src/main/java/net/elytrium/limboapi/api/player/LimboPlayer.java @@ -10,44 +10,92 @@ 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.material.VirtualItem; 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.EntityDataValue; import net.kyori.adventure.nbt.CompoundBinaryTag; +import org.checkerframework.checker.nullness.qual.Nullable; public interface LimboPlayer { - void writePacket(Object packetObj); + void writePacket(Object msg); - void writePacketAndFlush(Object packetObj); + void writePacketAndFlush(Object msg); void flushPackets(); - void closeWith(Object packetObj); + void closeWith(Object msg); ScheduledExecutorService getScheduledExecutor(); - void sendImage(BufferedImage image); + default void sendImage(BufferedImage image) { + this.sendImage(0, image, true, 36, true); + } - void sendImage(BufferedImage image, boolean sendItem); + default void sendImage(BufferedImage image, boolean sendItem) { + this.sendImage(0, image, sendItem, 36, true); + } - void sendImage(int mapID, BufferedImage image); + default void sendImage(int mapId, BufferedImage image) { + this.sendImage(mapId, image, true, 36, true); + } - void sendImage(int mapID, BufferedImage image, boolean sendItem); + default void sendImage(int mapId, BufferedImage image, boolean sendItem) { + this.sendImage(mapId, image, sendItem, 36, true); + } - void sendImage(int mapID, BufferedImage image, boolean sendItem, boolean resize); + default void sendImage(int mapId, BufferedImage image, boolean sendItem, boolean resize) { + this.sendImage(mapId, image, sendItem, 36, resize); + } - void setInventory(VirtualItem item, int count); + void sendImage(int mapId, BufferedImage image, boolean sendItem, int itemSlot, boolean resize); - void setInventory(VirtualItem item, int slot, int count); + @Deprecated(forRemoval = true) + default void setInventory(VirtualItem item, int count) { + this.setItemInMainHand(item, count); + } - void setInventory(int slot, VirtualItem item, int count, int data, CompoundBinaryTag nbt); + @Deprecated(forRemoval = true) + default void setInventory(VirtualItem item, int slot, int count) { + this.setItem(slot, item, count); + } - void setInventory(int slot, VirtualItem item, int count, int data, ItemComponentMap map); + @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, ItemComponentMap 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 ItemComponentMap 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 ItemComponentMap map); + + void setItem(int slot, VirtualItem item, int count, short data, @Nullable CompoundBinaryTag nbt, @Nullable ItemComponentMap map); void setGameMode(GameMode gameMode); + void setWorldTime(long ticks); + void teleport(double posX, double posY, double posZ, float yaw, float pitch); void disableFalling(); @@ -58,13 +106,37 @@ public interface LimboPlayer { void disconnect(RegisteredServer server); - void sendAbilities(); + /** + * @param id Entity id + */ + void setEntityData(int id, Collection> packedItems); + + @Deprecated(forRemoval = true) + default void sendAbilities() { + this.sendGameModeSpecificAbilities(); + } - void sendAbilities(int abilities, float flySpeed, float walkSpeed); + /** + * @apiNote Also resets flyingSpeed and walkingSpeed + */ + void sendGameModeSpecificAbilities(); - void sendAbilities(byte abilities, float flySpeed, float walkSpeed); + /** + * @param abilities See {@link AbilityFlags} (e.g. {@code AbilityFlags.ALLOW_FLYING | AbilityFlags.CREATIVE_MODE}) + */ + void sendAbilities(int abilities, float flyingSpeed, float walkingSpeed); - byte getAbilities(); + /** + * @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(); @@ -73,6 +145,4 @@ public interface LimboPlayer { Player getProxyPlayer(); int getPing(); - - void setWorldTime(long ticks); } diff --git a/api/src/main/java/net/elytrium/limboapi/api/protocol/item/DataComponentType.java b/api/src/main/java/net/elytrium/limboapi/api/protocol/item/DataComponentType.java new file mode 100644 index 00000000..0d1e9686 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/protocol/item/DataComponentType.java @@ -0,0 +1,123 @@ +/* + * 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.item; + +/** + * @sinceMinecraft 1.20.5 + */ +public enum DataComponentType { + + CUSTOM_DATA, + MAX_STACK_SIZE, + MAX_DAMAGE, + DAMAGE, + UNBREAKABLE, + CUSTOM_NAME, + ITEM_NAME, + /** + * @sinceMinecraft 1.21.2 + */ + ITEM_MODEL, + LORE, + RARITY, + ENCHANTMENTS, + CAN_PLACE_ON, + CAN_BREAK, + ATTRIBUTE_MODIFIERS, + CUSTOM_MODEL_DATA, + HIDE_ADDITIONAL_TOOLTIP, + HIDE_TOOLTIP, + REPAIR_COST, + CREATIVE_SLOT_LOCK, + ENCHANTMENT_GLINT_OVERRIDE, + INTANGIBLE_PROJECTILE, + FOOD, + /** + * @deprecated Removed in 1.21.2 + */ + @Deprecated + FIRE_RESISTANT, + /** + * @sinceMinecraft 1.21.2 + */ + CONSUMABLE, + /** + * @sinceMinecraft 1.21.2 + */ + USE_REMAINDER, + /** + * @sinceMinecraft 1.21.2 + */ + USE_COOLDOWN, + /** + * @sinceMinecraft 1.21.2 + */ + DAMAGE_RESISTANT, + TOOL, + /** + * @sinceMinecraft 1.21.2 + */ + ENCHANTABLE, + /** + * @sinceMinecraft 1.21.2 + */ + EQUIPPABLE, + /** + * @sinceMinecraft 1.21.2 + */ + REPAIRABLE, + /** + * @sinceMinecraft 1.21.2 + */ + GLIDER, + /** + * @sinceMinecraft 1.21.2 + */ + TOOLTIP_STYLE, + /** + * @sinceMinecraft 1.21.2 + */ + DEATH_PROTECTION, + STORED_ENCHANTMENTS, + DYED_COLOR, + MAP_COLOR, + MAP_ID, + MAP_DECORATIONS, + MAP_POST_PROCESSING, + CHARGED_PROJECTILES, + BUNDLE_CONTENTS, + POTION_CONTENTS, + SUSPICIOUS_STEW_EFFECTS, + WRITABLE_BOOK_CONTENT, + WRITTEN_BOOK_CONTENT, + TRIM, + DEBUG_STICK_STATE, + ENTITY_DATA, + BUCKET_ENTITY_DATA, + BLOCK_ENTITY_DATA, + INSTRUMENT, + OMINOUS_BOTTLE_AMPLIFIER, + /** + * @sinceMinecraft 1.21 + */ + JUKEBOX_PLAYABLE, + RECIPES, + LODESTONE_TRACKER, + FIREWORK_EXPLOSION, + FIREWORKS, + PROFILE, + NOTE_BLOCK_SOUND, + BANNER_PATTERNS, + BASE_COLOR, + POT_DECORATIONS, + CONTAINER, + BLOCK_STATE, + BEES, + LOCK, + CONTAINER_LOOT +} 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/item/ItemComponent.java index bcef52cd..c816d459 100644 --- a/api/src/main/java/net/elytrium/limboapi/api/protocol/item/ItemComponent.java +++ b/api/src/main/java/net/elytrium/limboapi/api/protocol/item/ItemComponent.java @@ -7,11 +7,12 @@ package net.elytrium.limboapi.api.protocol.item; -public interface ItemComponent { +import org.checkerframework.checker.nullness.qual.Nullable; - String getName(); +public interface ItemComponent { - ItemComponent setValue(T value); + void setValue(@Nullable T value); + @Nullable T getValue(); } 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 index a3ab0cb0..b1fed10b 100644 --- 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 @@ -8,19 +8,41 @@ package net.elytrium.limboapi.api.protocol.item; import com.velocitypowered.api.network.ProtocolVersion; -import java.util.List; +import java.util.Map; +import org.checkerframework.checker.nullness.qual.Nullable; +/** + * @sinceMinecraft 1.20.5 + */ public interface ItemComponentMap { - ItemComponentMap add(ProtocolVersion version, String name, T value); + @Deprecated(forRemoval = true) + default ItemComponentMap add(ProtocolVersion version, String name, T value) { + return this.put(DataComponentType.valueOf(name.substring(10/*"minecraft:".length()*/).toUpperCase()), value); + } + + @Deprecated(forRemoval = true) + default ItemComponentMap remove(ProtocolVersion version, String name) { + this.remove(DataComponentType.valueOf(name.substring(10/*"minecraft:".length()*/).toUpperCase())); + return this; + } + + ItemComponentMap put(DataComponentType type, T value); + + ItemComponent remove(DataComponentType type); + + T get(DataComponentType type); - ItemComponentMap remove(ProtocolVersion version, String name); + T getOrDefault(DataComponentType type, T defaultValue); - List getAdded(); + Map> getComponents(); - List getRemoved(); + ItemComponentMap read(Object buf, ProtocolVersion version); - void read(ProtocolVersion version, Object buffer); + @Deprecated(forRemoval = true) + default void write(ProtocolVersion version, Object buf) { + this.write(buf, version); + } - void write(ProtocolVersion version, Object buffer); + ItemComponentMap write(Object buf, ProtocolVersion version); } 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 ceaf8177..00000000 --- a/api/src/main/java/net/elytrium/limboapi/api/protocol/map/MapPalette.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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.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 index 4d3c894d..64cdb905 100644 --- 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 @@ -8,58 +8,174 @@ package net.elytrium.limboapi.api.protocol.packets; import com.velocitypowered.api.network.ProtocolVersion; -import java.util.List; +import java.util.Collection; import java.util.Map; import net.elytrium.limboapi.api.chunk.Dimension; +import net.elytrium.limboapi.api.chunk.VirtualBlockEntity; 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.PreparedPacket; 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.EntityDataValue; import net.elytrium.limboapi.api.protocol.packets.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 { - Object createChangeGameStatePacket(int reason, float value); + @Deprecated(forRemoval = true) + default Object createChangeGameStatePacket(int event, float param) { + return this.createGameEventPacket(event, param); + } - Object createChunkDataPacket(ChunkSnapshot chunkSnapshot, boolean legacySkyLight, int maxSections); + Object createGameEventPacket(int event, float param); - Object createChunkDataPacket(ChunkSnapshot chunkSnapshot, Dimension dimension); + /** + * @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 Minecraft 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); Object createDefaultSpawnPositionPacket(int posX, int posY, int posZ, float angle); - Object createMapDataPacket(int mapID, byte scale, MapData mapData); + Object createMapDataPacket(int mapId, byte scale, MapData mapData); /** - * @param flags See {@link AbilityFlags}. (e.g. {@code AbilityFlags.ALLOW_FLYING | AbilityFlags.CREATIVE_MODE}) + * @param id Entity id */ - Object createPlayerAbilitiesPacket(int flags, float flySpeed, float walkSpeed); + Object createEntityDataPacket(int id, Collection> packedItems); /** - * @param flags See {@link AbilityFlags}. (e.g. {@code AbilityFlags.ALLOW_FLYING | AbilityFlags.CREATIVE_MODE}) + * @param abilities See {@link AbilityFlags} (e.g. {@code AbilityFlags.ALLOW_FLYING | AbilityFlags.CREATIVE_MODE}) */ - Object createPlayerAbilitiesPacket(byte flags, float flySpeed, float walkSpeed); + 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); + } - Object createPositionRotationPacket(double posX, double posY, double posZ, float yaw, float pitch, - boolean onGround, int teleportID, boolean dismountVehicle); + @Deprecated(forRemoval = true) + default Object createSetSlotPacket(int containerId, int slot, VirtualItem item, int count, int data, @Nullable ItemComponentMap map) { + return this.createSetSlotPacket(containerId, slot, item, count, (short) data, map); + } - Object createSetExperiencePacket(float expBar, int level, int totalExp); + Object createSetSlotPacket(int containerId, int slot, VirtualItem item, int count); - Object createSetSlotPacket(int windowID, int slot, VirtualItem item, int count, int data, @Nullable CompoundBinaryTag nbt); + Object createSetSlotPacket(int containerId, int slot, VirtualItem item, int count, @Nullable CompoundBinaryTag nbt); - Object createSetSlotPacket(int windowID, int slot, VirtualItem item, int count, int data, @Nullable ItemComponentMap map); + Object createSetSlotPacket(int containerId, int slot, VirtualItem item, int count, @Nullable ItemComponentMap map); - Object createTimeUpdatePacket(long worldAge, long timeOfDay); + Object createSetSlotPacket(int containerId, int slot, VirtualItem item, int count, short data); - Object createUpdateViewPositionPacket(int posX, int posZ); + 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 ItemComponentMap map); + + Object createSetSlotPacket(int containerId, int slot, VirtualItem item, int count, short data, @Nullable CompoundBinaryTag nbt, @Nullable ItemComponentMap 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); - Object createUpdateTagsPacket(Map>> tags); + /** + * @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/packets/PacketMapping.java index 14f6ad09..11eb36ab 100644 --- a/api/src/main/java/net/elytrium/limboapi/api/protocol/packets/PacketMapping.java +++ b/api/src/main/java/net/elytrium/limboapi/api/protocol/packets/PacketMapping.java @@ -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/packets/data/AbilityFlags.java b/api/src/main/java/net/elytrium/limboapi/api/protocol/packets/data/AbilityFlags.java index 665d0ae1..dd4cb5f1 100644 --- 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 @@ -8,12 +8,12 @@ package net.elytrium.limboapi.api.protocol.packets.data; /** - * For PlayerAbilities packet. + * 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; + 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/packets/data/BiomeData.java index f42e03d9..d4b6550d 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/packets/data/BiomeData.java @@ -13,27 +13,27 @@ import net.elytrium.limboapi.api.chunk.data.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/packets/data/BlockPos.java b/api/src/main/java/net/elytrium/limboapi/api/protocol/packets/data/BlockPos.java new file mode 100644 index 00000000..dc6db112 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/protocol/packets/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.packets.data; + +public record BlockPos(int posX, int posY, int posZ) implements EntityDataValue.Particle.VibrationParticle.PositionSource { + +} diff --git a/api/src/main/java/net/elytrium/limboapi/api/protocol/packets/data/EntityDataValue.java b/api/src/main/java/net/elytrium/limboapi/api/protocol/packets/data/EntityDataValue.java new file mode 100644 index 00000000..c18c7891 --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/protocol/packets/data/EntityDataValue.java @@ -0,0 +1,514 @@ +/* + * 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.packets.data; + +import com.velocitypowered.api.network.ProtocolVersion; +import java.util.ArrayList; +import java.util.Collection; +import java.util.OptionalInt; +import java.util.UUID; +import net.elytrium.limboapi.api.player.LimboPlayer; +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.nbt.EndBinaryTag; +import net.kyori.adventure.text.Component; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.common.value.qual.IntRange; + +/** + * 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 NBTComponent}, {@link Component}, {@link OptionalNBTComponent}, {@link OptionalComponent}, {@link ItemStack},
+ * {@link Boolean}, (serialized as byte data type <=1.8, boolean >1.8)
+ * {@link Rotations}, {@link BlockPos}, {@link OptionalBlockPos}, {@link Direction}, {@link OptionalUUID}, {@link BlockState}, {@link OptionalBlockState},
+ * {@link CompoundBinaryTag} ({@link EndBinaryTag#endBinaryTag()} Should be used instead as 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 WolfVariant}, {@link FrogVariant}, {@link OptionalGlobalPos}, {@link PaintingVariant},
+ * {@link SnifferState}, {@link ArmadilloState}, {@link Vector3}, {@link Quaternion}.
+ * 
+ * + * @see LimboPlayer#setEntityData(int, Collection) + */ +public record EntityDataValue(int id, @Nullable T value) { + + //public EntityDataValue { + // System.out.println(value); + //} + + public record OptionalComponent(@Nullable Component component) { + + public static final OptionalComponent EMPTY = new OptionalComponent(null); + } + + /** + * Modern {@link Component} represented in compound nbt tag, used in >=1.20.3. Will be automatically converted to a JSON component when encoding for <1.20.3 + */ + public record NBTComponent(BinaryTag component) { + + } + + public record OptionalNBTComponent(@Nullable NBTComponent component) { + + public static final OptionalNBTComponent EMPTY = new OptionalNBTComponent(null); + } + + public record OptionalBlockPos(@Nullable BlockPos blockPos) { + + public static final OptionalBlockPos EMPTY = new OptionalBlockPos(null); + } + + /** + * @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(); + } + + public record OptionalUUID(@Nullable UUID uuid) { + + public static final OptionalUUID EMPTY = new OptionalUUID(null); + } + + /** + * @sinceMinecraft 1.19.4 + */ + public record BlockState(int blockState/*TODO use existing api*/) implements Particle.ParticleData { + + } + + public record OptionalBlockState(@Nullable BlockState blockState) { + + public static final OptionalBlockState EMPTY = new OptionalBlockState(null); + } + + /** + * @sinceMinecraft 1.13 + */ + public record Particle(int type/*TODO enum + registry*/, @Nullable ParticleData data) { + + /** + * @sinceMinecraft 1.20.5 + * @param color ARGB + */ + public record ColorParticleData(int color) implements ParticleData { + + /** + * @param red must be within range [0.0, 1.0] + * @param green must be within range [0.0, 1.0] + * @param blue must be within range [0.0, 1.0] + */ + public ColorParticleData(float red, float green, float blue) { + this(1.0F, red, green, blue); + } + + /** + * @param alpha must be within range [0.0, 1.0] + * @param red must be within range [0.0, 1.0] + * @param green must be within range [0.0, 1.0] + * @param blue must be within range [0.0, 1.0] + */ + public ColorParticleData(float alpha, float red, float green, float blue) { + this(ColorParticleData.as8BitChannel(alpha), ColorParticleData.as8BitChannel(red), ColorParticleData.as8BitChannel(green), ColorParticleData.as8BitChannel(blue)); + } + + /** + * @param red must be within range [0, 255] + * @param green must be within range [0, 255] + * @param blue must be within range [0, 255] + */ + public ColorParticleData(@IntRange(from = 0, to = 255) int red, @IntRange(from = 0, to = 255) int green, @IntRange(from = 0, to = 255) int blue) { + this(255, red, green, blue); + } + + /** + * @param alpha must be within range [0, 255] + * @param red must be within range [0, 255] + * @param green must be within range [0, 255] + * @param blue must be within range [0, 255] + */ + public ColorParticleData(@IntRange(from = 0, to = 255) int alpha, @IntRange(from = 0, to = 255) int red, @IntRange(from = 0, to = 255) int green, @IntRange(from = 0, to = 255) int blue) { + this((alpha << 24) | (red << 16) | (green << 8) | blue); + } + + public float red() { + return (float) (this.color >> 16 & 0xFF) / 255.0F; + } + + public float green() { + return (float) (this.color >> 8 & 0xFF) / 255.0F; + } + + public float blue() { + return (float) (this.color & 0xFF) / 255.0F; + } + + public float alpha() { + return (float) (this.color >>> 24) / 255.0F; + } + + private static int as8BitChannel(float f) { + return ColorParticleData.floor(f * 255.0F); + } + + private static int floor(float f) { + int i = (int) f; + return f < i ? i - 1 : i; + } + } + + /** + * @sinceMinecraft 1.17 + * @param scale Must be within range [0.01, 4.0] + */ + public record DustColorTransitionParticleData(float fromRed, float fromGreen, float fromBlue, float toRed, float toGreen, float toBlue, float scale) implements ParticleData { + + public DustColorTransitionParticleData(float fromRed, float fromGreen, float fromBlue, float toRed, float toGreen, float toBlue, float scale) { + this.fromRed = fromRed; + this.fromGreen = fromGreen; + this.fromBlue = fromBlue; + this.toRed = toRed; + this.toGreen = toGreen; + this.toBlue = toBlue; + this.scale = scale < 0.01F ? 0.01F : Math.min(scale, 4.0F); + } + } + + /** + * @sinceMinecraft 1.13 + * @param scale Must be within range [0.01, 4.0] + */ + public record DustParticleData(float red, float green, float blue, float scale) implements ParticleData { + + /** + * @param scale Must be within range [0.01, 4.0] + */ + public DustParticleData(float red, float green, float blue, float scale) { + this.red = red; + this.green = green; + this.blue = blue; + this.scale = scale < 0.01F ? 0.01F : Math.min(scale, 4.0F); + } + } + + /** + * @sinceMinecraft 1.19 + */ + public record SculkChargeParticleData(float roll) implements ParticleData { + + } + + /** + * @sinceMinecraft 1.19 + */ + public record ShriekParticle(int delay) implements ParticleData { + + } + + /** + * @sinceMinecraft 1.17 + * @param origin Used only in [1.17-1.18.2] + */ + public record VibrationParticle(@Deprecated @Nullable BlockPos origin, PositionSource destination, int arrivalInTicks) implements ParticleData { + + public record EntityPositionSource(int entityId, float yOffset) implements PositionSource { + + } + + public sealed interface PositionSource permits BlockPos, EntityPositionSource { + + } + } + + /** + * @sinceMinecraft 1.21.2 + */ + public record TargetColorParticle(Vector3 target, int color) implements ParticleData { + + } + + public sealed interface ParticleData permits BlockState, + ColorParticleData, DustColorTransitionParticleData, DustParticleData, + ItemStack, SculkChargeParticleData, VibrationParticle, TargetColorParticle, ShriekParticle { + + } + } + + /** + * @sinceMinecraft 1.20.5 + */ + public static class Particles extends ArrayList { + + public Particles() { + + } + + public Particles(int initialCapacity) { + super(initialCapacity); + } + } + + /** + * @sinceMinecraft 1.14 + * @param level Must be no less than 1 + */ + 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.20.5 + */ + public record WolfVariant(int id) { + + } + + /** + * @sinceMinecraft 1.19 + */ + public record FrogVariant(int id) { + + } + + /** + * @sinceMinecraft 1.19 + */ + public record OptionalGlobalPos(@Nullable GlobalPos globalPos) { + + public static final OptionalGlobalPos EMPTY = new OptionalGlobalPos(null); + } + + /** + * @sinceMinecraft 1.19 + */ + public record PaintingVariant(int id) { + + } + + /** + * @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.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/templates/net/elytrium/limboapi/BuildConstants.java b/api/src/main/java/net/elytrium/limboapi/api/protocol/packets/data/GlobalPos.java similarity index 57% rename from api/src/main/templates/net/elytrium/limboapi/BuildConstants.java rename to api/src/main/java/net/elytrium/limboapi/api/protocol/packets/data/GlobalPos.java index f95103dc..550932beb 100644 --- a/api/src/main/templates/net/elytrium/limboapi/BuildConstants.java +++ b/api/src/main/java/net/elytrium/limboapi/api/protocol/packets/data/GlobalPos.java @@ -5,10 +5,11 @@ * reference the LICENSE file in the api top-level directory. */ -package net.elytrium.limboapi; +package net.elytrium.limboapi.api.protocol.packets.data; -// The constants are replaced before compilation. -public class BuildConstants { +/** + * @sinceMinecraft 1.19 + */ +public record GlobalPos(String dimension, BlockPos blockPos) { - public static final String LIMBO_VERSION = "${version}"; } diff --git a/api/src/main/java/net/elytrium/limboapi/api/protocol/packets/data/ItemStack.java b/api/src/main/java/net/elytrium/limboapi/api/protocol/packets/data/ItemStack.java new file mode 100644 index 00000000..c74dfe7c --- /dev/null +++ b/api/src/main/java/net/elytrium/limboapi/api/protocol/packets/data/ItemStack.java @@ -0,0 +1,42 @@ +/* + * 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.packets.data; + +import net.elytrium.limboapi.api.protocol.item.DataComponentType; +import net.elytrium.limboapi.api.protocol.item.ItemComponentMap; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import org.checkerframework.checker.nullness.qual.Nullable; + +public record ItemStack(int material/*TODO VirtualItem*/, int amount, int data, @Nullable CompoundBinaryTag nbt, @Nullable ItemComponentMap map) implements EntityDataValue.Particle.ParticleData { + + public static final ItemStack EMPTY = new ItemStack(-1, 0, 0, null, null); + + public ItemStack(int material, int amount, @Nullable CompoundBinaryTag nbt) { + this(material, amount, nbt == null ? 0 : nbt.getInt("Damage", 0), nbt, null); + } + + public ItemStack(int material, int amount, @Nullable ItemComponentMap map) { + this(material, amount, map == null ? 0 : map.getOrDefault(DataComponentType.DAMAGE, 0), map); + } + + public ItemStack(int material, int amount, int data) { + this(material, amount, data, null, null); + } + + public ItemStack(int material, int amount, int data, @Nullable CompoundBinaryTag nbt) { + this(material, amount, data, nbt, null); + } + + public ItemStack(int material, int amount, int data, @Nullable ItemComponentMap map) { + this(material, amount, data, null, map); + } + + public boolean isEmpty(boolean checkDamage) { + return this == ItemStack.EMPTY || this.material <= 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/packets/data/MapData.java index fbea44ed..99788ab2 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/packets/data/MapData.java @@ -8,62 +8,43 @@ package net.elytrium.limboapi.api.protocol.packets.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/packets/data/MapPalette.java index ef19822e..2990baf3 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/packets/data/MapPalette.java @@ -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 = 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")); + 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 { diff --git a/build.gradle b/build.gradle index 0ffa818b..d85ae7ea 100644 --- a/build.gradle +++ b/build.gradle @@ -1,71 +1,99 @@ -//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("org.ajoberstar.grgit").version("5.2.2") + + id("com.diffplug.spotless").version("6.25.0").apply(false) + id("com.github.spotbugs").version("6.0.19").apply(false) + + id("io.github.goooler.shadow").version("8.1.8") +} + +this.group = "net.elytrium" +this.version = "1.2.0-SNAPSHOT" + +if (this.version.endsWith("-SNAPSHOT")) { + this.version += "+${grgit.head().abbreviatedId}" } -allprojects() { +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") + tasks.withType(JavaCompile).configureEach() { + options.setEncoding("UTF-8") + } + + java() { + sourceCompatibility = targetCompatibility = JavaVersion.VERSION_17 + } - compileJava() { - sourceCompatibility = JavaVersion.VERSION_17 - 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://papermc.io/repo/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 = "10.17.0" + + 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 + //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/gradle.properties b/gradle.properties index 0fd62712..ea880281 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,11 +1,11 @@ -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 +org.gradle.caching=true +org.gradle.parallel=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,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..2c352119 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 48c0a02c..7cf748e7 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.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-all.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 1b6c7873..f5feea6d 100755 --- a/gradlew +++ b/gradlew @@ -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,12 @@ 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 +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,22 +134,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 @@ -193,11 +201,15 @@ 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, 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" \ @@ -205,6 +217,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# 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..9d21a218 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,11 +59,11 @@ 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 @@ -75,13 +78,15 @@ set CLASSPATH=%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/lobb.nbt b/lobb.nbt new file mode 100644 index 00000000..35bb061a Binary files /dev/null and b/lobb.nbt differ diff --git a/mappings-1.13to1.12.nbt b/mappings-1.13to1.12.nbt new file mode 100644 index 00000000..e246587a Binary files /dev/null and b/mappings-1.13to1.12.nbt differ diff --git a/plugin/build.gradle b/plugin/build.gradle index 5bf07b13..05e0bb29 100644 --- a/plugin/build.gradle +++ b/plugin/build.gradle @@ -1,699 +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("io.github.goooler.shadow").version("8.1.8") + id("com.modrinth.minotaur").version("2.8.7") } -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) - - 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 - ) - - 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.3.0-SNAPSHOT") - this.generateBlockMappings(targetDir, blockReports) + compileOnly("io.netty:netty-codec:4.1.86.Final") + compileOnly("io.netty:netty-handler:4.1.86.Final") + compileOnly("it.unimi.dsi:fastutil-core:8.5.11") - 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/**"]) { +// setHeader(getRootProject().file("HEADER_MCPROTOCOLLIB.txt")) +// } +// matching(includes: ["**/LoginListener.java", "**/LoginTasksQueue.java", "**/MinecraftLimitedCompressDecoder.java"]) { +// setHeader(getRootProject().file("HEADER_MIXED.txt")) +// } +// +// setHeader(getRootProject().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 99% rename from plugin/mapping/fallbackdata.json rename to plugin/mappings/fallback_data.json index b2177026..7f010f63 --- a/plugin/mapping/fallbackdata.json +++ b/plugin/mappings/fallback_data.json @@ -427,7 +427,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..f04e1026 --- /dev/null +++ b/plugin/mappings_generator.gradle @@ -0,0 +1,454 @@ +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) + + 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 + ) + + 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 + ) + + 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('.', '_')) + } + + // 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 + } + + String getVersionName() { + return this.versionName + } +} + +ext() { + File buildDirectory = this.getLayout().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 URL(manifestUrl).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 URL(versionInfo["url"]).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, (byte[] buf, int 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 URL(server["url"]).openStream().transferTo(new FileOutputStream(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") + + targetDir.deleteDir() + + String command = version >= MinecraftVersion.MINECRAFT_1_18 + ? "\"%s\" -DbundlerMainClass=net.minecraft.data.Main -jar \"${jarFile.getAbsolutePath()}\" --reports --server" + : "\"%s\" -cp \"${jarFile.getAbsolutePath()}\" net.minecraft.data.Main --reports --server" + exec() { + String javaHome = System.getProperty("java.home") + if (System.getProperty("os.name").toLowerCase(Locale.ROOT).contains("windows")) { + commandLine("cmd", "/c", String.format(command, new File(javaHome, "bin/java.exe"))) + } else { + commandLine("bash", "-c", String.format(command, new File(javaHome, "bin/java"))) + } + + 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, true) + 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 2b0ac454..168a80a8 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; @@ -99,12 +99,10 @@ 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.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; @@ -118,7 +116,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 = { @@ -130,23 +128,26 @@ public class LimboAPI implements LimboFactory { private static final int SUPPORTED_MAXIMUM_PROTOCOL_VERSION_NUMBER = 768; + // 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; @@ -184,19 +185,14 @@ public LimboAPI(Logger logger, ProxyServer server, Metrics.Factory metricsFactor } 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); + } catch (Throwable t) { + throw new ReflectionException(t); } } @@ -207,7 +203,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-2024 Elytrium"); LOGGER.warn(""); LOGGER.warn("Check out our plugins here: https://ely.su/github <3"); LOGGER.warn("Discord: https://ely.su/discord"); @@ -262,17 +258,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 +303,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 +317,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 +417,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 +429,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 +459,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 || !onLogin && 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 { @@ -504,9 +476,9 @@ public void fixCompressor(ChannelPipeline pipeline, ProtocolVersion version) { 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 = Natives.compress.get().create(configuration.getCompressionLevel()); if (!Settings.IMP.MAIN.COMPATIBILITY_MODE) { MinecraftCompressorAndLengthEncoder encoder = new MinecraftCompressorAndLengthEncoder(compressionThreshold, compressor); pipeline.remove(compressionHandler); @@ -535,23 +507,28 @@ 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 getLegacyItem(int itemLegacyID) { - return SimpleItem.fromLegacyID(itemLegacyID); + public VirtualItem getLegacyItem(int legacyId) { + return SimpleItem.fromLegacyId(legacyId); } @Override public ItemComponentMap createItemComponentMap() { - return new SimpleItemComponentMap(this.itemComponentManager); + return new SimpleItemComponentMap(); + } + + @Override + 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 +604,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; } @@ -678,6 +643,28 @@ public WorldFile openWorldFile(BuiltInWorldFileType apiType, CompoundBinaryTag t return WorldFileTypeRegistry.fromApiType(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) { 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 d73497b3..0f8b2c74 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:", 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 8f33215f..0e441399 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/file/MCEditSchematicFile.java +++ b/plugin/src/main/java/net/elytrium/limboapi/file/MCEditSchematicFile.java @@ -20,6 +20,8 @@ import net.elytrium.limboapi.api.LimboFactory; import net.elytrium.limboapi.api.chunk.VirtualWorld; import net.elytrium.limboapi.api.file.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) { + 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 54d9f3f4..20586587 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/file/StructureNbtFile.java +++ b/plugin/src/main/java/net/elytrium/limboapi/file/StructureNbtFile.java @@ -26,6 +26,7 @@ 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 { @@ -42,31 +43,28 @@ public void toWorld(LimboFactory factory, VirtualWorld world, int offsetX, int o 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 b5fed011..2c0587fe 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/file/WorldEditSchemFile.java +++ b/plugin/src/main/java/net/elytrium/limboapi/file/WorldEditSchemFile.java @@ -46,10 +46,10 @@ 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"); } @@ -57,13 +57,12 @@ public WorldEditSchemFile(CompoundBinaryTag tag) { @Override public void toWorld(LimboFactory factory, VirtualWorld world, int offsetX, int offsetY, int offsetZ, int 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/injection/disconnect/DisconnectListener.java b/plugin/src/main/java/net/elytrium/limboapi/injection/disconnect/DisconnectListener.java index 2d0deb4b..002b917f 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/injection/disconnect/DisconnectListener.java +++ b/plugin/src/main/java/net/elytrium/limboapi/injection/disconnect/DisconnectListener.java @@ -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); + this.plugin.removeClientUniqueId(player); } } 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 cd7218a9..35580600 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 98% 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 d622fcae..0b95b387 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() { 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 79136253..0e108055 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,9 @@ 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 +38,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() + ); + 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 +64,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 +73,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.invoke(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); @@ -99,8 +96,8 @@ public EventTask onGameProfileRequest(GameProfileRequestEvent event) { if (future != null) { future.complete(result); } - } catch (Throwable e) { - throw new ReflectionException(e); + } catch (Throwable t) { + throw new ReflectionException(t); } })); } @@ -129,9 +126,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 +141,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 +162,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 1e635a9a..b59743e4 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 @@ -39,16 +39,12 @@ import com.velocitypowered.api.event.player.PlayerChooseInitialServerEvent; import com.velocitypowered.api.event.player.ServerConnectedEvent; import com.velocitypowered.api.network.ProtocolVersion; -import com.velocitypowered.api.proxy.InboundConnection; 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; @@ -62,42 +58,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, 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; @@ -119,7 +111,7 @@ public void hookInitialServer(PlayerChooseInitialServerEvent event) { @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" @@ -132,23 +124,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(); @@ -171,39 +164,37 @@ 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(), 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()); } } @@ -218,16 +209,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); @@ -235,25 +225,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); @@ -263,15 +253,14 @@ 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); @@ -289,7 +278,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; } @@ -299,46 +288,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, - 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 bc027dbd..e9645661 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,8 +61,6 @@ 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.EnumSet; import java.util.List; @@ -71,35 +69,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); + private 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; - private 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; @@ -128,79 +124,66 @@ 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, + 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()); + connection.delayedWrite(new UpsertPlayerInfoPacket(EnumSet.of(UpsertPlayerInfoPacket.Action.UPDATE_DISPLAY_NAME, UpsertPlayerInfoPacket.Action.ADD_PLAYER), List.of(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, 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, t); + } + }, connection.eventLoop()); } - // From Velocity. + // From Velocity @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,13 +191,13 @@ 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, version); } 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(); @@ -223,9 +206,9 @@ private void initialize(MinecraftConnection connection) throws Throwable { } else { if (this.server.registerConnection(this.player)) { if (connection.getActiveSessionHandler() instanceof LoginConfirmHandler confirm) { - confirm.waitForConfirmation(() -> this.connectToServer(logger, this.player, connection)); + confirm.waitForConfirmation(() -> this.connectToServer(connection)); } else { - this.connectToServer(logger, this.player, connection); + this.connectToServer(connection); } } else { this.player.disconnect0(Component.translatable("velocity.error.already-connected-proxy"), false); @@ -238,20 +221,18 @@ private void initialize(MinecraftConnection connection) throws Throwable { }); } + @SuppressWarnings("unchecked") @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 +249,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 +259,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, 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 (Exception 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 c8a1fc3a..e9d14ba0 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<>(); @@ -76,14 +72,13 @@ public void waitForConfirmation(Runnable runnable) { try { this.connection.channelRead(ctx, packet); } catch (Throwable throwable) { - LimboAPI.getLogger().error("{}: exception handling exception in {}", ctx.channel().remoteAddress(), - this.connection.getActiveSessionHandler(), throwable); + LimboAPI.getLogger().error("{}: exception handling exception in {}", ctx.channel().remoteAddress(), this.connection.getActiveSessionHandler(), throwable); } } this.queuedPackets.clear(); } catch (Throwable throwable) { - LimboAPI.getLogger().error("Failed to process packet queue for " + this.player, throwable); + LimboAPI.getLogger().error("Failed to process packet queue for {}", this.player, throwable); } }); } @@ -97,7 +92,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 +108,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 +119,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 2d29d0c6..7cca7152 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 @@ -28,26 +28,17 @@ 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; import net.elytrium.limboapi.protocol.LimboProtocol; +import net.elytrium.limboapi.utils.Reflection; @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 +46,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 +64,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 +73,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 26f6df37..61b2bd00 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 3800f5f5..5e60b0f2 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 4cb0fa5a..61ca7232 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 6cd16acd..207c7f0c 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,65 +36,48 @@ 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.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); + fixedEntry.setDisplayName(item.getDisplayName()); + fixedEntry.setGameMode(item.getGameMode()); + fixedEntry.setLatency(item.getLatency()); + fixedEntry.setDisplayName(item.getDisplayName()); + 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.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 { @@ -104,7 +86,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 e4ab5dfc..05618dbb 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 1ccebd93..3ff709e3 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 @@ -34,8 +34,7 @@ default TabListEntry rewriteEntry(TabListEntry 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.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()); @@ -47,7 +46,12 @@ default TabListEntry rewriteEntry(TabListEntry entry) { default UUID rewriteUuid(UUID uuid) { if (this.getPlayer().getUniqueId().equals(uuid)) { - return LimboAPI.INITIAL_ID.getOrDefault(this.getPlayer(), uuid); + UUID clientUniqueId = LimboAPI.getClientUniqueId(this.getPlayer()); + if (clientUniqueId == null) { + return uuid; + } + + return 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 c7d304c1..a7fb482f 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 ae25ed31..9834181a 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/material/Biome.java b/plugin/src/main/java/net/elytrium/limboapi/material/Biome.java index 60d4b1f1..af01bf7b 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/material/Biome.java +++ b/plugin/src/main/java/net/elytrium/limboapi/material/Biome.java @@ -18,12 +18,10 @@ 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.BinaryTag; import net.kyori.adventure.nbt.CompoundBinaryTag; import net.kyori.adventure.nbt.CompoundBinaryTag.Builder; import net.kyori.adventure.nbt.ListBinaryTag; @@ -38,8 +36,8 @@ public enum Biome implements VirtualBiome { 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")) + Element.Effects.builder(7907327, 329011, 12638463, 415920) + .moodSound(new Element.Effects.MoodSound(6000, 2.0, 8, "minecraft:ambient.cave")) .build() ) ), @@ -49,10 +47,10 @@ public enum Biome implements VirtualBiome { 6, new Element( true, -0.2F, 0.8F, 0.1F, 0.9F, "swamp", - Effects.builder(7907327, 329011, 12638463, 415920) + Element.Effects.builder(7907327, 329011, 12638463, 415920) .grassColorModifier("swamp") .foliageColor(6975545) - .moodSound(MoodSound.of(6000, 2.0, 8, "minecraft:ambient.cave")) + .moodSound(new Element.Effects.MoodSound(6000, 2.0, 8, "minecraft:ambient.cave")) .build() ) ), @@ -62,10 +60,10 @@ public enum Biome implements VirtualBiome { 134, new Element( true, -0.1F, 0.8F, 0.3F, 0.9F, "swamp", - Effects.builder(7907327, 329011, 12638463, 415920) + Element.Effects.builder(7907327, 329011, 12638463, 415920) .grassColorModifier("swamp") .foliageColor(6975545) - .moodSound(MoodSound.of(6000, 2.0, 8, "minecraft:ambient.cave")) + .moodSound(new Element.Effects.MoodSound(6000, 2.0, 8, "minecraft:ambient.cave")) .build() ) ), @@ -73,9 +71,10 @@ public enum Biome implements VirtualBiome { 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")) + new Element( + false, 0.1f, 2.0f, 0.2f, 0.0f, "nether", + Element.Effects.builder(7254527, 329011, 3344392, 4159204) + .moodSound(new Element.Effects.MoodSound(6000, 2.0, 8, "minecraft:ambient.nether_wastes.mood")) .build() ) ), @@ -83,13 +82,15 @@ public enum Biome implements VirtualBiome { 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")) + new Element( + false, 0.1f, 0.5f, 0.2f, 0.5f, "the_end", + Element.Effects.builder(0, 10518688, 12638463, 4159204) + .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; @@ -104,21 +105,13 @@ public enum Biome implements VirtualBiome { 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() { + public int getId() { return this.id; } @@ -126,45 +119,40 @@ 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()) { + 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); + 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", ListBinaryTag.from(Arrays.stream(Biome.values()).map(biome -> biome.encodeBiome(version)).collect(Collectors.toList()))) + .put("value", list.build()) .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 record Element(boolean hasPrecipitation, float depth, float temperature, float scale, float downfall, String category, Effects effects) { public CompoundBinaryTag encode(ProtocolVersion version) { - CompoundBinaryTag.Builder tagBuilder = CompoundBinaryTag.builder() + Builder tagBuilder = CompoundBinaryTag.builder() .putFloat("depth", this.depth) .putFloat("temperature", this.temperature) .putFloat("scale", this.scale) @@ -172,7 +160,7 @@ public CompoundBinaryTag encode(ProtocolVersion version) { .putString("category", this.category) .put("effects", this.effects.encode()); - if (version.compareTo(ProtocolVersion.MINECRAFT_1_19_4) < 0) { + if (version.lessThan(ProtocolVersion.MINECRAFT_1_19_4)) { tagBuilder.putString("precipitation", this.hasPrecipitation ? "rain" : "none"); } else { tagBuilder.putBoolean("has_precipitation", this.hasPrecipitation); @@ -181,524 +169,217 @@ public CompoundBinaryTag encode(ProtocolVersion version) { 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 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() { - 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; - } + Builder result = CompoundBinaryTag.builder(); - public double getOffset() { - return this.offset; - } + 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); - public int getBlockSearchExtent() { - return this.blockSearchExtent; - } + if (this.foliageColor != null) { + result.putInt("foliage_color", this.foliageColor); + } - @NonNull - public String getSound() { - return this.sound; - } + if (this.grassColorModifier != null) { + result.putString("grass_color_modifier", this.grassColorModifier); + } - @Override - public String toString() { - return "Biome.Effects.MoodSound{" - + "tickDelay=" + this.tickDelay - + ", offset=" + this.offset - + ", blockSearchExtent=" + this.blockSearchExtent - + ", sound=" + this.sound - + "}"; - } - } + if (this.music != null) { + result.put("music", this.music.encode()); + } - public static final class Music { + if (this.ambientSound != null) { + result.putString("ambient_sound", this.ambientSound); + } - private final boolean replaceCurrentMusic; - @NonNull - private final String sound; - private final int maxDelay; - private final int minDelay; + if (this.additionsSound != null) { + result.put("additions_sound", this.additionsSound.encode()); + } - private Music(boolean replaceCurrentMusic, @NonNull String sound, int maxDelay, int minDelay) { - this.replaceCurrentMusic = replaceCurrentMusic; - this.sound = sound; - this.maxDelay = maxDelay; - this.minDelay = minDelay; - } + if (this.moodSound != null) { + result.put("mood_sound", this.moodSound.encode()); + } - public static Music of(boolean replaceCurrentMusic, @NonNull String sound, int maxDelay, int minDelay) { - return new Music(replaceCurrentMusic, sound, maxDelay, minDelay); - } + if (this.particle != null) { + result.put("particle", this.particle.encode()); + } - 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(); + return result.build(); } - public boolean isReplaceCurrentMusic() { - return this.replaceCurrentMusic; + public static EffectsBuilder builder(int skyColor, int waterFogColor, int fogColor, int waterColor) { + return new EffectsBuilder() + .skyColor(skyColor) + .waterFogColor(waterFogColor) + .fogColor(fogColor) + .waterColor(waterColor); } - @NonNull - public String getSound() { - return this.sound; - } + public record MoodSound(int tickDelay, double offset, int blockSearchExtent, @NonNull String sound) { - public int getMaxDelay() { - return this.maxDelay; + 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 getMinDelay() { - return this.minDelay; - } + public record Music(boolean replaceCurrentMusic, @NonNull String sound, int maxDelay, int minDelay) { - @Override - public String toString() { - return "Biome.Effects.Music{" - + "replaceCurrentMusic=" + this.replaceCurrentMusic - + ", sound=" + this.sound - + ", maxDelay=" + this.maxDelay - + ", minDelay=" + this.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 static final class AdditionsSound { + public record AdditionsSound(@NonNull String sound, double tickChance) { - @NonNull - private final String sound; - private final double tickChance; - - private AdditionsSound(@NonNull String sound, double tickChance) { - this.sound = sound; - this.tickChance = tickChance; + public CompoundBinaryTag encode() { + return CompoundBinaryTag.builder() + .putString("sound", this.sound) + .putDouble("tick_chance", this.tickChance) + .build(); + } } - public static AdditionsSound of(@NonNull String sound, double tickChance) { - return new AdditionsSound(sound, tickChance); - } + public record Particle(float probability, Effects.Particle.@NonNull ParticleOptions options) { - public CompoundBinaryTag encode() { - return CompoundBinaryTag.builder() - .putString("sound", this.sound) - .putDouble("tick_chance", this.tickChance) - .build(); - } + public Particle(float probability, @NonNull ParticleOptions options) { + this.probability = probability; + this.options = options; + } - @NonNull - public String getSound() { - return this.sound; - } + public CompoundBinaryTag encode() { + return CompoundBinaryTag.builder() + .putFloat("probability", this.probability) + .put("options", this.options.encode()) + .build(); + } - public double getTickChance() { - return this.tickChance; - } + public record ParticleOptions(@NonNull String type) { - @Override - public String toString() { - return "Biome.Effects.AdditionsSound{" - + "sound=" + this.sound - + ", tickChance=" + this.tickChance - + "}"; + public CompoundBinaryTag encode() { + return CompoundBinaryTag.builder() + .putString("type", this.type) + .build(); + } + } } - } - public static final class Particle { + public static class EffectsBuilder { - private final float probability; - @NonNull - private final ParticleOptions options; + 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; - private Particle(float probability, @NonNull ParticleOptions options) { - this.probability = probability; - this.options = options; - } + public EffectsBuilder skyColor(int skyColor) { + this.skyColor = skyColor; + return this; + } - public static Particle of(float probability, @NonNull ParticleOptions options) { - return new Particle(probability, options); - } + public EffectsBuilder waterFogColor(int waterFogColor) { + this.waterFogColor = waterFogColor; + return this; + } - public CompoundBinaryTag encode() { - return CompoundBinaryTag.builder() - .putFloat("probability", this.probability) - .put("options", this.options.encode()) - .build(); - } + public EffectsBuilder fogColor(int fogColor) { + this.fogColor = fogColor; + return this; + } - public float getProbability() { - return this.probability; - } + public EffectsBuilder waterColor(int waterColor) { + this.waterColor = waterColor; + return this; + } - @NonNull - public ParticleOptions getOptions() { - return this.options; - } + public EffectsBuilder foliageColor(Integer foliageColor) { + this.foliageColor = foliageColor; + return this; + } - @Override - public String toString() { - return "Biome.Effects.Particle{" - + "probability=" + this.probability - + ", options=" + this.options - + "}"; - } + public EffectsBuilder grassColorModifier(String grassColorModifier) { + this.grassColorModifier = grassColorModifier; + return this; + } - public static class ParticleOptions { + public EffectsBuilder music(Music music) { + this.music = music; + return this; + } - @NonNull - private final String type; + public EffectsBuilder ambientSound(String ambientSound) { + this.ambientSound = ambientSound; + return this; + } - public ParticleOptions(@NonNull String type) { - this.type = type; + public EffectsBuilder additionsSound(AdditionsSound additionsSound) { + this.additionsSound = additionsSound; + return this; } - public CompoundBinaryTag encode() { - return CompoundBinaryTag.builder() - .putString("type", this.type) - .build(); + public EffectsBuilder moodSound(MoodSound moodSound) { + this.moodSound = moodSound; + return this; + } + + public EffectsBuilder particle(Particle particle) { + this.particle = particle; + return this; } - @NonNull - public String getType() { - return this.type; + 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.Particle.ParticleOptions{" - + "type=" + this.type - + "}"; + 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 + + "}"; } } } - - 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 e6b04b2d..21bf325c 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/mcprotocollib/BitStorage116.java +++ b/plugin/src/main/java/net/elytrium/limboapi/mcprotocollib/BitStorage116.java @@ -30,30 +30,19 @@ import io.netty.buffer.ByteBuf; import java.util.Arrays; import net.elytrium.limboapi.api.chunk.util.CompactStorage; +import net.elytrium.limboapi.protocol.util.LimboProtocolUtils; 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 +61,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 +70,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 +91,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,13 +111,9 @@ public int get(int index) { } @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/mcprotocollib/BitStorage19.java b/plugin/src/main/java/net/elytrium/limboapi/mcprotocollib/BitStorage19.java index 11a9aca4..18c158b2 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/mcprotocollib/BitStorage19.java +++ b/plugin/src/main/java/net/elytrium/limboapi/mcprotocollib/BitStorage19.java @@ -30,6 +30,7 @@ import io.netty.buffer.ByteBuf; import java.util.Arrays; import net.elytrium.limboapi.api.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 3693bbb7..184b4456 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; @@ -39,38 +37,36 @@ 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.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.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; public static final Field VERSIONS_FIELD; public static final MethodHandle PACKET_ID_TO_SUPPLIER_GETTER; @@ -83,34 +79,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 +111,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,15 +129,13 @@ 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); @@ -181,7 +164,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), @@ -199,7 +182,7 @@ public static void init() throws Throwable { createMapping(0x23, ProtocolVersion.MINECRAFT_1_21_2, 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), @@ -217,7 +200,38 @@ public static void init() throws Throwable { createMapping(0x28, ProtocolVersion.MINECRAFT_1_21_2, 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) + ); + 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,7 +250,7 @@ public static void init() throws Throwable { createMapping(0x22, ProtocolVersion.MINECRAFT_1_21_2, 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), @@ -256,7 +270,7 @@ public static void init() throws Throwable { createMapping(0x5B, ProtocolVersion.MINECRAFT_1_21_2, 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), @@ -273,7 +287,7 @@ public static void init() throws Throwable { createMapping(0x2D, ProtocolVersion.MINECRAFT_1_21_2, 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), @@ -292,7 +306,7 @@ public static void init() throws Throwable { createMapping(0x3A, ProtocolVersion.MINECRAFT_1_21_2, 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), @@ -311,7 +325,7 @@ public static void init() throws Throwable { createMapping(0x42, ProtocolVersion.MINECRAFT_1_21_2, 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), @@ -329,7 +343,7 @@ public static void init() throws Throwable { createMapping(0x61, ProtocolVersion.MINECRAFT_1_21_2, 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), @@ -344,7 +358,7 @@ public static void init() throws Throwable { createMapping(0x15, ProtocolVersion.MINECRAFT_1_20_2, 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), @@ -364,7 +378,7 @@ public static void init() throws Throwable { createMapping(0x6B, ProtocolVersion.MINECRAFT_1_21_2, true) ); register(LIMBO_STATE_REGISTRY, PacketDirection.CLIENTBOUND, - UpdateViewPositionPacket.class, UpdateViewPositionPacket::new, // ViewCentre, ChunkRenderDistanceCenter + SetChunkCacheCenterPacket.class, null, // ViewCentre, ChunkRenderDistanceCenter createMapping(0x40, ProtocolVersion.MINECRAFT_1_14, true), createMapping(0x41, ProtocolVersion.MINECRAFT_1_15, true), createMapping(0x40, ProtocolVersion.MINECRAFT_1_16, true), @@ -379,7 +393,7 @@ public static void init() throws Throwable { createMapping(0x58, ProtocolVersion.MINECRAFT_1_21_2, 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), @@ -473,67 +487,71 @@ public static void init() throws Throwable { createMapping(0x1F, ProtocolVersion.MINECRAFT_1_21_2, 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) ); + + register(PLAY_CLIENTBOUND_REGISTRY, + SetEntityDataPacket.class, SetEntityDataPacket::new, + createMapping(0x1C, ProtocolVersion.MINECRAFT_1_7_2, false), + createMapping(0x39, ProtocolVersion.MINECRAFT_1_9, false), + createMapping(0x3B, ProtocolVersion.MINECRAFT_1_12, false), + createMapping(0x3C, ProtocolVersion.MINECRAFT_1_12_1, false), + createMapping(0x3F, ProtocolVersion.MINECRAFT_1_13, false), + createMapping(0x43, ProtocolVersion.MINECRAFT_1_14, false), + createMapping(0x44, ProtocolVersion.MINECRAFT_1_15, false), + createMapping(0x4D, ProtocolVersion.MINECRAFT_1_17, false), + createMapping(0x50, ProtocolVersion.MINECRAFT_1_19_1, false), + createMapping(0x4E, ProtocolVersion.MINECRAFT_1_19_3, false), + createMapping(0x52, ProtocolVersion.MINECRAFT_1_19_4, false), + createMapping(0x54, ProtocolVersion.MINECRAFT_1_20_2, false), + createMapping(0x56, ProtocolVersion.MINECRAFT_1_20_3, false), + createMapping(0x58, ProtocolVersion.MINECRAFT_1_20_5, false), + createMapping(0x5D, ProtocolVersion.MINECRAFT_1_21_2, false) + ); } 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 -> { @@ -548,16 +566,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); } } @@ -565,8 +582,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/data/BiomeStorage118.java b/plugin/src/main/java/net/elytrium/limboapi/protocol/data/BiomeStorage118.java index 051dd8b2..65cb8242 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 @@ -26,28 +26,25 @@ 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.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() { 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 9cf0facc..47bf7574 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 @@ -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 : 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 d47f88ad..52e82e05 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 @@ -30,12 +30,16 @@ import net.elytrium.limboapi.api.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/data/StorageUtils.java b/plugin/src/main/java/net/elytrium/limboapi/protocol/data/StorageUtils.java deleted file mode 100644 index e39defbb..00000000 --- a/plugin/src/main/java/net/elytrium/limboapi/protocol/data/StorageUtils.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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.data; - -import com.velocitypowered.api.network.ProtocolVersion; - -public class StorageUtils { - - 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; - } - } -} 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 82361702..558f1d8a 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.Collection; import java.util.List; import java.util.Map; +import net.elytrium.limboapi.api.chunk.BlockEntityVersion; import net.elytrium.limboapi.api.chunk.Dimension; +import net.elytrium.limboapi.api.chunk.VirtualBlockEntity; 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.PreparedPacket; import net.elytrium.limboapi.api.protocol.item.ItemComponentMap; import net.elytrium.limboapi.api.protocol.packets.PacketFactory; +import net.elytrium.limboapi.api.protocol.packets.data.EntityDataValue; import net.elytrium.limboapi.api.protocol.packets.data.MapData; -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.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 ? List.of() : 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 @@ -71,49 +121,78 @@ public Object createDefaultSpawnPositionPacket(int posX, int posY, int posZ, flo } @Override - public Object createMapDataPacket(int mapID, byte scale, MapData mapData) { - return new MapDataPacket(mapID, scale, mapData); + public Object createMapDataPacket(int mapId, byte scale, MapData mapData) { + return new MapDataPacket(mapId, scale, mapData); + } + + @Override + public Object createEntityDataPacket(int id, Collection> packedItems) { + return new SetEntityDataPacket(id, packedItems); + } + + @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 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 ItemComponentMap 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 ItemComponentMap 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 ItemComponentMap 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 +206,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 6157c428..bca4b252 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 expectedMinLength(ByteBuf buf, ProtocolUtils.Direction direction, Pro 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/PlayerChatSessionPacket.java b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/c2s/ChatSessionUpdatePacket.java similarity index 57% rename from plugin/src/main/java/net/elytrium/limboapi/protocol/packets/c2s/PlayerChatSessionPacket.java rename to plugin/src/main/java/net/elytrium/limboapi/protocol/packets/c2s/ChatSessionUpdatePacket.java index 7fe2e1c0..fb4561ca 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/c2s/PlayerChatSessionPacket.java +++ b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/c2s/ChatSessionUpdatePacket.java @@ -27,48 +27,42 @@ import java.util.UUID; import net.elytrium.limboapi.Settings; -@SuppressWarnings("unused") -public class PlayerChatSessionPacket implements MinecraftPacket { +public class ChatSessionUpdatePacket implements MinecraftPacket { - private UUID holderId; - private IdentifiedKey playerKey; + private UUID sessionId; + private IdentifiedKey profilePublicKey; @Override - public void decode(ByteBuf byteBuf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { - this.holderId = ProtocolUtils.readUuid(byteBuf); - this.playerKey = ProtocolUtils.readPlayerKey(protocolVersion, byteBuf); + 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 byteBuf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { - ProtocolUtils.writeUuid(byteBuf, this.holderId); - ProtocolUtils.writePlayerKey(byteBuf, this.playerKey); + 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 - if (minecraftSessionHandler instanceof ClientPlaySessionHandler) { - return Settings.IMP.MAIN.FORCE_DISABLE_MODERN_CHAT_SIGNING; - } - - return false; - } - - public UUID getHolderId() { - return this.holderId; + return minecraftSessionHandler instanceof ClientPlaySessionHandler && Settings.IMP.MAIN.FORCE_DISABLE_MODERN_CHAT_SIGNING; } - public void setHolderId(UUID holderId) { - this.holderId = holderId; + public UUID getSessionId() { + return this.sessionId; } - public IdentifiedKey getPlayerKey() { - return this.playerKey; + public IdentifiedKey getProfilePublicKey() { + return this.profilePublicKey; } - public void setPlayerKey(IdentifiedKey playerKey) { - this.playerKey = playerKey; + @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 40d6942e..64167146 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 @@ -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 expectedMinLength(ByteBuf buf, ProtocolUtils.Direction direction, Pro 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 cdac72dd..f223ea08 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,16 +35,15 @@ 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 { @@ -56,40 +54,23 @@ public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVer } @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 expectedMaxLength(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 expectedMinLength(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 Double.BYTES * 3 + Float.BYTES * 2 + 1; } 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 7249aed2..873ae16e 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,12 +35,11 @@ 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 { @@ -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 expectedMaxLength(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 expectedMinLength(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 Double.BYTES * 3 + 1; } 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 85a6bd3a..168d7fc9 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,7 +32,7 @@ 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(); @@ -47,37 +46,23 @@ public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVer } @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 expectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { - return 9; + return Float.BYTES * 2 + 1; } @Override public int expectedMinLength(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 Float.BYTES * 2 + 1; } public float getYaw() { @@ -95,4 +80,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/s2c/BlockEntityDataPacket.java b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/BlockEntityDataPacket.java new file mode 100644 index 00000000..a199e76d --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/BlockEntityDataPacket.java @@ -0,0 +1,35 @@ +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.chunk.VirtualBlockEntity; +import net.elytrium.limboapi.protocol.util.LimboProtocolUtils; + +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 { + LimboProtocolUtils.writeBlockPos(buf, protocolVersion, this.entry.getPosX(), this.entry.getPosY(), this.entry.getPosZ()); + } + ProtocolUtils.writeVarInt(buf, this.entry.getBlockEntity().getId(protocolVersion)); + LimboProtocolUtils.writeBinaryTag(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 6b0345f1..a2812ded 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,20 +17,24 @@ 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.ArrayList; +import java.util.Arrays; import java.util.List; -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.VirtualBiome; import net.elytrium.limboapi.api.chunk.VirtualBlock; import net.elytrium.limboapi.api.chunk.VirtualBlockEntity; +import net.elytrium.limboapi.api.chunk.data.BlockSection; import net.elytrium.limboapi.api.chunk.data.ChunkSnapshot; import net.elytrium.limboapi.api.chunk.data.LightSection; import net.elytrium.limboapi.api.chunk.util.CompactStorage; @@ -39,173 +43,210 @@ import net.elytrium.limboapi.material.Biome; import net.elytrium.limboapi.mcprotocollib.BitStorage116; import net.elytrium.limboapi.mcprotocollib.BitStorage19; +import net.elytrium.limboapi.protocol.util.LimboProtocolUtils; import net.elytrium.limboapi.protocol.util.NetworkSection; +import net.elytrium.limboapi.server.world.SimpleBlockEntity; import net.kyori.adventure.nbt.CompoundBinaryTag; +// 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 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, List.of()); + } + + 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.biomeData = new BiomeData(chunk); this.heightmap114 = this.createHeightMap(true); this.heightmap116 = this.createHeightMap(false); - this.biomeData = new BiomeData(this.chunk); - } - - public ChunkDataPacket() { - throw new IllegalStateException(); + 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); + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_14)) { + if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_16)) { + ProtocolUtils.writeBinaryTag(buf, protocolVersion, this.heightmap114); } else { - ProtocolUtils.writeBinaryTag(buf, version, this.heightmap116); + ProtocolUtils.writeBinaryTag(buf, protocolVersion, this.heightmap116); } } - // 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); + ProtocolUtils.writeBinaryTag(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) { @@ -213,60 +254,86 @@ 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) { - dataLength += (this.maxSections - this.nonNullSections) * 8; + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_18)) { + dataLength += (this.maxSections - this.nonNullSections) * (Short.BYTES + Byte.BYTES + 1 + 1 + Byte.BYTES + 1 + 1); } 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. - 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. - ProtocolUtils.writeVarInt(data, 0); // BiomeStorage: 0 entries. + 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 + 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 + 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); - + CompactStorage surface = pre116 ? 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); + } } } } @@ -277,39 +344,167 @@ private CompoundBinaryTag createHeightMap(boolean pre116) { .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 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 - 4); + 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 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 2cb069bc..8cb6e9fe 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(); 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 ff9fd778..72c32fd3 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.protocol.util.LimboProtocolUtils; -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(int posX, int posY, int posZ, float angle) implements MinecraftPacket { @Override public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { @@ -48,38 +33,14 @@ 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); - } + LimboProtocolUtils.writeBlockPos(buf, protocolVersion, this.posX, this.posY, this.posZ); + if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_17)) { + buf.writeFloat(this.angle); } } @Override public boolean handle(MinecraftSessionHandler handler) { - return true; - } - - @Override - public String toString() { - return "DefaultSpawnPosition{" - + "posX=" + this.posX - + ", posY=" + this.posY - + ", posZ=" + this.posZ - + ", angle=" + this.angle - + "}"; + throw new IllegalStateException(); } } 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 72% 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 366342c5..4b6ad2e5 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,12 @@ 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; - } - - @Override - public String toString() { - return "ChangeGameState{" - + "reason=" + this.reason - + ", value=" + this.value - + "}"; + throw new IllegalStateException(); } } 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..c75b1742 --- /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.chunk.data.ChunkSnapshot; +import net.elytrium.limboapi.api.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 295d4d0f..31699418 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 @@ -24,21 +24,7 @@ import io.netty.buffer.ByteBuf; import net.elytrium.limboapi.api.protocol.packets.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,6 @@ public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersi @Override public boolean handle(MinecraftSessionHandler handler) { - return true; - } - - @Override - public String toString() { - return "MapDataPacket{" - + "mapID=" + this.mapID - + ", scale=" + this.scale - + ", mapData=" + this.mapData - + "}"; + throw new IllegalStateException(); } } 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 8b51f053..39a6f1cd 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,13 @@ 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; - } - - @Override - public String toString() { - return "PlayerAbilities{" - + "flags=" + this.flags - + ", flySpeed=" + this.flySpeed - + ", walkSpeed=" + this.walkSpeed - + "}"; + throw new IllegalStateException(); } } 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..8a892e0b --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/PlayerPositionPacket.java @@ -0,0 +1,73 @@ +/* + * 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(); + } + + public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + final boolean v121 = protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_21_2); + final boolean v17 = !v121 && protocolVersion.noGreaterThan(ProtocolVersion.MINECRAFT_1_7_6); + + if (v121) { + ProtocolUtils.writeVarInt(buf, this.teleportId); + } + + buf.writeDouble(this.posX); + buf.writeDouble(v17 ? this.posY + 1.62F/*in 1.7.x posY means eyes position*/ : this.posY); + buf.writeDouble(this.posZ); + if (v121) { + // deltaMovement + buf.writeDouble(0); + buf.writeDouble(0); + buf.writeDouble(0); + } + buf.writeFloat(this.yaw); + buf.writeFloat(this.pitch); + + if (v121) { + buf.writeInt(0x00); // relatives + } else if (v17) { + 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(); + } +} 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 da343d3c..00000000 --- a/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/PositionRotationPacket.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * 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 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 77% 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 ae087659..85a36746 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,6 @@ public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersi @Override public boolean handle(MinecraftSessionHandler handler) { - return true; - } - - @Override - public String toString() { - return "UpdateViewPosition{" - + "posX=" + this.posX - + ", posZ=" + this.posZ - + "}"; + throw new IllegalStateException(); } } 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..5a11a3c5 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/SetEntityDataPacket.java @@ -0,0 +1,1060 @@ +/* + * 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 com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.DecoderException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.OptionalInt; +import net.elytrium.limboapi.LimboAPI; +import net.elytrium.limboapi.api.protocol.packets.data.BlockPos; +import net.elytrium.limboapi.api.protocol.packets.data.EntityDataValue; +import net.elytrium.limboapi.api.protocol.packets.data.GlobalPos; +import net.elytrium.limboapi.api.protocol.packets.data.ItemStack; +import net.elytrium.limboapi.protocol.util.LimboProtocolUtils; +import net.elytrium.limboapi.server.world.SimpleParticlesManager; +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.BinaryTagIO; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.nbt.EndBinaryTag; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; + +/** + * @see SetEntityDataPacket#writeOptionalUUID(ByteBuf, EntityDataValue.OptionalUUID) + */ +public class SetEntityDataPacket implements MinecraftPacket { // 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 Collection> packedItems; + + public SetEntityDataPacket(int id, Collection> packedItems) { // TODO Short2ObjectArrayMap + this.id = id; + this.packedItems = packedItems; + } + + public SetEntityDataPacket() { + + } + + @Override + public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) { + boolean v1_7_x = version.noGreaterThan(ProtocolVersion.MINECRAFT_1_7_6); + if (v1_7_x || version == ProtocolVersion.MINECRAFT_1_8) { + this.id = v1_7_x ? buf.readInt() : ProtocolUtils.readVarInt(buf); + byte index; + while ((index = buf.readByte()) != SetEntityDataPacket.EOF_MARKER_LEGACY) { + if (this.packedItems == null) { + this.packedItems = new ArrayList<>(); + } + + this.packedItems.add(SetEntityDataPacket.read(buf, version, index & 0b00011111, (index & 0b11100000) >> 5)); + } + //System.out.println(this.packedItems); + } else { + this.id = ProtocolUtils.readVarInt(buf); + short id; + while ((id = buf.readUnsignedByte()) != SetEntityDataPacket.EOF_MARKER) { + if (this.packedItems == null) { + this.packedItems = new ArrayList<>(); + } + + this.packedItems.add(SetEntityDataPacket.read(buf, version, id, ProtocolUtils.readVarInt(buf))); + } + } + } + + @Override + public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) { + 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.packedItems != null) { + this.packedItems.forEach(dataValue -> { + Object value = dataValue.value(); + buf.writeByte((SetEntityDataPacket.type(protocolVersion, value) << 5 | dataValue.id() & 0b00011111) & 0xFF); + SetEntityDataPacket.write(buf, protocolVersion, value); + }); + } + + buf.writeByte(SetEntityDataPacket.EOF_MARKER_LEGACY); + } else { + ProtocolUtils.writeVarInt(buf, this.id); + + if (this.packedItems != null) { + this.packedItems.forEach(dataValue -> { + buf.writeByte(dataValue.id()); + Object value = dataValue.value(); + int type = SetEntityDataPacket.type(protocolVersion, value); + //System.out.println("wrote " + dataValue.id() + " as " + type); + ProtocolUtils.writeVarInt(buf, type); + SetEntityDataPacket.write(buf, protocolVersion, value); + }); + } + + buf.writeByte(SetEntityDataPacket.EOF_MARKER); + } + } + + @Override + public boolean handle(MinecraftSessionHandler handler) { + return false; // forward it to the player + } + + private static int type(ProtocolVersion version, T value) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) { + // TODO switch when update to java 21 + return value instanceof Byte ? 0 + : value instanceof Short || value instanceof Integer ? 1 + : value instanceof Long ? 2 + : value instanceof Float ? 3 + : value instanceof String ? 4 + : value instanceof Component || value instanceof EntityDataValue.NBTComponent ? 5 + : value instanceof EntityDataValue.OptionalComponent || value instanceof EntityDataValue.OptionalNBTComponent ? 6 + : value instanceof ItemStack ? 7 + : value instanceof Boolean ? 8 + : value instanceof EntityDataValue.Rotations ? 9 + : value instanceof BlockPos ? 10 + : value instanceof EntityDataValue.OptionalBlockPos ? 11 + : value instanceof EntityDataValue.Direction ? 12 + : value instanceof EntityDataValue.OptionalUUID ? 13 + : value instanceof EntityDataValue.BlockState ? 14 + : value instanceof EntityDataValue.OptionalBlockState ? 15 + : (value instanceof CompoundBinaryTag || value instanceof EndBinaryTag) ? 16 + : value instanceof EntityDataValue.Particle ? 17 + : value instanceof EntityDataValue.Particles ? 18 + : value instanceof EntityDataValue.VillagerData ? 19 + : value instanceof OptionalInt ? 20 + : value instanceof EntityDataValue.Pose ? 21 + : value instanceof EntityDataValue.CatVariant ? 22 + : value instanceof EntityDataValue.WolfVariant ? 23 + : value instanceof EntityDataValue.FrogVariant ? 24 + : value instanceof GlobalPos || value instanceof EntityDataValue.OptionalGlobalPos ? 25 + : value instanceof EntityDataValue.PaintingVariant ? 26 + : value instanceof EntityDataValue.SnifferState ? 27 + : value instanceof EntityDataValue.ArmadilloState ? 28 + : value instanceof EntityDataValue.Vector3 ? 29 + : value instanceof EntityDataValue.Quaternion ? 30 + : SetEntityDataPacket.fail(value); + } else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19_4)) { + return value instanceof Byte ? 0 + : value instanceof Short || value instanceof Integer ? 1 + : value instanceof Long ? 2 + : value instanceof Float ? 3 + : value instanceof String ? 4 + : value instanceof Component || value instanceof EntityDataValue.NBTComponent ? 5 + : value instanceof EntityDataValue.OptionalComponent || value instanceof EntityDataValue.OptionalNBTComponent ? 6 + : value instanceof ItemStack ? 7 + : value instanceof Boolean ? 8 + : value instanceof EntityDataValue.Rotations ? 9 + : value instanceof BlockPos ? 10 + : value instanceof EntityDataValue.OptionalBlockPos ? 11 + : value instanceof EntityDataValue.Direction ? 12 + : value instanceof EntityDataValue.OptionalUUID ? 13 + : value instanceof EntityDataValue.BlockState ? 14 + : value instanceof EntityDataValue.OptionalBlockState ? 15 + : (value instanceof CompoundBinaryTag || value instanceof EndBinaryTag) ? 16 + : value instanceof EntityDataValue.Particle ? 17 + : value instanceof EntityDataValue.VillagerData ? 18 + : value instanceof OptionalInt ? 19 + : value instanceof EntityDataValue.Pose ? 20 + : value instanceof EntityDataValue.CatVariant ? 21 + : value instanceof EntityDataValue.FrogVariant ? 22 + : value instanceof GlobalPos || value instanceof EntityDataValue.OptionalGlobalPos ? 23 + : value instanceof EntityDataValue.PaintingVariant ? 24 + : value instanceof EntityDataValue.SnifferState ? 25 + : value instanceof EntityDataValue.Vector3 ? 26 + : value instanceof EntityDataValue.Quaternion ? 27 + : SetEntityDataPacket.fail(value); + } else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19_3)) { + return value instanceof Byte ? 0 + : value instanceof Short || value instanceof Integer ? 1 + : value instanceof Long ? 2 + : value instanceof Float ? 3 + : value instanceof String ? 4 + : value instanceof Component || value instanceof EntityDataValue.NBTComponent ? 5 + : value instanceof EntityDataValue.OptionalComponent || value instanceof EntityDataValue.OptionalNBTComponent ? 6 + : value instanceof ItemStack ? 7 + : value instanceof Boolean ? 8 + : value instanceof EntityDataValue.Rotations ? 9 + : value instanceof BlockPos ? 10 + : value instanceof EntityDataValue.OptionalBlockPos ? 11 + : value instanceof EntityDataValue.Direction ? 12 + : value instanceof EntityDataValue.OptionalUUID ? 13 + : (value instanceof EntityDataValue.BlockState || value instanceof EntityDataValue.OptionalBlockState) ? 14 + : (value instanceof CompoundBinaryTag || value instanceof EndBinaryTag) ? 15 + : value instanceof EntityDataValue.Particle ? 16 + : value instanceof EntityDataValue.VillagerData ? 17 + : value instanceof OptionalInt ? 18 + : value instanceof EntityDataValue.Pose ? 19 + : value instanceof EntityDataValue.CatVariant ? 20 + : value instanceof EntityDataValue.FrogVariant ? 21 + : value instanceof GlobalPos || value instanceof EntityDataValue.OptionalGlobalPos ? 22 + : value instanceof EntityDataValue.PaintingVariant ? 23 + : SetEntityDataPacket.fail(value); + } else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_14)) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) { + if (value instanceof EntityDataValue.CatVariant) { + return 19; + } else if (value instanceof EntityDataValue.FrogVariant) { + return 20; + } else if (value instanceof GlobalPos || value instanceof EntityDataValue.OptionalGlobalPos) { + return 21; + } else if (value instanceof EntityDataValue.PaintingVariant) { + return 22; + } + } + + return value instanceof Byte ? 0 + : value instanceof Short || value instanceof Integer || value instanceof Long ? 1 + : value instanceof Float ? 2 + : value instanceof String ? 3 + : value instanceof Component || value instanceof EntityDataValue.NBTComponent ? 4 + : value instanceof EntityDataValue.OptionalComponent || value instanceof EntityDataValue.OptionalNBTComponent ? 5 + : value instanceof ItemStack ? 6 + : value instanceof Boolean ? 7 + : value instanceof EntityDataValue.Rotations ? 8 + : value instanceof BlockPos ? 9 + : value instanceof EntityDataValue.OptionalBlockPos ? 10 + : value instanceof EntityDataValue.Direction ? 11 + : value instanceof EntityDataValue.OptionalUUID ? 12 + : (value instanceof EntityDataValue.BlockState || value instanceof EntityDataValue.OptionalBlockState) ? 13 + : (value instanceof CompoundBinaryTag || value instanceof EndBinaryTag) ? 14 + : value instanceof EntityDataValue.Particle ? 15 + : value instanceof EntityDataValue.VillagerData ? 16 + : value instanceof OptionalInt ? 17 + : value instanceof EntityDataValue.Pose ? 18 + : SetEntityDataPacket.fail(value); + } else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_13)) { + return value instanceof Byte ? 0 + : value instanceof Short || value instanceof Integer || value instanceof Long || value instanceof OptionalInt ? 1 + : value instanceof Float ? 2 + : value instanceof String ? 3 + : value instanceof Component || value instanceof EntityDataValue.NBTComponent ? 4 + : value instanceof EntityDataValue.OptionalComponent || value instanceof EntityDataValue.OptionalNBTComponent ? 5 + : value instanceof ItemStack ? 6 + : value instanceof Boolean ? 7 + : value instanceof EntityDataValue.Rotations ? 8 + : value instanceof BlockPos ? 9 + : value instanceof EntityDataValue.OptionalBlockPos ? 10 + : value instanceof EntityDataValue.Direction ? 11 + : value instanceof EntityDataValue.OptionalUUID ? 12 + : (value instanceof EntityDataValue.BlockState || value instanceof EntityDataValue.OptionalBlockState) ? 13 + : (value instanceof CompoundBinaryTag || value instanceof EndBinaryTag) ? 14 + : value instanceof EntityDataValue.Particle ? 15 + : SetEntityDataPacket.fail(value); + } else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_9)) { + return value instanceof Byte ? 0 + : value instanceof Short || value instanceof Integer || value instanceof Long || (value instanceof OptionalInt optional && optional.isPresent()) ? 1 + : value instanceof Float ? 2 + : value instanceof String ? 3 + : value instanceof Component || (value instanceof EntityDataValue.OptionalComponent optional && optional.component() != null) + || value instanceof EntityDataValue.NBTComponent || (value instanceof EntityDataValue.OptionalNBTComponent optionalHolder && optionalHolder.component() != null) ? 4 + : value instanceof ItemStack ? 5 + : value instanceof Boolean ? 6 + : value instanceof EntityDataValue.Rotations ? 7 + : value instanceof BlockPos ? 8 + : value instanceof EntityDataValue.OptionalBlockPos ? 9 + : value instanceof EntityDataValue.Direction ? 10 + : value instanceof EntityDataValue.OptionalUUID ? 11 + : (value instanceof EntityDataValue.BlockState || value instanceof EntityDataValue.OptionalBlockState) ? 12 + : (value instanceof CompoundBinaryTag || value instanceof EndBinaryTag) && version.noLessThan(ProtocolVersion.MINECRAFT_1_12) ? 13 + : SetEntityDataPacket.fail(value); + } else/* if (version.noLessThan(ProtocolVersion.MINECRAFT_1_7_2))*/ { + return value instanceof Byte || value instanceof Boolean ? 0 + : value instanceof Short ? 1 + : value instanceof Integer || value instanceof Long || (value instanceof OptionalInt optional && optional.isPresent()) ? 2 + : value instanceof Float ? 3 + : value instanceof String + || value instanceof Component || (value instanceof EntityDataValue.OptionalComponent optional && optional.component() != null) + || value instanceof EntityDataValue.NBTComponent || (value instanceof EntityDataValue.OptionalNBTComponent optionalHolder && optionalHolder.component() != null) ? 4 + : value instanceof ItemStack ? 5 + : value instanceof BlockPos || (value instanceof EntityDataValue.OptionalBlockPos optional && optional.blockPos() != null) ? 6 + : (value instanceof EntityDataValue.Rotations && version == ProtocolVersion.MINECRAFT_1_8) ? 7 + : SetEntityDataPacket.fail(value); + } + } + + private static void write(ByteBuf buf, ProtocolVersion version, T value) { + if (value instanceof Byte number) { + buf.writeByte(number); + } else if (value instanceof Short number) { + if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_8)) { + buf.writeShort(number); + } else { + ProtocolUtils.writeVarInt(buf, number); + } + } else if (value instanceof Integer number) { + if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_8)) { + buf.writeInt(number); + } else { + ProtocolUtils.writeVarInt(buf, number); + } + } else if (value instanceof Long number) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19_4)) { + SetEntityDataPacket.writeVarLong(buf, number); + } else { + ProtocolUtils.writeVarInt(buf, number.intValue()); + } + } else if (value instanceof Float number) { + buf.writeFloat(number); + } else if (value instanceof String string) { + ProtocolUtils.writeString(buf, string); + } else if (value instanceof Component component) { + SetEntityDataPacket.writeComponent(buf, version, component); + } else if (value instanceof EntityDataValue.NBTComponent nbtComponent) { + SetEntityDataPacket.writeNBTComponent(buf, version, nbtComponent); + } else if (value instanceof EntityDataValue.OptionalComponent optional) { + if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_12_2)) { + Component component = optional.component(); + if (component == null) { + SetEntityDataPacket.fail(value); + } + + SetEntityDataPacket.writeComponent(buf, version, component); + } else { + SetEntityDataPacket.writeOptionalComponent(buf, version, optional); + } + } else if (value instanceof EntityDataValue.OptionalNBTComponent optional) { + if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_12_2)) { + EntityDataValue.NBTComponent component = optional.component(); + if (component == null) { + SetEntityDataPacket.fail(value); + } + + SetEntityDataPacket.writeNBTComponent(buf, version, component); + } else { + SetEntityDataPacket.writeOptionalNBTComponent(buf, version, optional); + } + } else if (value instanceof ItemStack itemStack) { + LimboProtocolUtils.writeItemStack(buf, version, itemStack); + } else if (value instanceof Boolean bool) { + buf.writeBoolean(bool); + } else if (value instanceof EntityDataValue.Rotations rotations) { + SetEntityDataPacket.writeRotations(buf, rotations); + } else if (value instanceof BlockPos blockPos) { + LimboProtocolUtils.writeBlockPos(buf, version, blockPos); + } else if (value instanceof EntityDataValue.OptionalBlockPos optional) { + if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_8)) { + BlockPos blockPos = optional.blockPos(); + if (blockPos == null) { + SetEntityDataPacket.fail(value); + } + + LimboProtocolUtils.writeBlockPos(buf, version, blockPos); + } else { + SetEntityDataPacket.writeOptionalBlockPos(buf, version, optional); + } + } else if (value instanceof EntityDataValue.Direction direction) { + SetEntityDataPacket.writeDirection(buf, direction); + } else if (value instanceof EntityDataValue.OptionalUUID optional) { + SetEntityDataPacket.writeOptionalUUID(buf, optional); + } else if (value instanceof EntityDataValue.BlockState state) { + if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_19_3)) { + // Make it optional + buf.writeBoolean(true); + } + + SetEntityDataPacket.writeBlockState(buf, state); + } else if (value instanceof EntityDataValue.OptionalBlockState optional) { + SetEntityDataPacket.writeOptionalBlockState(buf, optional); + } else if (value instanceof BinaryTag binaryTag) { + LimboProtocolUtils.writeCompoundBinaryTag(buf, version, binaryTag); + } else if (value instanceof EntityDataValue.Particle particle) { + SetEntityDataPacket.writeParticle(buf, version, particle); + } else if (value instanceof EntityDataValue.Particles particles) { + SetEntityDataPacket.writeParticles(buf, version, particles); + } else if (value instanceof EntityDataValue.VillagerData villagerData) { + SetEntityDataPacket.writeVillagerData(buf, villagerData); + } else if (value instanceof OptionalInt optionalInt) { + SetEntityDataPacket.writeOptionalUnsignedInt(buf, optionalInt); + } else if (value instanceof EntityDataValue.Pose pose) { + SetEntityDataPacket.writePose(buf, version, pose); + } else if (value instanceof EntityDataValue.CatVariant catVariant) { + SetEntityDataPacket.writeCatVariant(buf, catVariant); + } else if (value instanceof EntityDataValue.WolfVariant wolfVariant) { + SetEntityDataPacket.writeWolfVariant(buf, wolfVariant); + } else if (value instanceof EntityDataValue.FrogVariant frogVariant) { + SetEntityDataPacket.writeFrogVariant(buf, frogVariant); + } else if (value instanceof GlobalPos globalPos) { + //if (version.noGreaterThan(ProtocolVersion.MINECRAFT_X_X_X)) { // Neither OptionalGlobalPos nor GlobalPos is not used for now + // Make it optional + buf.writeBoolean(true); + //} + + LimboProtocolUtils.writeGlobalPos(buf, version, globalPos); + } else if (value instanceof EntityDataValue.OptionalGlobalPos optionalGlobalPos) { + SetEntityDataPacket.writeOptionalGlobalPos(buf, version, optionalGlobalPos); + } else if (value instanceof EntityDataValue.PaintingVariant paintingVariant) { + SetEntityDataPacket.writePaintingVariant(buf, paintingVariant); + } else if (value instanceof EntityDataValue.SnifferState snifferState) { + SetEntityDataPacket.writeSnifferState(buf, snifferState); + } else if (value instanceof EntityDataValue.ArmadilloState armadilloState) { + SetEntityDataPacket.writeArmadilloState(buf, armadilloState); + } else if (value instanceof EntityDataValue.Vector3 vector3) { + SetEntityDataPacket.writeVector3(buf, vector3); + } else if (value instanceof EntityDataValue.Quaternion quaternion) { + SetEntityDataPacket.writeQuaternion(buf, quaternion); + } else { + SetEntityDataPacket.fail(value); + } + } + + private static EntityDataValue read(ByteBuf buf, ProtocolVersion version, int id, int type) { + //System.out.println("read " + id + " as " + type); + return new EntityDataValue<>(id, version.noLessThan(ProtocolVersion.MINECRAFT_1_19_3) ? switch (type) { + // >=1.19.3 + case 0 -> buf.readByte(); + case 1 -> ProtocolUtils.readVarInt(buf); + case 2 -> SetEntityDataPacket.readVarLong(buf); + case 3 -> buf.readFloat(); + case 4 -> ProtocolUtils.readString(buf, Short.MAX_VALUE); + case 5 -> version.noLessThan(ProtocolVersion.MINECRAFT_1_20_3) ? SetEntityDataPacket.readNBTComponent(buf, version) : SetEntityDataPacket.readComponent(buf, version); + case 6 -> version.noLessThan(ProtocolVersion.MINECRAFT_1_20_3) ? SetEntityDataPacket.readOptionalNBTComponent(buf, version) : SetEntityDataPacket.readOptionalComponent(buf, version); + case 7 -> LimboProtocolUtils.readItemStack(buf, version); + case 8 -> buf.readBoolean(); + case 9 -> SetEntityDataPacket.readRotations(buf); + case 10 -> LimboProtocolUtils.readBlockPos(buf, version); + case 11 -> SetEntityDataPacket.readOptionalBlockPos(buf, version); + case 12 -> SetEntityDataPacket.readDirection(buf); + case 13 -> SetEntityDataPacket.readOptionalUUID(buf); + default -> version.noLessThan(ProtocolVersion.MINECRAFT_1_19_4) ? switch (type) { + // >=1.19.4 + case 14 -> SetEntityDataPacket.readBlockState(buf); + case 15 -> SetEntityDataPacket.readOptionalBlockState(buf); + case 16 -> LimboProtocolUtils.readCompoundTag(buf, version); + case 17 -> SetEntityDataPacket.readParticle(buf, version); + default -> version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5) ? switch (type) { + // >=1.20.5 + case 18 -> SetEntityDataPacket.readParticles(buf, version); + case 19 -> SetEntityDataPacket.readVillagerData(buf); + case 20 -> SetEntityDataPacket.readOptionalUnsignedInt(buf); + case 21 -> SetEntityDataPacket.readPose(buf, version); + case 22 -> SetEntityDataPacket.readCatVariant(buf); + case 23 -> SetEntityDataPacket.readWolfVariant(buf); + case 24 -> SetEntityDataPacket.readFrogVariant(buf); + case 25 -> SetEntityDataPacket.readOptionalGlobalPos(buf, version); + case 26 -> SetEntityDataPacket.readPaintingVariant(buf); + case 27 -> SetEntityDataPacket.readSnifferState(buf); + case 28 -> SetEntityDataPacket.readArmadilloState(buf); + case 29 -> SetEntityDataPacket.readVector3(buf); + case 30 -> SetEntityDataPacket.readQuaternion(buf); + default -> SetEntityDataPacket.fail(type); + } : switch (type) { + // >=1.19.4 + case 18 -> SetEntityDataPacket.readVillagerData(buf); + case 19 -> SetEntityDataPacket.readOptionalUnsignedInt(buf); + case 20 -> SetEntityDataPacket.readPose(buf, version); + case 21 -> SetEntityDataPacket.readCatVariant(buf); + case 22 -> SetEntityDataPacket.readFrogVariant(buf); + case 23 -> SetEntityDataPacket.readOptionalGlobalPos(buf, version); + case 24 -> SetEntityDataPacket.readPaintingVariant(buf); + case 25 -> SetEntityDataPacket.readSnifferState(buf); + case 26 -> SetEntityDataPacket.readVector3(buf); + case 27 -> SetEntityDataPacket.readQuaternion(buf); + default -> SetEntityDataPacket.fail(type); + }; + } : switch (type) { + // >=1.19.3 + case 14 -> SetEntityDataPacket.readOptionalBlockState(buf); + case 15 -> LimboProtocolUtils.readCompoundTag(buf, version); + case 16 -> SetEntityDataPacket.readParticle(buf, version); + case 17 -> SetEntityDataPacket.readVillagerData(buf); + case 18 -> SetEntityDataPacket.readOptionalUnsignedInt(buf); + case 19 -> SetEntityDataPacket.readPose(buf, version); + case 20 -> SetEntityDataPacket.readCatVariant(buf); + case 21 -> SetEntityDataPacket.readFrogVariant(buf); + case 22 -> SetEntityDataPacket.readOptionalGlobalPos(buf, version); + case 23 -> SetEntityDataPacket.readPaintingVariant(buf); + default -> SetEntityDataPacket.fail(type); + }; + } : version.noLessThan(ProtocolVersion.MINECRAFT_1_9) ? switch (type) { + case 0 -> buf.readByte(); + case 1 -> ProtocolUtils.readVarInt(buf); + case 2 -> buf.readFloat(); + case 3 -> ProtocolUtils.readString(buf, Short.MAX_VALUE); + case 4 -> SetEntityDataPacket.readComponent(buf, version); + default -> version.noLessThan(ProtocolVersion.MINECRAFT_1_13) ? switch (type) { + // >=1.13 + case 5 -> SetEntityDataPacket.readOptionalComponent(buf, version); + case 6 -> LimboProtocolUtils.readItemStack(buf, version); + case 7 -> buf.readBoolean(); + case 8 -> SetEntityDataPacket.readRotations(buf); + case 9 -> LimboProtocolUtils.readBlockPos(buf, version); + case 10 -> SetEntityDataPacket.readOptionalBlockPos(buf, version); + case 11 -> SetEntityDataPacket.readDirection(buf); + case 12 -> SetEntityDataPacket.readOptionalUUID(buf); + case 13 -> SetEntityDataPacket.readOptionalBlockState(buf); + case 14 -> LimboProtocolUtils.readCompoundTag(buf, version); + case 15 -> SetEntityDataPacket.readParticle(buf, version); + default -> version.noLessThan(ProtocolVersion.MINECRAFT_1_14) ? switch (type) { + // >=1.14 + case 16 -> SetEntityDataPacket.readVillagerData(buf); + case 17 -> SetEntityDataPacket.readOptionalUnsignedInt(buf); + case 18 -> SetEntityDataPacket.readPose(buf, version); + default -> version.noLessThan(ProtocolVersion.MINECRAFT_1_19) ? switch (type) { + // >=1.19 + case 19 -> SetEntityDataPacket.readCatVariant(buf); + case 20 -> SetEntityDataPacket.readFrogVariant(buf); + case 21 -> SetEntityDataPacket.readOptionalGlobalPos(buf, version); + case 22 -> SetEntityDataPacket.readPaintingVariant(buf); + default -> SetEntityDataPacket.fail(type); + } : SetEntityDataPacket.fail(type); + } : SetEntityDataPacket.fail(type); + } : switch (type) { + // >=1.9 + case 5 -> LimboProtocolUtils.readItemStack(buf, version); + case 6 -> buf.readBoolean(); + case 7 -> SetEntityDataPacket.readRotations(buf); + case 8 -> LimboProtocolUtils.readBlockPos(buf, version); + case 9 -> SetEntityDataPacket.readOptionalBlockPos(buf, version); + case 10 -> SetEntityDataPacket.readDirection(buf); + case 11 -> SetEntityDataPacket.readOptionalUUID(buf); + case 12 -> SetEntityDataPacket.readOptionalBlockState(buf); + default -> type == 13 && version.noLessThan(ProtocolVersion.MINECRAFT_1_12) ? LimboProtocolUtils.readCompoundTag(buf, version) : SetEntityDataPacket.fail(type); + }; + } : switch (type) { + // [1.7.2 - 1.8.9] + case 0 -> buf.readByte(); + case 1 -> buf.readShort(); + case 2 -> buf.readInt(); + case 3 -> buf.readFloat(); + case 4 -> ProtocolUtils.readString(buf, Short.MAX_VALUE); + case 5 -> LimboProtocolUtils.readItemStack(buf, version); + case 6 -> LimboProtocolUtils.readBlockPos(buf, version); + default -> type == 7 && version == ProtocolVersion.MINECRAFT_1_8 ? SetEntityDataPacket.readRotations(buf) : SetEntityDataPacket.fail(type); + }); + } + + private static int fail(Object value) { + throw new DecoderException("Unexpected value: " + value); + } + + + // VarLong - START + private static Long readVarLong(ByteBuf buf) { + byte readByte; + long value = 0; + int i = 0; + do { + readByte = buf.readByte(); + value |= (long) (readByte & 0x7F) << i; + i += 7; + if (i > 10 * 7) { + throw new DecoderException("Varlong too big"); + } + } while ((readByte & 0x80) == 0x80); + return value; + } + + private static void writeVarLong(ByteBuf buf, long value) { + while ((value & ~0x7F) != 0) { + buf.writeByte(((int) value & 0x7F) | 0x80); + value >>>= 7; + } + + buf.writeByte((int) value); + } + // VarLong - START + + + // NBTComponent - START + private static EntityDataValue.NBTComponent readNBTComponent(ByteBuf buf, ProtocolVersion version) { + return new EntityDataValue.NBTComponent(ProtocolUtils.readBinaryTag(buf, version, BinaryTagIO.reader())); + } + + private static void writeNBTComponent(ByteBuf buf, ProtocolVersion version, EntityDataValue.NBTComponent value) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) { + ProtocolUtils.writeBinaryTag(buf, version, value.component()); + } else { + GsonComponentSerializer serializer = ProtocolUtils.getJsonChatSerializer(version); + ProtocolUtils.writeString(buf, serializer.serialize(serializer.deserializeFromTree(ComponentHolder.deserialize(value.component())))); + } + } + // NBTComponent - END + + + // Component - START + private static Component readComponent(ByteBuf buf, ProtocolVersion version) { + return ProtocolUtils.getJsonChatSerializer(version).deserialize(ProtocolUtils.readString(buf, 1 << 18)); + } + + private static void writeComponent(ByteBuf buf, ProtocolVersion version, Component value) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) { + ProtocolUtils.writeBinaryTag(buf, version, ComponentHolder.serialize(GsonComponentSerializer.gson().serializeToTree(value))); + } else { + ProtocolUtils.writeString(buf, ProtocolUtils.getJsonChatSerializer(version).serialize(value)); + } + } + // Component - END + + + // OptionalNBTComponent - START + private static EntityDataValue.OptionalNBTComponent readOptionalNBTComponent(ByteBuf buf, ProtocolVersion version) { + return LimboProtocolUtils.readOptional(buf, EntityDataValue.OptionalNBTComponent.EMPTY, () -> new EntityDataValue.OptionalNBTComponent(SetEntityDataPacket.readNBTComponent(buf, version))); + } + + private static void writeOptionalNBTComponent(ByteBuf buf, ProtocolVersion version, EntityDataValue.OptionalNBTComponent optional) { + LimboProtocolUtils.writeOptional(buf, optional.component(), value -> SetEntityDataPacket.writeNBTComponent(buf, version, value)); + } + // OptionalNBTComponent - END + + + // OptionalComponent - START + private static EntityDataValue.OptionalComponent readOptionalComponent(ByteBuf buf, ProtocolVersion version) { + return LimboProtocolUtils.readOptional(buf, EntityDataValue.OptionalComponent.EMPTY, () -> new EntityDataValue.OptionalComponent(SetEntityDataPacket.readComponent(buf, version))); + } + + private static void writeOptionalComponent(ByteBuf buf, ProtocolVersion version, EntityDataValue.OptionalComponent optional) { + LimboProtocolUtils.writeOptional(buf, optional.component(), value -> SetEntityDataPacket.writeComponent(buf, version, value)); + } + // OptionalComponent - END + + + // Rotations - START + private static EntityDataValue.Rotations readRotations(ByteBuf buf) { + return new EntityDataValue.Rotations(buf.readFloat(), buf.readFloat(), buf.readFloat()); + } + + private static void writeRotations(ByteBuf buf, EntityDataValue.Rotations value) { + buf.writeFloat(value.posX()); + buf.writeFloat(value.posY()); + buf.writeFloat(value.posZ()); + } + // Rotations - END + + + // OptionalBlockPos - START + private static EntityDataValue.OptionalBlockPos readOptionalBlockPos(ByteBuf buf, ProtocolVersion version) { + return LimboProtocolUtils.readOptional(buf, EntityDataValue.OptionalBlockPos.EMPTY, () -> new EntityDataValue.OptionalBlockPos(LimboProtocolUtils.readBlockPos(buf, version))); + } + + private static void writeOptionalBlockPos(ByteBuf buf, ProtocolVersion version, EntityDataValue.OptionalBlockPos optional) { + LimboProtocolUtils.writeOptional(buf, optional.blockPos(), value -> LimboProtocolUtils.writeBlockPos(buf, version, value)); + } + // OptionalBlockPos - END + + + // Direction - START + private static EntityDataValue.Direction readDirection(ByteBuf buf) { + return EntityDataValue.Direction.VALUES[ProtocolUtils.readVarInt(buf)]; + } + + private static void writeDirection(ByteBuf buf, EntityDataValue.Direction value) { + ProtocolUtils.writeVarInt(buf, value.ordinal()); + } + // Direction - END + + + // OptionalUUID - START + private static EntityDataValue.OptionalUUID readOptionalUUID(ByteBuf buf) { + return LimboProtocolUtils.readOptional(buf, EntityDataValue.OptionalUUID.EMPTY, () -> new EntityDataValue.OptionalUUID(ProtocolUtils.readUuid(buf))); + } + + private static void writeOptionalUUID(ByteBuf buf, EntityDataValue.OptionalUUID optional) { + // See #138 (Server here send uuid that limboauth or another plugin may change) + LimboProtocolUtils.writeOptional(buf, optional.uuid(), value -> ProtocolUtils.writeUuid(buf, LimboAPI.getClientUniqueId(value))); + } + // OptionalUUID - END + + + // BlockState - START + private static EntityDataValue.BlockState readBlockState(ByteBuf buf) { + return new EntityDataValue.BlockState(ProtocolUtils.readVarInt(buf)); + } + + private static void writeBlockState(ByteBuf buf, EntityDataValue.BlockState value) { + ProtocolUtils.writeVarInt(buf, value.blockState()); + } + // BlockState - END + + + // OptionalBlockState - START + private static EntityDataValue.OptionalBlockState readOptionalBlockState(ByteBuf buf) { + int blockState = ProtocolUtils.readVarInt(buf); + return blockState == 0 ? EntityDataValue.OptionalBlockState.EMPTY : new EntityDataValue.OptionalBlockState(new EntityDataValue.BlockState(blockState)); + } + + private static void writeOptionalBlockState(ByteBuf buf, EntityDataValue.OptionalBlockState optional) { + EntityDataValue.BlockState value = optional.blockState(); + if (value == null) { + ProtocolUtils.writeVarInt(buf, 0); + } else { + SetEntityDataPacket.writeBlockState(buf, value); + } + } + // OptionalBlockState - END + + + // Particle - START + private static EntityDataValue.Particle readParticle(ByteBuf buf, ProtocolVersion version) { + int type = ProtocolUtils.readVarInt(buf); + return new EntityDataValue.Particle(type, switch (SimpleParticlesManager.fromProtocolId(version, type)) { + case BLOCK, BLOCK_MARKER, FALLING_DUST, DUST_PILLAR, BLOCK_CRUMBLE -> SetEntityDataPacket.readBlockState(buf); + case DUST -> SetEntityDataPacket.readDustParticles(buf); + case DUST_COLOR_TRANSITION -> SetEntityDataPacket.readDustColorTransition(buf, version); + case ENTITY_EFFECT -> version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5) ? SetEntityDataPacket.readColor(buf) : null; + case SCULK_CHARGE -> SetEntityDataPacket.readSculkCharge(buf); + case ITEM -> LimboProtocolUtils.readItemStack(buf, version); + case VIBRATION -> SetEntityDataPacket.readVibration(buf, version); + case TRAIL -> SetEntityDataPacket.readTargetColor(buf); + case SHRIEK -> SetEntityDataPacket.readShriek(buf); + default -> null; + }); + } + + private static EntityDataValue.Particle.DustParticleData readDustParticles(ByteBuf buf) { + return new EntityDataValue.Particle.DustParticleData(buf.readFloat(), buf.readFloat(), buf.readFloat(), buf.readFloat()); + } + + private static EntityDataValue.Particle.DustColorTransitionParticleData readDustColorTransition(ByteBuf buf, ProtocolVersion version) { + 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 EntityDataValue.Particle.DustColorTransitionParticleData(fromRed, fromGreen, fromBlue, toRed, toGreen, toBlue, scale); + } + + private static EntityDataValue.Particle.ColorParticleData readColor(ByteBuf buf) { + return new EntityDataValue.Particle.ColorParticleData(buf.readInt()); + } + + private static EntityDataValue.Particle.SculkChargeParticleData readSculkCharge(ByteBuf buf) { + return new EntityDataValue.Particle.SculkChargeParticleData(buf.readFloat()); + } + + private static EntityDataValue.Particle.VibrationParticle readVibration(ByteBuf buf, ProtocolVersion version) { + BlockPos origin = version.noGreaterThan(ProtocolVersion.MINECRAFT_1_18_2) ? LimboProtocolUtils.readBlockPos(buf, version) : null; + EntityDataValue.Particle.VibrationParticle.PositionSource source; + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) { + int type = ProtocolUtils.readVarInt(buf); + source = switch (type) { + case 0 -> LimboProtocolUtils.readBlockPos(buf, version); + case 1 -> SetEntityDataPacket.readEntityPositionSource(buf); + default -> throw new IllegalStateException("Unexpected value: " + type); + }; + } else { + String type = ProtocolUtils.readString(buf); + source = switch (type) { + case "minecraft:block" -> LimboProtocolUtils.readBlockPos(buf, version); + case "minecraft:entity" -> SetEntityDataPacket.readEntityPositionSource(buf); + default -> throw new IllegalStateException("Unexpected value: " + type); + }; + } + + return new EntityDataValue.Particle.VibrationParticle(origin, source, ProtocolUtils.readVarInt(buf)); + } + + private static EntityDataValue.Particle.VibrationParticle.EntityPositionSource readEntityPositionSource(ByteBuf buf) { + return new EntityDataValue.Particle.VibrationParticle.EntityPositionSource(ProtocolUtils.readVarInt(buf), buf.readFloat()); + } + + private static EntityDataValue.Particle.TargetColorParticle readTargetColor(ByteBuf buf) { + return new EntityDataValue.Particle.TargetColorParticle(SetEntityDataPacket.readVector3(buf), buf.readInt()); + } + + private static EntityDataValue.Particle.ShriekParticle readShriek(ByteBuf buf) { + return new EntityDataValue.Particle.ShriekParticle(ProtocolUtils.readVarInt(buf)); + } + + private static void writeParticle(ByteBuf buf, ProtocolVersion version, EntityDataValue.Particle value) { + ProtocolUtils.writeVarInt(buf, value.type()); + EntityDataValue.Particle.ParticleData data = value.data(); + if (data instanceof EntityDataValue.BlockState state) { + ProtocolUtils.writeVarInt(buf, state.blockState()); + } else if (data instanceof EntityDataValue.Particle.ColorParticleData color) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) { + buf.writeInt(color.color()); + } else { + SetEntityDataPacket.failParticle(version, data); + } + } else if (data instanceof EntityDataValue.Particle.DustColorTransitionParticleData transition) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_17)) { + buf.writeFloat(transition.fromRed()); + buf.writeFloat(transition.fromGreen()); + buf.writeFloat(transition.fromBlue()); + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) { + buf.writeFloat(transition.toRed()); + buf.writeFloat(transition.toGreen()); + buf.writeFloat(transition.toBlue()); + buf.writeFloat(transition.scale()); + } else { + buf.writeFloat(transition.scale()); + buf.writeFloat(transition.toRed()); + buf.writeFloat(transition.toGreen()); + buf.writeFloat(transition.toBlue()); + } + } else { + SetEntityDataPacket.failParticle(version, data); + } + } else if (data instanceof EntityDataValue.Particle.DustParticleData dust) { + buf.writeFloat(dust.red()); + buf.writeFloat(dust.green()); + buf.writeFloat(dust.blue()); + buf.writeFloat(dust.scale()); + } else if (data instanceof ItemStack itemStack) { + LimboProtocolUtils.writeItemStack(buf, version, itemStack); + } else if (data instanceof EntityDataValue.Particle.SculkChargeParticleData charge) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) { + buf.writeFloat(charge.roll()); + } else { + SetEntityDataPacket.failParticle(version, data); + } + } else if (data instanceof EntityDataValue.Particle.VibrationParticle vibration) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_17)) { + if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_18_2)) { + BlockPos origin = vibration.origin(); + if (origin == null) { + throw new NullPointerException("origin"); + } + + LimboProtocolUtils.writeBlockPos(buf, version, origin); + } + + if (vibration.destination() instanceof BlockPos blockPos) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) { + ProtocolUtils.writeVarInt(buf, 0); + } else { + ProtocolUtils.writeString(buf, "minecraft:block"); + } + + LimboProtocolUtils.writeBlockPos(buf, version, blockPos); + } else if (vibration.destination() instanceof EntityDataValue.Particle.VibrationParticle.EntityPositionSource entity) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) { + ProtocolUtils.writeVarInt(buf, 1); + } else { + ProtocolUtils.writeString(buf, "minecraft:entity"); + } + + ProtocolUtils.writeVarInt(buf, entity.entityId()); + buf.writeFloat(entity.yOffset()); + } + + ProtocolUtils.writeVarInt(buf, vibration.arrivalInTicks()); + } else { + SetEntityDataPacket.failParticle(version, data); + } + } else if (data instanceof EntityDataValue.Particle.TargetColorParticle targetColor) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) { + SetEntityDataPacket.writeVector3(buf, targetColor.target()); + buf.writeInt(targetColor.color()); + } else { + SetEntityDataPacket.failParticle(version, data); + } + } else if (data instanceof EntityDataValue.Particle.ShriekParticle shriek) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) { + ProtocolUtils.writeVarInt(buf, shriek.delay()); + } else { + SetEntityDataPacket.failParticle(version, data); + } + } + } + + private static void failParticle(ProtocolVersion version, EntityDataValue.Particle.ParticleData data) { + throw new DecoderException("Don't know how to encode " + data + " for " + version); + } + // Particle - END + + + // Particles - START + private static EntityDataValue.Particles readParticles(ByteBuf buf, ProtocolVersion version) { + int amount = ProtocolUtils.readVarInt(buf); + EntityDataValue.Particles particles = new EntityDataValue.Particles(amount); + for (int i = 0; i < amount; ++i) { + particles.add(SetEntityDataPacket.readParticle(buf, version)); + } + + return particles; + } + + private static void writeParticles(ByteBuf buf, ProtocolVersion version, EntityDataValue.Particles values) { + ProtocolUtils.writeVarInt(buf, values.size()); + values.forEach(value -> SetEntityDataPacket.writeParticle(buf, version, value)); + } + // Particles - END + + + // VillagerData - START + private static EntityDataValue.VillagerData readVillagerData(ByteBuf buf) { + return new EntityDataValue.VillagerData(ProtocolUtils.readVarInt(buf), ProtocolUtils.readVarInt(buf), ProtocolUtils.readVarInt(buf)); + } + + private static void writeVillagerData(ByteBuf buf, EntityDataValue.VillagerData value) { + ProtocolUtils.writeVarInt(buf, value.type()); + ProtocolUtils.writeVarInt(buf, value.profession()); + ProtocolUtils.writeVarInt(buf, value.level()); + } + // VillagerData - END + + + // OptionalUnsignedInt - START + private static OptionalInt readOptionalUnsignedInt(ByteBuf buf) { + int result = ProtocolUtils.readVarInt(buf); + return result == 0 ? OptionalInt.empty() : OptionalInt.of(result - 1); + } + + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + private static void writeOptionalUnsignedInt(ByteBuf buf, OptionalInt value) { + ProtocolUtils.writeVarInt(buf, value.orElse(-1) + 1); + } + // OptionalUnsignedInt - END + + + // Pose - START + private static EntityDataValue.Pose readPose(ByteBuf buf, ProtocolVersion version) { + return EntityDataValue.Pose.fromProtocolId(ProtocolUtils.readVarInt(buf), version); + } + + private static void writePose(ByteBuf buf, ProtocolVersion version, EntityDataValue.Pose value) { + ProtocolUtils.writeVarInt(buf, value.getProtocolId(version)); + } + // Pose - END + + + // CatVariant - START + private static EntityDataValue.CatVariant readCatVariant(ByteBuf buf) { + return new EntityDataValue.CatVariant(ProtocolUtils.readVarInt(buf)); + } + + private static void writeCatVariant(ByteBuf buf, EntityDataValue.CatVariant value) { + ProtocolUtils.writeVarInt(buf, value.id()); + } + // CatVariant - END + + + // WolfVariant - START + private static EntityDataValue.WolfVariant readWolfVariant(ByteBuf buf) { + return new EntityDataValue.WolfVariant(ProtocolUtils.readVarInt(buf)); + } + + private static void writeWolfVariant(ByteBuf buf, EntityDataValue.WolfVariant value) { + ProtocolUtils.writeVarInt(buf, value.id()); + } + // WolfVariant - END + + + // FrogVariant - START + private static EntityDataValue.FrogVariant readFrogVariant(ByteBuf buf) { + return new EntityDataValue.FrogVariant(ProtocolUtils.readVarInt(buf)); + } + + private static void writeFrogVariant(ByteBuf buf, EntityDataValue.FrogVariant value) { + ProtocolUtils.writeVarInt(buf, value.id()); + } + // FrogVariant - END + + + // OptionalGlobalPos - START + private static EntityDataValue.OptionalGlobalPos readOptionalGlobalPos(ByteBuf buf, ProtocolVersion version) { + return LimboProtocolUtils.readOptional(buf, EntityDataValue.OptionalGlobalPos.EMPTY, () -> new EntityDataValue.OptionalGlobalPos(LimboProtocolUtils.readGlobalPos(buf, version))); + } + + private static void writeOptionalGlobalPos(ByteBuf buf, ProtocolVersion version, EntityDataValue.OptionalGlobalPos optional) { + LimboProtocolUtils.writeOptional(buf, optional.globalPos(), value -> LimboProtocolUtils.writeGlobalPos(buf, version, value)); + } + // OptionalGlobalPos - END + + + // PaintingVariant - START + private static EntityDataValue.PaintingVariant readPaintingVariant(ByteBuf buf) { + return new EntityDataValue.PaintingVariant(ProtocolUtils.readVarInt(buf)); + } + + private static void writePaintingVariant(ByteBuf buf, EntityDataValue.PaintingVariant value) { + ProtocolUtils.writeVarInt(buf, value.id()); + } + // PaintingVariant - END + + + // SnifferState - START + private static EntityDataValue.SnifferState readSnifferState(ByteBuf buf) { + return EntityDataValue.SnifferState.VALUES[ProtocolUtils.readVarInt(buf)]; + } + + private static void writeSnifferState(ByteBuf buf, EntityDataValue.SnifferState value) { + ProtocolUtils.writeVarInt(buf, value.ordinal()); + } + // SnifferState - END + + + // ArmadilloState - START + private static EntityDataValue.ArmadilloState readArmadilloState(ByteBuf buf) { + return EntityDataValue.ArmadilloState.VALUES[ProtocolUtils.readVarInt(buf)]; + } + + private static void writeArmadilloState(ByteBuf buf, EntityDataValue.ArmadilloState value) { + ProtocolUtils.writeVarInt(buf, value.ordinal()); + } + // ArmadilloState - END + + + // Vector3 - START + private static EntityDataValue.Vector3 readVector3(ByteBuf buf) { + return new EntityDataValue.Vector3(buf.readFloat(), buf.readFloat(), buf.readFloat()); + } + + private static void writeVector3(ByteBuf buf, EntityDataValue.Vector3 value) { + buf.writeFloat(value.x()); + buf.writeFloat(value.y()); + buf.writeFloat(value.z()); + } + // Vector3 - END + + + // Quaternion - START + private static EntityDataValue.Quaternion readQuaternion(ByteBuf buf) { + return new EntityDataValue.Quaternion(buf.readFloat(), buf.readFloat(), buf.readFloat(), buf.readFloat()); + } + + private static void writeQuaternion(ByteBuf buf, EntityDataValue.Quaternion value) { + buf.writeFloat(value.x()); + buf.writeFloat(value.y()); + buf.writeFloat(value.z()); + buf.writeFloat(value.w()); + } + // Quaternion - END +} 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 db9d0251..9241fb57 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,18 @@ 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; - } - - @Override - public String toString() { - return "SetExperience{" - + "expBar=" + this.expBar - + ", level=" + this.level - + ", totalExp=" + this.totalExp - + "}"; + throw new IllegalStateException(); } } 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 ab338a5b..99cbff64 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 @@ -24,127 +24,88 @@ 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.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 ItemComponentMap 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 ItemComponentMap 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 ItemComponentMap 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) { + LimboProtocolUtils.writeContainerId(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); + } + + LimboProtocolUtils.writeBinaryTag(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 15d08b98..25b6c03c 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,15 @@ 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; - } - - @Override - public String toString() { - return "TimeUpdatePacket{" - + "worldAge=" + this.worldAge - + ", timeOfDay=" + this.timeOfDay - + "}"; + throw new IllegalStateException(); } } 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..ae13bb49 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/protocol/packets/s2c/UpdateSignPacket.java @@ -0,0 +1,103 @@ +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.chunk.VirtualBlockEntity; +import net.elytrium.limboapi.protocol.util.LimboProtocolUtils; +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]; + 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 { + LimboProtocolUtils.writeBlockPos(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 05fdcc8c..96421491 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..9f61fdc3 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/protocol/util/LimboProtocolUtils.java @@ -0,0 +1,723 @@ +/* + * 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.api.network.ProtocolVersion; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufInputStream; +import io.netty.handler.codec.DecoderException; +import io.netty.handler.codec.EncoderException; +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.IntFunction; +import java.util.function.Supplier; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; +import net.elytrium.limboapi.api.protocol.item.ItemComponentMap; +import net.elytrium.limboapi.api.protocol.packets.data.BlockPos; +import net.elytrium.limboapi.api.protocol.packets.data.GlobalPos; +import net.elytrium.limboapi.api.protocol.packets.data.ItemStack; +import net.elytrium.limboapi.server.item.SimpleItemComponentMap; +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.nbt.BinaryTagType; +import net.kyori.adventure.nbt.BinaryTagTypes; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import net.kyori.adventure.nbt.EndBinaryTag; +import org.checkerframework.checker.nullness.qual.Nullable; + +public class LimboProtocolUtils { + + //public static final BinaryTagIO.Reader READER = BinaryTagIO.reader(1 << 21); + + 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); + } + } + + public static T readEither(ByteBuf buf, Supplier left, Supplier right) { + return buf.readBoolean() ? left.get() : right.get(); + } + + public static T readEither(ByteBuf buf, Function left, Function right) { + return buf.readBoolean() ? left.apply(buf) : right.apply(buf); + } + + public static T readOptional(ByteBuf buf, Supplier decoder) { + return buf.readBoolean() ? decoder.get() : null; + } + + public static T readOptional(ByteBuf buf, T empty, Supplier decoder) { + return buf.readBoolean() ? decoder.get() : empty; + } + + public static T readOptional(ByteBuf buf, Function decoder) { + return buf.readBoolean() ? decoder.apply(buf) : null; + } + + public static T readOptional(ByteBuf buf, T empty, Function decoder) { + return buf.readBoolean() ? decoder.apply(buf) : empty; + } + + public static void writeOptional(ByteBuf buf, T value, Consumer encoder) { + if (value == null) { + buf.writeBoolean(false); + return; + } + + buf.writeBoolean(true); + encoder.accept(value); + } + + public static void writeOptional(ByteBuf buf, T value, EncoderByteBufValue encoder) { + if (value == null) { + buf.writeBoolean(false); + return; + } + + buf.writeBoolean(true); + encoder.write(buf, value); + } + + public static void writeOptional(ByteBuf buf, T value, EncoderValueByteBuf encoder) { + if (value == null) { + buf.writeBoolean(false); + return; + } + + buf.writeBoolean(true); + encoder.write(value, buf); + } + + public static Collection readCollection(ByteBuf buf, Supplier decoder) { + return LimboProtocolUtils.readCollection(buf, Integer.MAX_VALUE, decoder); + } + + public static Collection readCollection(ByteBuf buf, int limit, Supplier decoder) { + int amount = ProtocolUtils.readVarInt(buf); + if (amount > 0) { + if (amount > limit) { + throw new DecoderException(amount + " elements exceeded max size of: " + limit); + } + + Collection list = new ArrayList<>(amount); + for (int i = 0; i < amount; ++i) { + list.add(decoder.get()); + } + + return list; + } else { + return List.of(); + } + } + + public static Collection readCollection(ByteBuf buf, Function decoder) { + return LimboProtocolUtils.readCollection(buf, Integer.MAX_VALUE, decoder); + } + + public static Collection readCollection(ByteBuf buf, int limit, Function decoder) { + int amount = ProtocolUtils.readVarInt(buf); + if (amount > 0) { + if (amount > limit) { + throw new DecoderException(amount + " elements exceeded max size of: " + limit); + } + + Collection list = new ArrayList<>(amount); + for (int i = 0; i < amount; ++i) { + list.add(decoder.apply(buf)); + } + + return list; + } else { + return List.of(); + } + } + + 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 writeCollection(ByteBuf buf, Collection collection, BiConsumer encoder) { + LimboProtocolUtils.writeCollection(buf, collection, Integer.MAX_VALUE, encoder); + } + + public static void writeCollection(ByteBuf buf, Collection collection, int limit, BiConsumer 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(value -> encoder.accept(value, buf)); + } + } + + public static T @Nullable [] readArray(ByteBuf buf, IntFunction generator, Supplier decoder) { + return LimboProtocolUtils.readArray(buf, Integer.MAX_VALUE, generator, decoder); + } + + public static T @Nullable [] readArray(ByteBuf buf, int limit, IntFunction generator, Supplier decoder) { + int amount = ProtocolUtils.readVarInt(buf); + if (amount > 0) { + if (amount > limit) { + throw new DecoderException(amount + " elements exceeded max size of: " + limit); + } + + T[] array = generator.apply(amount); + for (int i = 0; i < amount; ++i) { + array[i] = decoder.get(); + } + + return array; + } else { + return generator.apply(0); + } + } + + 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 long @Nullable [] readLongArray(ByteBuf buf) { + return LimboProtocolUtils.readLongArray(buf, Integer.MAX_VALUE); + } + + public static long @Nullable [] readLongArray(ByteBuf buf, int limit) { + int amount = ProtocolUtils.readVarInt(buf); + if (amount > 0) { + if (amount > limit) { + throw new DecoderException(amount + " elements exceeded max size of: " + limit); + } + + long[] array = new long[amount]; + for (int i = 0; i < amount; ++i) { + array[i] = buf.readLong(); + } + + return array; + } else { + return null; + } + } + + 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 int @Nullable [] readIntArray(ByteBuf buf) { + return LimboProtocolUtils.readIntArray(buf, Integer.MAX_VALUE); + } + + public static int @Nullable [] readIntArray(ByteBuf buf, int limit) { + int amount = ProtocolUtils.readVarInt(buf); + if (amount > 0) { + if (amount > limit) { + throw new DecoderException(amount + " elements exceeded max size of: " + limit); + } + + int[] array = new int[amount]; + for (int i = 0; i < amount; ++i) { + array[i] = buf.readInt(); + } + + return array; + } else { + return null; + } + } + + public static void writeIntArray(ByteBuf buf, int @Nullable [] array) { + LimboProtocolUtils.writeIntArray(buf, array, Integer.MAX_VALUE); + } + + public static void writeIntArray(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) { + buf.writeInt(value); + } + } + } + + public static int @Nullable [] readVarIntArray(ByteBuf buf) { + return LimboProtocolUtils.readVarIntArray(buf, Integer.MAX_VALUE); + } + + public static int @Nullable [] readVarIntArray(ByteBuf buf, int limit) { + int amount = ProtocolUtils.readVarInt(buf); + if (amount > 0) { + if (amount > limit) { + throw new DecoderException(amount + " elements exceeded max size of: " + limit); + } + + int[] array = new int[amount]; + for (int i = 0; i < amount; ++i) { + array[i] = ProtocolUtils.readVarInt(buf); + } + + return array; + } else { + return null; + } + } + + 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 Map readMap(ByteBuf buf, Supplier keyDecoder, Supplier valueDecoder) { + return LimboProtocolUtils.readMap(buf, Integer.MAX_VALUE, keyDecoder, valueDecoder); + } + + public static Map readMap(ByteBuf buf, int limit, Supplier keyDecoder, Supplier valueDecoder) { + int amount = ProtocolUtils.readVarInt(buf); + if (amount > 0) { + if (amount > limit) { + throw new DecoderException(amount + " elements exceeded max size of: " + limit); + } + + Map map = new HashMap<>(amount); + for (int i = 0; i < amount; ++i) { + map.put(keyDecoder.get(), valueDecoder.get()); + } + + return map; + } else { + return Map.of(); + } + } + + public static Map readMap(ByteBuf buf, Function keyDecoder, Function valueDecoder) { + return LimboProtocolUtils.readMap(buf, Integer.MAX_VALUE, keyDecoder, valueDecoder); + } + + public static Map readMap(ByteBuf buf, int limit, Function keyDecoder, Function valueDecoder) { + int amount = ProtocolUtils.readVarInt(buf); + if (amount > 0) { + if (amount > limit) { + throw new DecoderException(amount + " elements exceeded max size of: " + limit); + } + + Map map = new HashMap<>(amount); + for (int i = 0; i < amount; ++i) { + map.put(keyDecoder.apply(buf), valueDecoder.apply(buf)); + } + + return map; + } else { + return Map.of(); + } + } + + public static void writeMap(ByteBuf buf, Map map, BiConsumer encoder) { + LimboProtocolUtils.writeMap(buf, map, Integer.MAX_VALUE, encoder); + } + + public static void writeMap(ByteBuf buf, Map map, int limit, BiConsumer encoder) { + if (map == null) { + ProtocolUtils.writeVarInt(buf, 0); + return; + } + + int amount = map.size(); + // Inverted to avoid duplicate warning + if (amount != 0) { + if (amount > limit) { + throw new EncoderException(amount + " elements exceeded max size of: " + limit); + } + + ProtocolUtils.writeVarInt(buf, amount); + map.forEach(encoder); + } else { + ProtocolUtils.writeVarInt(buf, 0); + } + } + + 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); + }); + } + } + + 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) { + return version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2) ? ProtocolUtils.readVarInt(buf) : buf.readUnsignedByte(); + } + + public static GlobalPos readGlobalPos(ByteBuf buf, ProtocolVersion version) { + return new GlobalPos(ProtocolUtils.readString(buf), LimboProtocolUtils.readBlockPos(buf, version)); + } + + public static void writeGlobalPos(ByteBuf buf, ProtocolVersion version, GlobalPos value) { + ProtocolUtils.writeString(buf, value.dimension()); + LimboProtocolUtils.writeBlockPos(buf, version, value.blockPos()); + } + + public static BlockPos readBlockPos(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)); + } + } + + public static void writeBlockPos(ByteBuf buf, ProtocolVersion version, BlockPos value) { + LimboProtocolUtils.writeBlockPos(buf, version, value.posX(), value.posY(), value.posZ()); + } + + public static void writeBlockPos(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) + ); + } + } + + public static ItemStack readItemStack(ByteBuf buf, ProtocolVersion version) { + return LimboProtocolUtils.readItemStack(buf, version, true); + } + + public static ItemStack readItemStack(ByteBuf buf, ProtocolVersion version, boolean allowEmpty) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) { + int amount = ProtocolUtils.readVarInt(buf); + return amount > 0 + ? new ItemStack(ProtocolUtils.readVarInt(buf), amount, SimpleItemComponentMap.read(buf, version)) + : LimboProtocolUtils.emptyItemStack(allowEmpty); + } else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_13_2)) { + return buf.readBoolean() + ? new ItemStack(ProtocolUtils.readVarInt(buf), buf.readByte(), LimboProtocolUtils.readCompoundTagOrNull(buf, version)) + : LimboProtocolUtils.emptyItemStack(allowEmpty); + } else { + short material = buf.readShort(); + return material > 0 + ? new ItemStack(material, buf.readByte(), buf.readShort(), LimboProtocolUtils.readCompoundTagOrNull(buf, version)) + : LimboProtocolUtils.emptyItemStack(allowEmpty); + } + } + + private static ItemStack emptyItemStack(boolean allowEmpty) { + if (allowEmpty) { + return ItemStack.EMPTY; + } + + throw new DecoderException("Empty ItemStack not allowed"); + } + + public static void writeItemStack(ByteBuf buf, ProtocolVersion version, ItemStack value) { + LimboProtocolUtils.writeItemStack(buf, version, value, true); + } + + public static void writeItemStack(ByteBuf buf, ProtocolVersion version, ItemStack value, boolean allowEmpty) { + boolean hasDamage = version.noGreaterThan(ProtocolVersion.MINECRAFT_1_12_2); + if (value.isEmpty(hasDamage)) { + if (allowEmpty) { + 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 { + throw new EncoderException("Empty ItemStack not allowed"); + } + } else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) { + ProtocolUtils.writeVarInt(buf, value.amount()); + ProtocolUtils.writeVarInt(buf, value.material()); + ItemComponentMap 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()); + } else { + buf.writeShort(value.material()); + } + + buf.writeByte(value.amount()); + if (hasDamage) { + buf.writeShort(value.data()); + } + + LimboProtocolUtils.writeCompoundBinaryTag(buf, version, value.nbt()); + } + } + + public static BinaryTag readCompoundTag(ByteBuf buf, ProtocolVersion version) { + CompoundBinaryTag result = LimboProtocolUtils.readCompoundTagOrNull(buf, version); + return result == null ? EndBinaryTag.endBinaryTag() : result; + } + + public static CompoundBinaryTag readCompoundTagOrNull(ByteBuf buf, ProtocolVersion version) { + BinaryTag binaryTag = LimboProtocolUtils.readBinaryTagOrNull(buf, version); + if (binaryTag == null) { + return null; + } + + if (binaryTag.type() != BinaryTagTypes.COMPOUND) { + throw new DecoderException("Expected root tag to be CompoundTag, but is " + binaryTag.getClass().getSimpleName()); + } + + return (CompoundBinaryTag) binaryTag; + } + + public static BinaryTag readBinaryTag(ByteBuf buf, ProtocolVersion version) { + BinaryTag result = LimboProtocolUtils.readBinaryTagOrNull(buf, version); + return result == null ? EndBinaryTag.endBinaryTag() : result; + } + + public static BinaryTag readBinaryTagOrNull(ByteBuf buf, ProtocolVersion version) { + if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_7_6)) { + short length = buf.readShort(); + if (length < 0) { + return null; + } else { + byte[] data = new byte[length]; + buf.readBytes(data); + try (DataInputStream inputStream = new DataInputStream(new BufferedInputStream(new GZIPInputStream(new ByteArrayInputStream(data))))) { + BinaryTagType type = LimboProtocolUtils.BINARY_TAG_TYPES[inputStream.readByte()]; + buf.skipBytes(buf.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 LimboProtocolUtils.BINARY_TAG_TYPES[type].read(new ByteBufInputStream(buf)); + } catch (IOException thrown) { + throw new DecoderException("Unable to parse BinaryTag, full error: " + thrown.getMessage()); + } + } + } + } + + public static void writeCompoundBinaryTag(ByteBuf buf, ProtocolVersion version, BinaryTag tag) { + LimboProtocolUtils.writeBinaryTag(buf, version, tag == EndBinaryTag.endBinaryTag() ? null : tag); + } + + @SuppressWarnings("unchecked") + public static void writeBinaryTag(ByteBuf buf, ProtocolVersion version, T tag) { + if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_7_6)) { + if (tag == null) { + buf.writeShort(-1); + } else { + try (ByteArrayOutputStream output = new ByteArrayOutputStream()) { + try (DataOutputStream outputStream = new DataOutputStream(new GZIPOutputStream(output))) { + BinaryTagType type = (BinaryTagType) tag.type(); + outputStream.writeByte(type.id()); + outputStream.writeShort(0); // writeUTF("") + type.write(tag, outputStream); + } + + byte[] result = output.toByteArray(); + buf.writeShort((short) result.length); + buf.writeBytes(result); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } else if (tag == null) { + buf.writeByte(0); + } else { + ProtocolUtils.writeBinaryTag(buf, version, tag); + } + } + + @FunctionalInterface + public interface EncoderByteBufValue { + + void write(ByteBuf buf, T value); + } + + @FunctionalInterface + public interface EncoderValueByteBuf { + + void write(T value, ByteBuf buf); + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/protocol/util/NetworkSection.java b/plugin/src/main/java/net/elytrium/limboapi/protocol/util/NetworkSection.java index 1a2c6d85..29f87bd5 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/protocol/util/NetworkSection.java +++ b/plugin/src/main/java/net/elytrium/limboapi/protocol/util/NetworkSection.java @@ -20,7 +20,6 @@ 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; @@ -33,15 +32,15 @@ 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 +52,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(); } 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 +140,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/server/LimboImpl.java b/plugin/src/main/java/net/elytrium/limboapi/server/LimboImpl.java index 5b7dff2e..72e83648 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/server/LimboImpl.java +++ b/plugin/src/main/java/net/elytrium/limboapi/server/LimboImpl.java @@ -57,7 +57,6 @@ 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; @@ -65,13 +64,11 @@ 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.EnumSet; import java.util.HashMap; -import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -81,7 +78,6 @@ 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; @@ -95,7 +91,6 @@ 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.protocol.PacketDirection; import net.elytrium.limboapi.api.protocol.PreparedPacket; @@ -104,13 +99,14 @@ import net.elytrium.limboapi.injection.packet.MinecraftLimitedCompressDecoder; import net.elytrium.limboapi.material.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; @@ -127,12 +123,12 @@ public class LimboImpl implements Limbo { 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 MethodHandle GRACEFUL_DISCONNECT_FIELD = Reflection.findSetter(VelocityServerConnection.class, "gracefulDisconnect", boolean.class); + private static final MethodHandle PARTIAL_HASHED_SEED_FIELD = Reflection.findSetter(JoinGamePacket.class, "partialHashedSeed", long.class); + private static final MethodHandle LEVEL_NAMES_FIELDS = Reflection.findSetter(JoinGamePacket.class, "levelNames", ImmutableSet.class); + private static final MethodHandle REGISTRY_FIELD = Reflection.findSetter(JoinGamePacket.class, "registry", CompoundBinaryTag.class); + private static final MethodHandle CURRENT_DIMENSION_DATA_FIELD = Reflection.findSetter(JoinGamePacket.class, "currentDimensionData", CompoundBinaryTag.class); + private static final MethodHandle ROOT_NODE_FIELD = Reflection.findSetter(AvailableCommandsPacket.class, "rootNode", RootCommandNode.class); private static final CompoundBinaryTag CHAT_TYPE_119; private static final CompoundBinaryTag CHAT_TYPE_1191; @@ -156,7 +152,7 @@ public class LimboImpl implements Limbo { 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; @@ -211,30 +207,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() @@ -255,43 +251,33 @@ 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); @@ -314,26 +300,27 @@ private void createRegistrySyncModern(PreparedPacket packet, ProtocolVersion fro } } - 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()); } } - 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.left()); + BinaryTag tag = pair.right(); + if (tag == null) { + registry.writeBoolean(false); + } else { + registry.writeBoolean(true); + ProtocolUtils.writeBinaryTag(registry, version, tag); } - } + }); RegistrySyncPacket sync = new RegistrySyncPacket(); sync.replace(registry); @@ -343,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) { @@ -381,26 +368,25 @@ 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(); }); } if (connection.getState() != this.localStateRegistry) { - if (connection.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_20_2) < 0) { + if (connection.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_20_2)) { connection.eventLoop().execute(() -> connection.setState(this.localStateRegistry)); } VelocityServerConnection server = player.getConnectedServer(); @@ -410,12 +396,11 @@ public void spawnPlayer(Player apiPlayer, LimboSessionHandler handler) { if (serverConnection != null) { try { GRACEFUL_DISCONNECT_FIELD.invokeExact(server, true); - } catch (Throwable e) { - throw new ReflectionException(e); + } 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; } @@ -429,15 +414,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.compareTo(ProtocolVersion.MINECRAFT_1_20_2) >= 0) { if (connection.getState() != StateRegistry.CONFIG) { if (this.shouldRejoin) { // Switch to CONFIG state @@ -453,8 +438,7 @@ 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); } @@ -473,20 +457,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.getUsername(), player.getRemoteAddress(), 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); @@ -495,24 +478,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(); @@ -526,17 +502,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(); @@ -549,8 +515,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) { if (this.plugin.isLimboJoined(player)) { if (this.shouldRejoin) { sessionHandler.setJoinGameTriggered(true); @@ -567,31 +532,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, + List.of(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), + List.of(playerInfoEntry) + ); } connection.delayedWrite(playerInfoPacket); @@ -616,29 +571,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]); } } } @@ -673,11 +627,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; @@ -687,15 +644,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; } @@ -703,7 +658,6 @@ public Limbo setGameMode(GameMode gameMode) { @Override public Limbo setShouldRejoin(boolean shouldRejoin) { this.shouldRejoin = shouldRejoin; - this.built = false; return this; } @@ -711,7 +665,6 @@ public Limbo setShouldRejoin(boolean shouldRejoin) { @Override public Limbo setShouldRespawn(boolean shouldRespawn) { this.shouldRespawn = shouldRespawn; - this.built = false; return this; } @@ -719,7 +672,6 @@ public Limbo setShouldRespawn(boolean shouldRespawn) { @Override public Limbo setShouldUpdateTags(boolean shouldUpdateTags) { this.shouldUpdateTags = shouldUpdateTags; - this.built = false; return this; } @@ -727,7 +679,6 @@ public Limbo setShouldUpdateTags(boolean shouldUpdateTags) { @Override public Limbo setReducedDebugInfo(boolean reducedDebugInfo) { this.reducedDebugInfo = reducedDebugInfo; - this.built = false; return this; } @@ -735,7 +686,6 @@ public Limbo setReducedDebugInfo(boolean reducedDebugInfo) { @Override public Limbo setViewDistance(int viewDistance) { this.viewDistance = viewDistance; - this.built = false; return this; } @@ -743,7 +693,6 @@ public Limbo setViewDistance(int viewDistance) { @Override public Limbo setSimulationDistance(int simulationDistance) { this.simulationDistance = simulationDistance; - this.built = false; return this; } @@ -751,21 +700,20 @@ 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; } @@ -781,7 +729,6 @@ public Limbo registerPacket(PacketDirection direction, Class packetClass, Sup } LimboProtocol.register(this.localStateRegistry, direction, packetClass, packetSupplier, packetMappings); - return this; } @@ -796,7 +743,6 @@ public void dispose() { private List takeSnapshot() { List packets = new ArrayList<>(); - if (this.joinPackets != null) { packets.add(this.joinPackets); } @@ -821,7 +767,6 @@ private List takeSnapshot() { if (this.configPackets != null) { packets.add(this.configPackets); } - return packets; } @@ -829,18 +774,18 @@ private void localDispose() { this.takeSnapshot().forEach(PreparedPacket::release); } - // 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); @@ -849,19 +794,21 @@ 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) @@ -882,16 +829,13 @@ private CompoundBinaryTag createDimensionData(Dimension dimension, ProtocolVersi .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()); - } + return version.noLessThan(ProtocolVersion.MINECRAFT_1_16_2) + ? CompoundBinaryTag.builder() + .putString("name", dimension.getKey()) + .putInt("id", dimension.getModernId()) + .put("element", details) + .build() + : details.putString("name", dimension.getKey()); } private JoinGamePacket createJoinGamePacket(ProtocolVersion version) { @@ -901,12 +845,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); + } catch (Throwable t) { + throw new ReflectionException(t); } joinGame.setMaxPlayers(1); @@ -920,30 +864,45 @@ 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"); + } - if (version.compareTo(ProtocolVersion.MINECRAFT_1_16_2) >= 0) { + LEVEL_NAMES_FIELDS.invokeExact(joinGame, LEVELS); + REGISTRY_FIELD.invokeExact(joinGame, LimboImpl.createRegistry(version, dimensionRegistry)); + CURRENT_DIMENSION_DATA_FIELD.invokeExact(joinGame, currentDimensionData); + } catch (Throwable t) { + throw new ReflectionException(t); + } + + 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)) { 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); + dimensionRegistryEntry.put("value", dimensionRegistry); + registry.put("minecraft:dimension_type", dimensionRegistryEntry.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); + if (version == ProtocolVersion.MINECRAFT_1_19_4) { + registry.put("minecraft:damage_type", DAMAGE_TYPE_1194); } else if (version.compareTo(ProtocolVersion.MINECRAFT_1_21) >= 0) { - CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder(); ListBinaryTag values = DAMAGE_TYPE_120.getList("value"); ListBinaryTag.Builder tags = ListBinaryTag.builder(BinaryTagTypes.COMPOUND); @@ -951,73 +910,63 @@ 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.compareTo(ProtocolVersion.MINECRAFT_1_21_2) >= 0 + ? 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: Generate mappings for painting_variant and wolf_variant registries - if (version.compareTo(ProtocolVersion.MINECRAFT_1_21) >= 0) { - // TODO: API - 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()))); - - 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()))); + // TODO: API + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) { + // TODO мб ещё сюда и варианты для баннеров добавлять (если их в регистре не отправит, то они пустыми будут) + registry.put("minecraft:wolf_variant", LimboImpl.createRegistry("minecraft:wolf_variant", Map.of( + "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() + ))); + } + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21)) { + registry.put("minecraft:painting_variant", LimboImpl.createRegistry("minecraft:painting_variant", Map.of( + "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; } @@ -1025,8 +974,8 @@ private DefaultSpawnPositionPacket createDefaultSpawnPositionPacket() { return new DefaultSpawnPositionPacket((int) this.world.getSpawnX(), (int) this.world.getSpawnY(), (int) this.world.getSpawnZ(), 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() { @@ -1034,14 +983,14 @@ private AvailableCommandsPacket createAvailableCommandsPacket() { AvailableCommandsPacket packet = new AvailableCommandsPacket(); ROOT_NODE_FIELD.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(); + var orderedChunks = this.world.getOrderedChunks(); int chunkCounter = 0; for (List chunksWithSameDistance : orderedChunks) { @@ -1050,7 +999,7 @@ private PreparedPacket createFirstChunks() { } 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()); } } @@ -1058,7 +1007,7 @@ 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(); } @@ -1067,8 +1016,8 @@ private List createDelayedChunksPackets() { 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(); } @@ -1080,17 +1029,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. @@ -1099,24 +1047,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). @@ -1137,38 +1079,34 @@ private List createSafeClientServerSwitch(JoinGamePacket joinGa return packets; } - private PreparedPacket getBrandMessage(Class handlerClass) { - if (this.brandMessages.containsKey(handlerClass)) { - return this.brandMessages.get(handlerClass); + private PreparedPacket getBrandMessage(Class clazz) { + if (this.brandMessages.containsKey(clazz)) { + return this.brandMessages.get(clazz); } else { PreparedPacket preparedPacket = this.plugin.createPreparedPacket().prepare(this::createBrandMessage).build(); - this.brandMessages.put(handlerClass, preparedPacket); + this.brandMessages.put(clazz, preparedPacket); return preparedPacket; } } 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() { @@ -1177,32 +1115,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 f26767f2..f4688fba 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/server/LimboPlayerImpl.java +++ b/plugin/src/main/java/net/elytrium/limboapi/server/LimboPlayerImpl.java @@ -29,6 +29,7 @@ import java.awt.Graphics2D; import java.awt.Image; import java.awt.image.BufferedImage; +import java.util.Collection; import java.util.EnumSet; import java.util.List; import java.util.UUID; @@ -41,17 +42,20 @@ 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.EntityDataValue; import net.elytrium.limboapi.api.protocol.packets.data.MapData; import net.elytrium.limboapi.api.protocol.packets.data.MapPalette; -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 { @@ -60,7 +64,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; @@ -71,17 +74,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 @@ -90,8 +92,8 @@ public void flushPackets() { } @Override - public void closeWith(Object packetObj) { - this.connection.closeWith(packetObj); + public void closeWith(Object msg) { + this.connection.closeWith(msg); } @Override @@ -100,36 +102,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 ); } @@ -144,21 +127,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(); @@ -168,74 +150,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 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) { + this.writePacketAndFlush(new SetSlotPacket(0, slot, item, count)); } @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, CompoundBinaryTag nbt) { + this.writePacketAndFlush(new SetSlotPacket(0, slot, item, count, nbt)); } @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, ItemComponentMap 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 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 setItem(int slot, VirtualItem item, int count, short data, @Nullable ItemComponentMap map) { + this.writePacketAndFlush(new SetSlotPacket(0, slot, item, count, data, map)); + } + + @Override + public void setItem(int slot, VirtualItem item, int count, short data, CompoundBinaryTag nbt, ItemComponentMap 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, List.of(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 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 @@ -244,7 +248,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(); @@ -269,7 +273,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(); @@ -288,26 +292,27 @@ 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, this.player.getProtocolVersion()); } 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 - this.connection.setActiveSessionHandler(StateRegistry.CONFIG, - new ClientConfigSessionHandler(this.plugin.getServer(), this.player)); + this.connection.setActiveSessionHandler(StateRegistry.CONFIG, new ClientConfigSessionHandler(this.plugin.getServer(), this.player)); 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))) + List.of(new LegacyPlayerListItemPacket.Item(LimboAPI.getClientUniqueId(this.player))) )); } @@ -317,22 +322,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, Collection> packedItems) { + this.writePacketAndFlush(new SetEntityDataPacket(id, packedItems)); } @Override - public void sendAbilities(int abilities, float flySpeed, float walkSpeed) { - this.writePacketAndFlush(new PlayerAbilitiesPacket((byte) abilities, flySpeed, walkSpeed)); + public void sendGameModeSpecificAbilities() { + this.writePacketAndFlush(new PlayerAbilitiesPacket(this.getGameModeSpecificAbilities(), 0.05F, 0.1F)); } @Override - public void sendAbilities(byte abilities, float flySpeed, float walkSpeed) { - this.writePacketAndFlush(new PlayerAbilitiesPacket(abilities, flySpeed, walkSpeed)); + public void sendAbilities(int abilities, float flyingSpeed, float walkingSpeed) { + this.writePacketAndFlush(new PlayerAbilitiesPacket((byte) abilities, flyingSpeed, walkingSpeed)); } @Override - public byte getAbilities() { + public void sendAbilities(byte abilities, float flyingSpeed, float walkingSpeed) { + this.writePacketAndFlush(new PlayerAbilitiesPacket(abilities, flyingSpeed, walkingSpeed)); + } + + @Override + 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; @@ -358,15 +368,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 be8c91cf..88e5cc2b 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/server/LimboSessionHandlerImpl.java +++ b/plugin/src/main/java/net/elytrium/limboapi/server/LimboSessionHandlerImpl.java @@ -45,13 +45,11 @@ 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; @@ -63,12 +61,13 @@ 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,12 +107,12 @@ 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; } } @@ -123,10 +121,7 @@ public void onConfig(LimboPlayer player) { 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. serverReadTimeout /= 2; @@ -150,7 +145,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 +154,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() { @@ -229,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; @@ -263,9 +262,9 @@ 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; @@ -280,6 +279,7 @@ public boolean handle(KeepAlivePacket packet) { if (Settings.IMP.MAIN.LOGGING_ENABLED) { LimboAPI.getLogger().warn("{} sent an invalid keepalive.", this.player); } + return false; } else { this.keepAlivePending = false; @@ -290,10 +290,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); } + return false; } } @@ -337,11 +337,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); } } @@ -349,24 +349,23 @@ 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) { @@ -384,7 +383,6 @@ public void handleGeneric(MinecraftPacket packet) { 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); } @@ -411,17 +409,15 @@ public void disconnected() { 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.getUsername(), this.player.getRemoteAddress(), 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; @@ -444,16 +440,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); @@ -488,13 +482,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/type/WriteableItemComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/AbstractItemComponent.java similarity index 65% rename from plugin/src/main/java/net/elytrium/limboapi/server/item/type/WriteableItemComponent.java rename to plugin/src/main/java/net/elytrium/limboapi/server/item/AbstractItemComponent.java index 3c673846..5e23ea1d 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/WriteableItemComponent.java +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/AbstractItemComponent.java @@ -15,36 +15,34 @@ * along with this program. If not, see . */ -package net.elytrium.limboapi.server.item.type; +package net.elytrium.limboapi.server.item; 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 { +public abstract class AbstractItemComponent implements ItemComponent { - private final String name; - private T value; + private T value; // TODO remove setValue, getValue and value field, the components themselves must be the value - public WriteableItemComponent(String name) { - this.name = name; - } - - public abstract void write(ProtocolVersion version, ByteBuf buffer); + public abstract void read(ByteBuf buf, ProtocolVersion version); - @Override - public String getName() { - return this.name; - } + public abstract void write(ByteBuf buf, ProtocolVersion version); @Override - public ItemComponent setValue(T value) { + public void setValue(T value) { this.value = value; - return this; } @Override public T getValue() { return this.value; } + + @Override + public String toString() { + return this.getClass().getSimpleName() + "{" + + "value=" + this.value + + "}"; + } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/ItemComponentRegistry.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/ItemComponentRegistry.java new file mode 100644 index 00000000..0aacbf72 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/ItemComponentRegistry.java @@ -0,0 +1,186 @@ +/* + * 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 io.netty.buffer.ByteBuf; +import io.netty.util.collection.IntObjectHashMap; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import java.util.EnumMap; +import java.util.Locale; +import java.util.Map; +import java.util.function.Supplier; +import net.elytrium.limboapi.LimboAPI; +import net.elytrium.limboapi.api.protocol.item.DataComponentType; +import net.elytrium.limboapi.server.item.type.AdventureModePredicateComponent; +import net.elytrium.limboapi.server.item.type.ArmorTrimComponent; +import net.elytrium.limboapi.server.item.type.AttributeModifiersComponent; +import net.elytrium.limboapi.server.item.type.BannerPatternLayersComponent; +import net.elytrium.limboapi.server.item.type.BeesComponent; +import net.elytrium.limboapi.server.item.type.BlockStatePropertiesComponent; +import net.elytrium.limboapi.server.item.type.BooleanComponent; +import net.elytrium.limboapi.server.item.type.ConsumableComponent; +import net.elytrium.limboapi.server.item.type.DeathProtectionComponent; +import net.elytrium.limboapi.server.item.type.EquippableComponent; +import net.elytrium.limboapi.server.item.type.FireworkExplosionComponent; +import net.elytrium.limboapi.server.item.type.FireworksComponent; +import net.elytrium.limboapi.server.item.type.InstrumentComponent; +import net.elytrium.limboapi.server.item.type.ItemStackComponent; +import net.elytrium.limboapi.server.item.type.JukeboxPlayableComponent; +import net.elytrium.limboapi.server.item.type.LodestoneTrackerComponent; +import net.elytrium.limboapi.server.item.type.VarIntArrayComponent; +import net.elytrium.limboapi.server.item.type.HolderSetComponent; +import net.elytrium.limboapi.server.item.type.TextComponent; +import net.elytrium.limboapi.server.item.type.TextListComponent; +import net.elytrium.limboapi.server.item.type.DyedColorComponent; +import net.elytrium.limboapi.server.item.type.PotionContentsComponent; +import net.elytrium.limboapi.server.item.type.SuspiciousStewEffectsComponent; +import net.elytrium.limboapi.server.item.type.UnitComponent; +import net.elytrium.limboapi.server.item.type.EnchantmentsComponent; +import net.elytrium.limboapi.server.item.type.FoodPropertiesComponent; +import net.elytrium.limboapi.server.item.type.ResolvableProfileComponent; +import net.elytrium.limboapi.server.item.type.IntComponent; +import net.elytrium.limboapi.server.item.type.ItemStackListComponent; +import net.elytrium.limboapi.server.item.type.StringComponent; +import net.elytrium.limboapi.server.item.type.BinaryTagComponent; +import net.elytrium.limboapi.server.item.type.ToolComponent; +import net.elytrium.limboapi.server.item.type.UseCooldownComponent; +import net.elytrium.limboapi.server.item.type.VarIntComponent; +import net.elytrium.limboapi.server.item.type.WritableBookContentComponent; +import net.elytrium.limboapi.server.item.type.WrittenBookContentComponent; +import net.elytrium.limboapi.utils.JsonParser; + +@SuppressWarnings({"deprecation", "unchecked"}) +public class ItemComponentRegistry { + + private static final EnumMap, Object2IntMap>> REGISTRY; + private static final EnumMap>> FACTORY; + + static { + LinkedTreeMap> mappings = JsonParser.parse(LimboAPI.class.getResourceAsStream("/mappings/data_component_types_mappings.json")); + REGISTRY = new EnumMap<>(ProtocolVersion.class); + FACTORY = new EnumMap<>(DataComponentType.class); + mappings.forEach((name, versions) -> { + DataComponentType type = DataComponentType.valueOf(name.substring(10/*"minecraft:".length()*/).toUpperCase(Locale.US)); + versions.forEach((version, id) -> { + var entry = ItemComponentRegistry.REGISTRY.computeIfAbsent(ProtocolVersion.getProtocolVersion(Integer.parseInt(version)), key -> { + Object2IntOpenHashMap t2i = new Object2IntOpenHashMap<>(2); + t2i.defaultReturnValue(Integer.MIN_VALUE); + return Map.entry(new IntObjectHashMap<>(2), t2i); + }); + int component = id.intValue(); + entry.getKey().put(component, type); + entry.getValue().put(type, component); + }); + }); + + ItemComponentRegistry.register(BinaryTagComponent::new, + DataComponentType.ENTITY_DATA, DataComponentType.BUCKET_ENTITY_DATA, DataComponentType.BLOCK_ENTITY_DATA, + // ˅ I guess they forgot to add networkSynchronized codec for those below ˅ + DataComponentType.CUSTOM_DATA, DataComponentType.INTANGIBLE_PROJECTILE, DataComponentType.MAP_DECORATIONS, DataComponentType.DEBUG_STICK_STATE, + DataComponentType.RECIPES, DataComponentType.LOCK, DataComponentType.CONTAINER_LOOT + ); + ItemComponentRegistry.register(VarIntComponent::new, + DataComponentType.MAX_STACK_SIZE, DataComponentType.MAX_DAMAGE, DataComponentType.DAMAGE, DataComponentType.RARITY, DataComponentType.CUSTOM_MODEL_DATA, + DataComponentType.REPAIR_COST, DataComponentType.ENCHANTABLE, DataComponentType.MAP_ID, DataComponentType.MAP_POST_PROCESSING, DataComponentType.OMINOUS_BOTTLE_AMPLIFIER, + DataComponentType.BASE_COLOR + ); + ItemComponentRegistry.register(BooleanComponent::new, DataComponentType.UNBREAKABLE, DataComponentType.ENCHANTMENT_GLINT_OVERRIDE); + ItemComponentRegistry.register(TextComponent::new, DataComponentType.CUSTOM_NAME, DataComponentType.ITEM_NAME); + ItemComponentRegistry.register(() -> new TextListComponent(256), DataComponentType.LORE); + ItemComponentRegistry.register(EnchantmentsComponent::new, DataComponentType.ENCHANTMENTS, DataComponentType.STORED_ENCHANTMENTS); + ItemComponentRegistry.register(AdventureModePredicateComponent::new, DataComponentType.CAN_PLACE_ON, DataComponentType.CAN_BREAK); + ItemComponentRegistry.register(AttributeModifiersComponent::new, DataComponentType.ATTRIBUTE_MODIFIERS); + ItemComponentRegistry.register(() -> UnitComponent.INSTANCE, + DataComponentType.HIDE_ADDITIONAL_TOOLTIP, DataComponentType.HIDE_TOOLTIP, + DataComponentType.CREATIVE_SLOT_LOCK, DataComponentType.FIRE_RESISTANT, DataComponentType.GLIDER + ); + ItemComponentRegistry.register(FoodPropertiesComponent::new, DataComponentType.FOOD); + ItemComponentRegistry.register(ConsumableComponent::new, DataComponentType.CONSUMABLE); + ItemComponentRegistry.register(() -> new ItemStackComponent(false), DataComponentType.USE_REMAINDER); + ItemComponentRegistry.register(UseCooldownComponent::new, DataComponentType.USE_COOLDOWN); + ItemComponentRegistry.register(ToolComponent::new, DataComponentType.TOOL); + ItemComponentRegistry.register(EquippableComponent::new, DataComponentType.EQUIPPABLE); + ItemComponentRegistry.register(HolderSetComponent::new, DataComponentType.REPAIRABLE); + ItemComponentRegistry.register(DeathProtectionComponent::new, DataComponentType.DEATH_PROTECTION); + ItemComponentRegistry.register(DyedColorComponent::new, DataComponentType.DYED_COLOR); + ItemComponentRegistry.register(IntComponent::new, DataComponentType.MAP_COLOR); + ItemComponentRegistry.register(() -> new ItemStackListComponent(false), DataComponentType.CHARGED_PROJECTILES, DataComponentType.BUNDLE_CONTENTS); + ItemComponentRegistry.register(PotionContentsComponent::new, DataComponentType.POTION_CONTENTS); + ItemComponentRegistry.register(SuspiciousStewEffectsComponent::new, DataComponentType.SUSPICIOUS_STEW_EFFECTS); + ItemComponentRegistry.register(WritableBookContentComponent::new, DataComponentType.WRITABLE_BOOK_CONTENT); + ItemComponentRegistry.register(WrittenBookContentComponent::new, DataComponentType.WRITTEN_BOOK_CONTENT); + ItemComponentRegistry.register(ArmorTrimComponent::new, DataComponentType.TRIM); + ItemComponentRegistry.register(InstrumentComponent::new, DataComponentType.INSTRUMENT); + ItemComponentRegistry.register(JukeboxPlayableComponent::new, DataComponentType.JUKEBOX_PLAYABLE); + ItemComponentRegistry.register(LodestoneTrackerComponent::new, DataComponentType.LODESTONE_TRACKER); + ItemComponentRegistry.register(FireworkExplosionComponent::new, DataComponentType.FIREWORK_EXPLOSION); + ItemComponentRegistry.register(FireworksComponent::new, DataComponentType.FIREWORKS); + ItemComponentRegistry.register(ResolvableProfileComponent::new, DataComponentType.PROFILE); + ItemComponentRegistry.register(StringComponent::new, DataComponentType.ITEM_MODEL, DataComponentType.DAMAGE_RESISTANT, DataComponentType.TOOLTIP_STYLE, DataComponentType.NOTE_BLOCK_SOUND); + ItemComponentRegistry.register(BannerPatternLayersComponent::new, DataComponentType.BANNER_PATTERNS); + ItemComponentRegistry.register(() -> new VarIntArrayComponent(4), DataComponentType.POT_DECORATIONS); + ItemComponentRegistry.register(() -> new ItemStackListComponent(true, 256), DataComponentType.CONTAINER); + ItemComponentRegistry.register(BlockStatePropertiesComponent::new, DataComponentType.BLOCK_STATE); + ItemComponentRegistry.register(BeesComponent::new, DataComponentType.BEES); + } + + private static void register(Supplier> factory, DataComponentType... types) { + for (DataComponentType type : types) { + ItemComponentRegistry.FACTORY.put(type, factory); + } + } + + public static AbstractItemComponent createComponent(DataComponentType type, ByteBuf buf, ProtocolVersion version) { + //System.out.println("read item component: " + type); + var constructor = ItemComponentRegistry.FACTORY.get(type); + if (constructor == null) { + throw new IllegalStateException("Component not found: " + type); + } + + AbstractItemComponent object = (AbstractItemComponent) constructor.get(); + object.read(buf, version); + return object; + } + + public static AbstractItemComponent createComponent(DataComponentType type, T value) { + var constructor = ItemComponentRegistry.FACTORY.get(type); + if (constructor == null) { + throw new IllegalStateException("Component not found: " + type); + } + + AbstractItemComponent object = (AbstractItemComponent) constructor.get(); + object.setValue(value); + return object; + } + + public static DataComponentType getType(int id, ProtocolVersion version) { + DataComponentType type = ItemComponentRegistry.REGISTRY.get(version).getKey().get(id); + if (type == null) { + throw new IllegalStateException("Component not found: " + id); + } + + return type; + } + + public static int getId(DataComponentType name, ProtocolVersion version) { + return ItemComponentRegistry.REGISTRY.get(version).getValue().getInt(name); + } +} 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 078a33b0..00000000 --- a/plugin/src/main/java/net/elytrium/limboapi/server/item/SimpleItemComponentManager.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * 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.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.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 index bd4fb190..828e6964 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/server/item/SimpleItemComponentMap.java +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/SimpleItemComponentMap.java @@ -20,64 +20,153 @@ 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 java.util.HashMap; +import java.util.Map; +import net.elytrium.limboapi.api.protocol.item.DataComponentType; import net.elytrium.limboapi.api.protocol.item.ItemComponent; import net.elytrium.limboapi.api.protocol.item.ItemComponentMap; -import net.elytrium.limboapi.server.item.type.WriteableItemComponent; +@SuppressWarnings("unchecked") public class SimpleItemComponentMap implements ItemComponentMap { - private final List> addedComponents = new ArrayList<>(); - private final List> removedComponents = new ArrayList<>(); - private final SimpleItemComponentManager manager; + private Map> components; - public SimpleItemComponentMap(SimpleItemComponentManager manager) { - this.manager = manager; + @Override + public ItemComponentMap put(DataComponentType type, T value) { + if (this.components == null) { + this.components = new HashMap<>(); + } + + this.components.put(type, ItemComponentRegistry.createComponent(type, value)); + return this; } @Override - public ItemComponentMap add(ProtocolVersion version, String name, T value) { - this.addedComponents.add((WriteableItemComponent) this.manager.createComponent(version, name).setValue(value)); - return this; + public ItemComponent remove(DataComponentType type) { + if (this.components == null) { + this.components = new HashMap<>(); + } + + return (ItemComponent) this.components.put(type, null); } @Override - public ItemComponentMap remove(ProtocolVersion version, String name) { - this.removedComponents.add(this.manager.createComponent(version, name)); - return null; + public T get(DataComponentType type) { + if (this.components == null || this.components.isEmpty()) { + return null; + } + + AbstractItemComponent component = this.components.get(type); + return component == null ? null : (T) component.getValue(); } @Override - public List getAdded() { - return (List) (Object) this.addedComponents; + public T getOrDefault(DataComponentType type, T defaultValue) { + if (this.components == null || this.components.isEmpty()) { + return defaultValue; + } + + T value = this.get(type); + return value == null ? defaultValue : value; } @Override - public List getRemoved() { - return (List) (Object) this.removedComponents; + public Map> getComponents() { + if (this.components == null) { + this.components = new HashMap<>(); + } + + return (Map>) (Object) this.components; } @Override - public void read(ProtocolVersion version, Object buffer) { - // TODO: implement - throw new UnsupportedOperationException("read"); + public ItemComponentMap 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) { + DataComponentType type = ItemComponentRegistry.getType(ProtocolUtils.readVarInt(buf), version); + this.components.put(type, ItemComponentRegistry.createComponent(type, buf, version)); + } + + for (int i = 0; i < removed; ++i) { + this.components.put(ItemComponentRegistry.getType(ProtocolUtils.readVarInt(buf), version), null); + } } @Override - public void write(ProtocolVersion version, Object buffer) { - ByteBuf buf = (ByteBuf) buffer; + public ItemComponentMap write(Object bufObj, ProtocolVersion version) { + ByteBuf buf = (ByteBuf) bufObj; - ProtocolUtils.writeVarInt(buf, this.getAdded().size()); - ProtocolUtils.writeVarInt(buf, this.getRemoved().size()); + if (this.components == null || this.components.isEmpty()) { + ProtocolUtils.writeVarInt(buf, 0); + ProtocolUtils.writeVarInt(buf, 0); + } else { + int added = 0; + int removed = 0; + for (AbstractItemComponent component : this.components.values()) { + if (component.getValue() == null) { + ++removed; + } else { + ++added; + } + } - for (WriteableItemComponent component : this.addedComponents) { - ProtocolUtils.writeVarInt(buf, this.manager.getId(component.getName(), version)); - component.write(version, buf); + ProtocolUtils.writeVarInt(buf, added); + ProtocolUtils.writeVarInt(buf, removed); + + this.components.forEach((key, component) -> { + if (component != null && component.getValue() != null) { + int id = ItemComponentRegistry.getId(key, version); + if (id != Integer.MIN_VALUE) { // allow plugins to send version-specific components and don't do throw if id cannot be found + ProtocolUtils.writeVarInt(buf, id); + component.write(buf, version); + } + } + }); + + this.components.forEach((key, component) -> { + if (component == null || component.getValue() == null) { + int id = ItemComponentRegistry.getId(key, version); + if (id != Integer.MIN_VALUE) { + ProtocolUtils.writeVarInt(buf, id); + } + } + }); } - for (WriteableItemComponent component : this.removedComponents) { - ProtocolUtils.writeVarInt(buf, this.manager.getId(component.getName(), version)); + return this; + } + + @Override + public String toString() { + return "SimpleItemComponentMap{" + + "components=" + this.components + + "}"; + } + + public static ItemComponentMap read(ByteBuf buf, ProtocolVersion version) { + int added = ProtocolUtils.readVarInt(buf); + int removed = ProtocolUtils.readVarInt(buf); + if (added == 0 && removed == 0) { + return null; + } else { + SimpleItemComponentMap map = new SimpleItemComponentMap(); + map.read(buf, version, added, removed); + return map; } } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/AdventureModePredicateComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/AdventureModePredicateComponent.java new file mode 100644 index 00000000..cfb4900c --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/AdventureModePredicateComponent.java @@ -0,0 +1,99 @@ +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.Collection; +import net.elytrium.limboapi.protocol.util.LimboProtocolUtils; +import net.elytrium.limboapi.server.item.AbstractItemComponent; +import net.elytrium.limboapi.server.item.type.data.HolderSet; +import net.kyori.adventure.nbt.CompoundBinaryTag; +import org.checkerframework.checker.nullness.qual.Nullable; + +public class AdventureModePredicateComponent extends AbstractItemComponent { + + @Override + public void read(ByteBuf buf, ProtocolVersion version) { + this.setValue(Value.read(buf, version)); + } + + @Override + public void write(ByteBuf buf, ProtocolVersion version) { + this.getValue().write(buf, version); + } + + public record Value(Collection predicates, boolean showInTooltip) { + + public void write(ByteBuf buf, ProtocolVersion version) { + LimboProtocolUtils.writeCollection(buf, this.predicates, predicate -> predicate.write(buf, version)); + buf.writeBoolean(this.showInTooltip); + } + + public static Value read(ByteBuf buf, ProtocolVersion version) { + return new Value(LimboProtocolUtils.readCollection(buf, () -> BlockPredicate.read(buf, version)), buf.readBoolean()); + } + + public record BlockPredicate(@Nullable HolderSet blocks, @Nullable Collection properties, @Nullable CompoundBinaryTag nbt) { + + public void write(ByteBuf buf, ProtocolVersion version) { + LimboProtocolUtils.writeOptional(buf, this.blocks, HolderSet::write); + LimboProtocolUtils.writeOptional(buf, this.properties, properties -> LimboProtocolUtils.writeCollection(buf, properties, PropertyMatcher::write)); + LimboProtocolUtils.writeOptional(buf, this.nbt, nbt -> ProtocolUtils.writeBinaryTag(buf, version, nbt)); + } + + public static BlockPredicate read(ByteBuf buf, ProtocolVersion version) { + return new BlockPredicate( + LimboProtocolUtils.readOptional(buf, HolderSet::read), + LimboProtocolUtils.readOptional(buf, () -> LimboProtocolUtils.readCollection(buf, PropertyMatcher::read)), + LimboProtocolUtils.readOptional(buf, () -> ProtocolUtils.readCompoundTag(buf, version, null)) + ); + } + + public record PropertyMatcher(String name, ValueMatcher valueMatcher) { + + public void write(ByteBuf buf) { + ProtocolUtils.writeString(buf, this.name); + this.valueMatcher.write(buf); + } + + public static PropertyMatcher read(ByteBuf buf) { + return new PropertyMatcher(ProtocolUtils.readString(buf), ValueMatcher.read(buf)); + } + + public sealed interface ValueMatcher permits ValueMatcher.ExactMatcher, ValueMatcher.RangedMatcher { + + void write(ByteBuf buf); + + static ValueMatcher read(ByteBuf buf) { + return LimboProtocolUtils.readEither(buf, ExactMatcher::read, RangedMatcher::read); + } + + record ExactMatcher(String value) implements ValueMatcher { + + @Override + public void write(ByteBuf buf) { + ProtocolUtils.writeString(buf, this.value); + } + + public static ExactMatcher read(ByteBuf buf) { + return new ExactMatcher(ProtocolUtils.readString(buf)); + } + } + + record RangedMatcher(@Nullable String minValue, @Nullable String maxValue) implements ValueMatcher { + + @Override + public void write(ByteBuf buf) { + LimboProtocolUtils.writeOptional(buf, this.minValue, ProtocolUtils::writeString); + LimboProtocolUtils.writeOptional(buf, this.maxValue, ProtocolUtils::writeString); + } + + public static RangedMatcher read(ByteBuf buf) { + return new RangedMatcher(LimboProtocolUtils.readOptional(buf, ProtocolUtils::readString), LimboProtocolUtils.readOptional(buf, ProtocolUtils::readString)); + } + } + } + } + } + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/ArmorTrimComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/ArmorTrimComponent.java new file mode 100644 index 00000000..c38683cf --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/ArmorTrimComponent.java @@ -0,0 +1,108 @@ +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.Map; +import net.elytrium.limboapi.protocol.util.LimboProtocolUtils; +import net.elytrium.limboapi.server.item.AbstractItemComponent; +import net.elytrium.limboapi.server.item.type.data.VersionLessComponentHolder; + +public class ArmorTrimComponent extends AbstractItemComponent { + + @Override + public void read(ByteBuf buf, ProtocolVersion version) { + this.setValue(Value.read(buf, version)); + } + + @Override + public void write(ByteBuf buf, ProtocolVersion version) { + this.getValue().write(buf, version); + } + + public record Value(TrimMaterial material, TrimPattern pattern, boolean showInTooltip) { + + public void write(ByteBuf buf, ProtocolVersion version) { + this.material.write(buf, version); + this.pattern.write(buf, version); + buf.writeBoolean(this.showInTooltip); + } + + public static Value read(ByteBuf buf, ProtocolVersion version) { + return new Value(TrimMaterial.read(buf, version), TrimPattern.read(buf, version), buf.readBoolean()); + } + + public sealed interface TrimMaterial permits TrimMaterial.ReferenceTrimMaterial, TrimMaterial.DirectTrimMaterial { + + void write(ByteBuf buf, ProtocolVersion version); + + static TrimMaterial read(ByteBuf buf, ProtocolVersion version) { + int i = ProtocolUtils.readVarInt(buf); + return i == 0 ? DirectTrimMaterial.read(buf, version) : new ReferenceTrimMaterial(i - 1); + } + + record ReferenceTrimMaterial(int id) implements TrimMaterial { + + @Override + public void write(ByteBuf buf, ProtocolVersion version) { + ProtocolUtils.writeVarInt(buf, this.id + 1); + } + } + + record DirectTrimMaterial(String assetName, int ingredient, float itemModelIndex, Map overrideArmorMaterials, VersionLessComponentHolder description) implements TrimMaterial { + + @Override + public void write(ByteBuf buf, ProtocolVersion version) { + ProtocolUtils.writeString(buf, this.assetName); + ProtocolUtils.writeVarInt(buf, this.ingredient); + buf.writeFloat(this.itemModelIndex); + LimboProtocolUtils.writeMap(buf, this.overrideArmorMaterials, ProtocolUtils::writeString, ProtocolUtils::writeString); + this.description.write(buf, version); + } + + public static DirectTrimMaterial read(ByteBuf buf, ProtocolVersion version) { + return new DirectTrimMaterial( + ProtocolUtils.readString(buf), + ProtocolUtils.readVarInt(buf), + buf.readFloat(), + LimboProtocolUtils.readMap(buf, ProtocolUtils::readString, ProtocolUtils::readString), + VersionLessComponentHolder.read(buf, version) + ); + } + } + } + + public sealed interface TrimPattern permits TrimPattern.ReferenceTrimPattern, TrimPattern.DirectTrimPattern { + + void write(ByteBuf buf, ProtocolVersion version); + + static TrimPattern read(ByteBuf buf, ProtocolVersion version) { + int i = ProtocolUtils.readVarInt(buf); + return i == 0 ? DirectTrimPattern.read(buf, version) : new ReferenceTrimPattern(i - 1); + } + + record ReferenceTrimPattern(int id) implements TrimPattern { + + @Override + public void write(ByteBuf buf, ProtocolVersion version) { + ProtocolUtils.writeVarInt(buf, this.id + 1); + } + } + + record DirectTrimPattern(String assetId, int templateItem, VersionLessComponentHolder description, boolean decal) implements TrimPattern { + + @Override + public void write(ByteBuf buf, ProtocolVersion version) { + ProtocolUtils.writeString(buf, this.assetId); + ProtocolUtils.writeVarInt(buf, this.templateItem); + this.description.write(buf, version); + buf.writeBoolean(this.decal); + } + + public static DirectTrimPattern read(ByteBuf buf, ProtocolVersion version) { + return new DirectTrimPattern(ProtocolUtils.readString(buf), ProtocolUtils.readVarInt(buf), VersionLessComponentHolder.read(buf, version), buf.readBoolean()); + } + } + } + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/AttributeModifiersComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/AttributeModifiersComponent.java new file mode 100644 index 00000000..f43ca1e9 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/AttributeModifiersComponent.java @@ -0,0 +1,73 @@ +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.Collection; +import java.util.UUID; +import net.elytrium.limboapi.protocol.util.LimboProtocolUtils; +import net.elytrium.limboapi.server.item.AbstractItemComponent; + +public class AttributeModifiersComponent extends AbstractItemComponent { + + @Override + public void read(ByteBuf buf, ProtocolVersion version) { + this.setValue(Value.read(buf, version)); + } + + @Override + public void write(ByteBuf buf, ProtocolVersion version) { + this.getValue().write(buf, version); + } + + public record Value(Collection modifiers, boolean showInTooltip) { + + public void write(ByteBuf buf, ProtocolVersion version) { + LimboProtocolUtils.writeCollection(buf, this.modifiers, entry -> entry.write(buf, version)); + buf.writeBoolean(this.showInTooltip); + } + + public static Value read(ByteBuf buf, ProtocolVersion version) { + return new Value(LimboProtocolUtils.readCollection(buf, () -> Entry.read(buf, version)), buf.readBoolean()); + } + + public record Entry(int attribute, AttributeModifier modifier, int slot) { + + public void write(ByteBuf buf, ProtocolVersion version) { + ProtocolUtils.writeVarInt(buf, this.attribute); + this.modifier.write(buf, version); + ProtocolUtils.writeVarInt(buf, this.slot); + } + + public static Entry read(ByteBuf buf, ProtocolVersion version) { + return new Entry(ProtocolUtils.readVarInt(buf), AttributeModifier.read(buf, version), ProtocolUtils.readVarInt(buf)); + } + + public record AttributeModifier(UUID uuid, String name, double amount, int operation) { + + public void write(ByteBuf buf, ProtocolVersion version) { + if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_20_5)) { + if (this.uuid == null) { + throw new IllegalArgumentException("uuid cannot be null on 1.20.5!"); + } + + ProtocolUtils.writeUuid(buf, this.uuid); + } + + ProtocolUtils.writeString(buf, this.name); + buf.writeDouble(this.amount); + ProtocolUtils.writeVarInt(buf, this.operation); + } + + public static AttributeModifier read(ByteBuf buf, ProtocolVersion version) { + return new AttributeModifier( + version.noGreaterThan(ProtocolVersion.MINECRAFT_1_20_5) ? ProtocolUtils.readUuid(buf) : null, + ProtocolUtils.readString(buf), + buf.readDouble(), + ProtocolUtils.readVarInt(buf) + ); + } + } + } + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/BannerPatternLayersComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/BannerPatternLayersComponent.java new file mode 100644 index 00000000..0e7403c9 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/BannerPatternLayersComponent.java @@ -0,0 +1,64 @@ +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.Collection; +import net.elytrium.limboapi.protocol.util.LimboProtocolUtils; +import net.elytrium.limboapi.server.item.AbstractItemComponent; + +public class BannerPatternLayersComponent extends AbstractItemComponent> { + + @Override + public void read(ByteBuf buf, ProtocolVersion version) { + this.setValue(LimboProtocolUtils.readCollection(buf, Layer::read)); + } + + @Override + public void write(ByteBuf buf, ProtocolVersion version) { + LimboProtocolUtils.writeCollection(buf, this.getValue(), Layer::write); + } + + public record Layer(BannerPattern pattern, int color) { + + public void write(ByteBuf buf) { + this.pattern.write(buf); + ProtocolUtils.writeVarInt(buf, this.color); + } + + public static Layer read(ByteBuf buf) { + return new Layer(BannerPattern.read(buf), ProtocolUtils.readVarInt(buf)); + } + + public sealed interface BannerPattern permits BannerPattern.ReferenceBannerPattern, BannerPattern.DirectBannerPattern { + + void write(ByteBuf buf); + + static BannerPattern read(ByteBuf buf) { + int i = ProtocolUtils.readVarInt(buf); + return i == 0 ? DirectBannerPattern.read(buf) : new ReferenceBannerPattern(i - 1); + } + + record ReferenceBannerPattern(int id) implements BannerPattern { + + @Override + public void write(ByteBuf buf) { + ProtocolUtils.writeVarInt(buf, this.id + 1); + } + } + + record DirectBannerPattern(String assetId, String translationKey) implements BannerPattern { + + @Override + public void write(ByteBuf buf) { + ProtocolUtils.writeString(buf, this.assetId); + ProtocolUtils.writeString(buf, this.translationKey); + } + + public static DirectBannerPattern read(ByteBuf buf) { + return new DirectBannerPattern(ProtocolUtils.readString(buf), ProtocolUtils.readString(buf)); + } + } + } + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/BeesComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/BeesComponent.java new file mode 100644 index 00000000..82be8f66 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/BeesComponent.java @@ -0,0 +1,35 @@ +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.Collection; +import net.elytrium.limboapi.protocol.util.LimboProtocolUtils; +import net.elytrium.limboapi.server.item.AbstractItemComponent; +import net.kyori.adventure.nbt.CompoundBinaryTag; + +public class BeesComponent extends AbstractItemComponent> { + + @Override + public void read(ByteBuf buf, ProtocolVersion version) { + this.setValue(LimboProtocolUtils.readCollection(buf, () -> Occupant.read(buf, version))); + } + + @Override + public void write(ByteBuf buf, ProtocolVersion version) { + LimboProtocolUtils.writeCollection(buf, this.getValue(), occupant -> occupant.write(buf, version)); + } + + public record Occupant(CompoundBinaryTag entityData, int ticksInHive, int minTicksInHive) { + + public void write(ByteBuf buf, ProtocolVersion version) { + ProtocolUtils.writeBinaryTag(buf, version, this.entityData); + ProtocolUtils.writeVarInt(buf, this.ticksInHive); + ProtocolUtils.writeVarInt(buf, this.minTicksInHive); + } + + public static Occupant read(ByteBuf buf, ProtocolVersion version) { + return new Occupant(ProtocolUtils.readCompoundTag(buf, version, null), ProtocolUtils.readVarInt(buf), ProtocolUtils.readVarInt(buf)); + } + } +} 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/BinaryTagComponent.java similarity index 67% rename from plugin/src/main/java/net/elytrium/limboapi/server/item/type/StringsItemComponent.java rename to plugin/src/main/java/net/elytrium/limboapi/server/item/type/BinaryTagComponent.java index 741a6cad..a8e4861d 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/StringsItemComponent.java +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/BinaryTagComponent.java @@ -20,19 +20,18 @@ import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; -import java.util.List; +import net.elytrium.limboapi.server.item.AbstractItemComponent; +import net.kyori.adventure.nbt.BinaryTag; -public class StringsItemComponent extends WriteableItemComponent> { +public class BinaryTagComponent extends AbstractItemComponent { - public StringsItemComponent(String name) { - super(name); + @Override + public void read(ByteBuf buf, ProtocolVersion version) { + this.setValue(ProtocolUtils.readBinaryTag(buf, version, null)); } @Override - public void write(ProtocolVersion version, ByteBuf buffer) { - ProtocolUtils.writeVarInt(buffer, this.getValue().size()); - for (String string : this.getValue()) { - ProtocolUtils.writeString(buffer, string); - } + public void write(ByteBuf buf, ProtocolVersion version) { + ProtocolUtils.writeBinaryTag(buf, version, this.getValue()); } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/BlockStatePropertiesComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/BlockStatePropertiesComponent.java new file mode 100644 index 00000000..c49a0d35 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/BlockStatePropertiesComponent.java @@ -0,0 +1,21 @@ +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.Map; +import net.elytrium.limboapi.protocol.util.LimboProtocolUtils; +import net.elytrium.limboapi.server.item.AbstractItemComponent; + +public class BlockStatePropertiesComponent extends AbstractItemComponent> { + + @Override + public void read(ByteBuf buf, ProtocolVersion version) { + this.setValue(LimboProtocolUtils.readMap(buf, ProtocolUtils::readString, ProtocolUtils::readString)); + } + + @Override + public void write(ByteBuf buf, ProtocolVersion version) { + LimboProtocolUtils.writeMap(buf, this.getValue(), ProtocolUtils::writeString, ProtocolUtils::writeString); + } +} 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/type/BooleanComponent.java similarity index 71% 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/type/BooleanComponent.java index a1e8a131..fbfa12dc 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/BooleanItemComponent.java +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/BooleanComponent.java @@ -19,15 +19,17 @@ import com.velocitypowered.api.network.ProtocolVersion; import io.netty.buffer.ByteBuf; +import net.elytrium.limboapi.server.item.AbstractItemComponent; -public class BooleanItemComponent extends WriteableItemComponent { +public class BooleanComponent extends AbstractItemComponent { - public BooleanItemComponent(String name) { - super(name); + @Override + public void read(ByteBuf buf, ProtocolVersion version) { + this.setValue(buf.readBoolean()); } @Override - public void write(ProtocolVersion version, ByteBuf buffer) { - buffer.writeBoolean(this.getValue()); + public void write(ByteBuf buf, ProtocolVersion version) { + buf.writeBoolean(this.getValue()); } } 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 3b632810..00000000 --- a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/ComponentsItemComponent.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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.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/ConsumableComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/ConsumableComponent.java new file mode 100644 index 00000000..a0ee0395 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/ConsumableComponent.java @@ -0,0 +1,38 @@ +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.Collection; +import net.elytrium.limboapi.protocol.util.LimboProtocolUtils; +import net.elytrium.limboapi.server.item.AbstractItemComponent; +import net.elytrium.limboapi.server.item.type.data.ConsumeEffect; +import net.elytrium.limboapi.server.item.type.data.SoundEvent; + +public class ConsumableComponent extends AbstractItemComponent { + + @Override + public void read(ByteBuf buf, ProtocolVersion version) { + this.setValue(Value.read(buf)); + } + + @Override + public void write(ByteBuf buf, ProtocolVersion version) { + this.getValue().write(buf); + } + + public record Value(float consumeSeconds, int animation, SoundEvent sound, boolean hasConsumeParticles, Collection onConsumeEffects) { + + public void write(ByteBuf buf) { + buf.writeFloat(this.consumeSeconds); + ProtocolUtils.writeVarInt(buf, this.animation); + this.sound.write(buf); + buf.writeBoolean(this.hasConsumeParticles); + LimboProtocolUtils.writeCollection(buf, this.onConsumeEffects, ConsumeEffect::write); + } + + public static Value read(ByteBuf buf) { + return new Value(buf.readFloat(), ProtocolUtils.readVarInt(buf), SoundEvent.read(buf), buf.readBoolean(), LimboProtocolUtils.readCollection(buf, ConsumeEffect::read)); + } + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/DeathProtectionComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/DeathProtectionComponent.java new file mode 100644 index 00000000..2999c7ec --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/DeathProtectionComponent.java @@ -0,0 +1,21 @@ +package net.elytrium.limboapi.server.item.type; + +import com.velocitypowered.api.network.ProtocolVersion; +import io.netty.buffer.ByteBuf; +import java.util.Collection; +import net.elytrium.limboapi.protocol.util.LimboProtocolUtils; +import net.elytrium.limboapi.server.item.AbstractItemComponent; +import net.elytrium.limboapi.server.item.type.data.ConsumeEffect; + +public class DeathProtectionComponent extends AbstractItemComponent> { + + @Override + public void read(ByteBuf buf, ProtocolVersion version) { + this.setValue(LimboProtocolUtils.readCollection(buf, ConsumeEffect::read)); + } + + @Override + public void write(ByteBuf buf, ProtocolVersion version) { + LimboProtocolUtils.writeCollection(buf, this.getValue(), ConsumeEffect::write); + } +} 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/DyedColorComponent.java similarity index 52% rename from plugin/src/main/java/net/elytrium/limboapi/server/item/type/EnchantmentsItemComponent.java rename to plugin/src/main/java/net/elytrium/limboapi/server/item/type/DyedColorComponent.java index 834e88d0..d9baca14 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/EnchantmentsItemComponent.java +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/DyedColorComponent.java @@ -18,24 +18,35 @@ 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; +import net.elytrium.limboapi.server.item.AbstractItemComponent; -public class EnchantmentsItemComponent extends WriteableItemComponent>, Boolean>> { +public class DyedColorComponent extends AbstractItemComponent> { // TODO DyedColorComponent.Value - public EnchantmentsItemComponent(String name) { - super(name); + @Override + public void read(ByteBuf buf, ProtocolVersion version) { + //this.setValue(Value.read(buf)); + this.setValue(Pair.of(buf.readInt(), buf.readBoolean())); } @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()); + public void write(ByteBuf buf, ProtocolVersion version) { + var value = this.getValue(); + buf.writeInt(value.left()); + buf.writeBoolean(value.right()); + //this.getValue().write(buf); + }/* + + public record Value(int rgb, boolean showInTooltip) { + + public void write(ByteBuf buf) { + buf.writeInt(this.rgb); + buf.writeBoolean(this.showInTooltip); } - buffer.writeBoolean(this.getValue().right()); - } + + public static Value read(ByteBuf buf) { + return new Value(buf.readInt(), buf.readBoolean()); + } + }*/ } 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 8d914e62..00000000 --- a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/DyedColorItemComponent.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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.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/GameProfileItemComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/EnchantmentsComponent.java similarity index 51% rename from plugin/src/main/java/net/elytrium/limboapi/server/item/type/GameProfileItemComponent.java rename to plugin/src/main/java/net/elytrium/limboapi/server/item/type/EnchantmentsComponent.java index 842792ec..1e3d2b5e 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/GameProfileItemComponent.java +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/EnchantmentsComponent.java @@ -18,31 +18,33 @@ 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; +import java.util.Map; +import net.elytrium.limboapi.protocol.util.LimboProtocolUtils; +import net.elytrium.limboapi.server.item.AbstractItemComponent; -public class GameProfileItemComponent extends WriteableItemComponent { +public class EnchantmentsComponent extends AbstractItemComponent { - private static final UUID ZERO = new UUID(0, 0); - - public GameProfileItemComponent(String name) { - super(name); + @Override + public void read(ByteBuf buf, ProtocolVersion version) { + this.setValue(Value.read(buf)); } @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()); - } + public void write(ByteBuf buf, ProtocolVersion version) { + this.getValue().write(buf); + } + + public record Value(Map enchantments, boolean showInTooltip) { - buffer.writeBoolean(!this.getValue().getId().equals(ZERO)); - if (!this.getValue().getId().equals(ZERO)) { - ProtocolUtils.writeUuid(buffer, this.getValue().getId()); + public void write(ByteBuf buf) { + LimboProtocolUtils.writeMap(buf, this.enchantments, ProtocolUtils::writeVarInt, ProtocolUtils::writeVarInt); + buf.writeBoolean(this.showInTooltip); } - ProtocolUtils.writeProperties(buffer, this.getValue().getProperties()); + public static Value read(ByteBuf buf) { + return new Value(LimboProtocolUtils.readMap(buf, ProtocolUtils::readVarInt, ProtocolUtils::readVarInt), buf.readBoolean()); + } } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/EquippableComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/EquippableComponent.java new file mode 100644 index 00000000..7cf63c28 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/EquippableComponent.java @@ -0,0 +1,51 @@ +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.elytrium.limboapi.protocol.util.LimboProtocolUtils; +import net.elytrium.limboapi.server.item.AbstractItemComponent; +import net.elytrium.limboapi.server.item.type.data.HolderSet; +import net.elytrium.limboapi.server.item.type.data.SoundEvent; +import org.checkerframework.checker.nullness.qual.Nullable; + +public class EquippableComponent extends AbstractItemComponent { + + @Override + public void read(ByteBuf buf, ProtocolVersion version) { + this.setValue(Value.read(buf)); + } + + @Override + public void write(ByteBuf buf, ProtocolVersion version) { + this.getValue().write(buf); + } + + public record Value(int slot, SoundEvent equipSound, @Nullable String model, @Nullable String cameraOverlay, @Nullable HolderSet allowedEntities, + boolean dispensable, boolean swappable, boolean damageOnHurt) { + + public void write(ByteBuf buf) { + ProtocolUtils.writeVarInt(buf, this.slot); + this.equipSound.write(buf); + LimboProtocolUtils.writeOptional(buf, this.model, ProtocolUtils::writeString); + LimboProtocolUtils.writeOptional(buf, this.cameraOverlay, ProtocolUtils::writeString); + LimboProtocolUtils.writeOptional(buf, this.allowedEntities, HolderSet::write); + buf.writeBoolean(this.dispensable); + buf.writeBoolean(this.swappable); + buf.writeBoolean(this.damageOnHurt); + } + + public static Value read(ByteBuf buf) { + return new Value( + ProtocolUtils.readVarInt(buf), + SoundEvent.read(buf), + LimboProtocolUtils.readOptional(buf, ProtocolUtils::readString), + LimboProtocolUtils.readOptional(buf, ProtocolUtils::readString), + LimboProtocolUtils.readOptional(buf, HolderSet::read), + buf.readBoolean(), + buf.readBoolean(), + buf.readBoolean() + ); + } + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/FireworkExplosionComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/FireworkExplosionComponent.java new file mode 100644 index 00000000..1ff5bb43 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/FireworkExplosionComponent.java @@ -0,0 +1,36 @@ +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.elytrium.limboapi.protocol.util.LimboProtocolUtils; +import net.elytrium.limboapi.server.item.AbstractItemComponent; +import org.checkerframework.checker.nullness.qual.Nullable; + +public class FireworkExplosionComponent extends AbstractItemComponent { + + @Override + public void read(ByteBuf buf, ProtocolVersion version) { + this.setValue(Value.read(buf)); + } + + @Override + public void write(ByteBuf buf, ProtocolVersion version) { + this.getValue().write(buf); + } + + public record Value(int shape, int @Nullable [] colors, int @Nullable [] fadeColors, boolean hasTrail, boolean hasTwinkle) { + + public void write(ByteBuf buf) { + ProtocolUtils.writeVarInt(buf, this.shape); + LimboProtocolUtils.writeIntArray(buf, this.colors); + LimboProtocolUtils.writeIntArray(buf, this.fadeColors); + buf.writeBoolean(this.hasTrail); + buf.writeBoolean(this.hasTwinkle); + } + + public static Value read(ByteBuf buf) { + return new Value(ProtocolUtils.readVarInt(buf), LimboProtocolUtils.readIntArray(buf), LimboProtocolUtils.readIntArray(buf), buf.readBoolean(), buf.readBoolean()); + } + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/FireworksComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/FireworksComponent.java new file mode 100644 index 00000000..deda0413 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/FireworksComponent.java @@ -0,0 +1,33 @@ +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.Collection; +import net.elytrium.limboapi.protocol.util.LimboProtocolUtils; +import net.elytrium.limboapi.server.item.AbstractItemComponent; + +public class FireworksComponent extends AbstractItemComponent { + + @Override + public void read(ByteBuf buf, ProtocolVersion version) { + this.setValue(Value.read(buf)); + } + + @Override + public void write(ByteBuf buf, ProtocolVersion version) { + this.getValue().write(buf); + } + + public record Value(int flightDuration, Collection explosions) { + + public void write(ByteBuf buf) { + ProtocolUtils.writeVarInt(buf, this.flightDuration); + LimboProtocolUtils.writeCollection(buf, this.explosions, 256, FireworkExplosionComponent.Value::write); + } + + public static Value read(ByteBuf buf) { + return new Value(ProtocolUtils.readVarInt(buf), LimboProtocolUtils.readCollection(buf, 256, FireworkExplosionComponent.Value::read)); + } + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/FoodPropertiesComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/FoodPropertiesComponent.java new file mode 100644 index 00000000..14bfe5bc --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/FoodPropertiesComponent.java @@ -0,0 +1,67 @@ +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.Collection; +import net.elytrium.limboapi.api.protocol.packets.data.ItemStack; +import net.elytrium.limboapi.protocol.util.LimboProtocolUtils; +import net.elytrium.limboapi.server.item.AbstractItemComponent; +import net.elytrium.limboapi.server.item.type.data.MobEffect; +import org.checkerframework.checker.nullness.qual.Nullable; + +public class FoodPropertiesComponent extends AbstractItemComponent { + + @Override + public void read(ByteBuf buf, ProtocolVersion version) { + this.setValue(Value.read(buf, version)); + } + + @Override + public void write(ByteBuf buf, ProtocolVersion version) { + this.getValue().write(buf, version); + } + + public record Value(int nutrition, float saturation, boolean canAlwaysEat, float eatSeconds, @Nullable ItemStack usingConvertsTo, @Nullable Collection effects) { + + public void write(ByteBuf buf, ProtocolVersion version) { + ProtocolUtils.writeVarInt(buf, this.nutrition); + buf.writeFloat(this.saturation); + buf.writeBoolean(this.canAlwaysEat); + boolean lt1_21_2 = version.lessThan(ProtocolVersion.MINECRAFT_1_21_2); + if (lt1_21_2) { + buf.writeFloat(this.eatSeconds); + } + if (version == ProtocolVersion.MINECRAFT_1_21) { + LimboProtocolUtils.writeOptional(buf, this.usingConvertsTo, itemStack -> LimboProtocolUtils.writeItemStack(buf, version, itemStack, false)); + } + if (lt1_21_2) { + LimboProtocolUtils.writeCollection(buf, this.effects, PossibleEffect::write); + } + } + + public static Value read(ByteBuf buf, ProtocolVersion version) { + boolean lt1_21_2 = version.lessThan(ProtocolVersion.MINECRAFT_1_21_2); + return new Value( + ProtocolUtils.readVarInt(buf), + buf.readFloat(), + buf.readBoolean(), + lt1_21_2 ? buf.readFloat() : 1.6F/*default value, source: ViaVersion*/, + version == ProtocolVersion.MINECRAFT_1_21 ? LimboProtocolUtils.readOptional(buf, () -> LimboProtocolUtils.readItemStack(buf, version, false)) : null, + lt1_21_2 ? LimboProtocolUtils.readCollection(buf, PossibleEffect::read) : null + ); + } + + public record PossibleEffect(MobEffect effect, float probability) { + + public void write(ByteBuf buf) { + this.effect.write(buf); + buf.writeFloat(this.probability); + } + + public static PossibleEffect read(ByteBuf buf) { + return new PossibleEffect(MobEffect.read(buf), buf.readFloat()); + } + } + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/HolderSetComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/HolderSetComponent.java new file mode 100644 index 00000000..10f37f9e --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/HolderSetComponent.java @@ -0,0 +1,19 @@ +package net.elytrium.limboapi.server.item.type; + +import com.velocitypowered.api.network.ProtocolVersion; +import io.netty.buffer.ByteBuf; +import net.elytrium.limboapi.server.item.AbstractItemComponent; +import net.elytrium.limboapi.server.item.type.data.HolderSet; + +public class HolderSetComponent extends AbstractItemComponent { + + @Override + public void read(ByteBuf buf, ProtocolVersion version) { + this.setValue(HolderSet.read(buf)); + } + + @Override + public void write(ByteBuf buf, ProtocolVersion version) { + this.getValue().write(buf); + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/InstrumentComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/InstrumentComponent.java new file mode 100644 index 00000000..60ba7f9e --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/InstrumentComponent.java @@ -0,0 +1,65 @@ +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.elytrium.limboapi.server.item.AbstractItemComponent; +import net.elytrium.limboapi.server.item.type.data.SoundEvent; +import net.elytrium.limboapi.server.item.type.data.VersionLessComponentHolder; + +public class InstrumentComponent extends AbstractItemComponent { + + @Override + public void read(ByteBuf buf, ProtocolVersion version) { + this.setValue(Value.read(buf, version)); + } + + @Override + public void write(ByteBuf buf, ProtocolVersion version) { + this.getValue().write(buf, version); + } + + public sealed interface Value permits Value.ReferenceValue, Value.DirectValue { + + void write(ByteBuf buf, ProtocolVersion version); + + static Value read(ByteBuf buf, ProtocolVersion version) { + int i = ProtocolUtils.readVarInt(buf); + return i == 0 ? DirectValue.read(buf, version) : new ReferenceValue(i - 1); + } + + record ReferenceValue(int id) implements Value { + + @Override + public void write(ByteBuf buf, ProtocolVersion version) { + ProtocolUtils.writeVarInt(buf, this.id + 1); + } + } + + record DirectValue(SoundEvent soundEvent, int useDuration, float range, VersionLessComponentHolder description) implements Value { + + @Override + public void write(ByteBuf buf, ProtocolVersion version) { + this.soundEvent.write(buf); + ProtocolUtils.writeVarInt(buf, this.useDuration); + buf.writeFloat(this.range); + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) { + if (this.description == null) { + throw new IllegalArgumentException("description cannot be null on >=1.21.2!"); + } + + this.description.write(buf, version); + } + } + + public static DirectValue read(ByteBuf buf, ProtocolVersion version) { + return new DirectValue( + SoundEvent.read(buf), + ProtocolUtils.readVarInt(buf), + buf.readFloat(), + version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2) ? VersionLessComponentHolder.read(buf, version) : null + ); + } + } + } +} 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/IntComponent.java similarity index 72% rename from plugin/src/main/java/net/elytrium/limboapi/server/item/type/IntItemComponent.java rename to plugin/src/main/java/net/elytrium/limboapi/server/item/type/IntComponent.java index 190068f6..ebce89ea 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/IntItemComponent.java +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/IntComponent.java @@ -19,15 +19,17 @@ import com.velocitypowered.api.network.ProtocolVersion; import io.netty.buffer.ByteBuf; +import net.elytrium.limboapi.server.item.AbstractItemComponent; -public class IntItemComponent extends WriteableItemComponent { +public class IntComponent extends AbstractItemComponent { - public IntItemComponent(String name) { - super(name); + @Override + public void read(ByteBuf buf, ProtocolVersion version) { + this.setValue(buf.readInt()); } @Override - public void write(ProtocolVersion version, ByteBuf buffer) { - buffer.writeInt(this.getValue()); + public void write(ByteBuf buf, ProtocolVersion version) { + buf.writeInt(this.getValue()); } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/ItemStackComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/ItemStackComponent.java new file mode 100644 index 00000000..38418876 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/ItemStackComponent.java @@ -0,0 +1,26 @@ +package net.elytrium.limboapi.server.item.type; + +import com.velocitypowered.api.network.ProtocolVersion; +import io.netty.buffer.ByteBuf; +import net.elytrium.limboapi.api.protocol.packets.data.ItemStack; +import net.elytrium.limboapi.protocol.util.LimboProtocolUtils; +import net.elytrium.limboapi.server.item.AbstractItemComponent; + +public class ItemStackComponent extends AbstractItemComponent { + + private final boolean allowEmpty; + + public ItemStackComponent(boolean allowEmpty) { + this.allowEmpty = allowEmpty; + } + + @Override + public void read(ByteBuf buf, ProtocolVersion version) { + this.setValue(LimboProtocolUtils.readItemStack(buf, version, this.allowEmpty)); + } + + @Override + public void write(ByteBuf buf, ProtocolVersion version) { + LimboProtocolUtils.writeItemStack(buf, version, this.getValue(), this.allowEmpty); + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/ItemStackListComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/ItemStackListComponent.java new file mode 100644 index 00000000..1b6aa3e9 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/ItemStackListComponent.java @@ -0,0 +1,33 @@ +package net.elytrium.limboapi.server.item.type; + +import com.velocitypowered.api.network.ProtocolVersion; +import io.netty.buffer.ByteBuf; +import java.util.Collection; +import net.elytrium.limboapi.api.protocol.packets.data.ItemStack; +import net.elytrium.limboapi.protocol.util.LimboProtocolUtils; +import net.elytrium.limboapi.server.item.AbstractItemComponent; + +public class ItemStackListComponent extends AbstractItemComponent> { + + private final boolean allowEmpty; + private final int limit; + + public ItemStackListComponent(boolean allowEmpty) { + this(allowEmpty, Integer.MAX_VALUE); + } + + public ItemStackListComponent(boolean allowEmpty, int limit) { + this.allowEmpty = allowEmpty; + this.limit = limit; + } + + @Override + public void read(ByteBuf buf, ProtocolVersion version) { + this.setValue(LimboProtocolUtils.readCollection(buf, this.limit, () -> LimboProtocolUtils.readItemStack(buf, version, this.allowEmpty))); + } + + @Override + public void write(ByteBuf buf, ProtocolVersion version) { + LimboProtocolUtils.writeCollection(buf, this.getValue(), this.limit, itemStack -> LimboProtocolUtils.writeItemStack(buf, version, itemStack, this.allowEmpty)); + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/JukeboxPlayableComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/JukeboxPlayableComponent.java new file mode 100644 index 00000000..b74fec97 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/JukeboxPlayableComponent.java @@ -0,0 +1,70 @@ +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.elytrium.limboapi.protocol.util.LimboProtocolUtils; +import net.elytrium.limboapi.server.item.AbstractItemComponent; +import net.elytrium.limboapi.server.item.type.data.SoundEvent; +import net.elytrium.limboapi.server.item.type.data.VersionLessComponentHolder; + +public class JukeboxPlayableComponent extends AbstractItemComponent { + + @Override + public void read(ByteBuf buf, ProtocolVersion version) { + this.setValue(JukeboxPlayableComponent.Value.read(buf, version)); + } + + @Override + public void write(ByteBuf buf, ProtocolVersion version) { + this.getValue().write(buf, version); + } + + public record Value(JukeboxSong song, boolean showInTooltip) { + + public void write(ByteBuf buf, ProtocolVersion version) { + this.song.write(buf, version); + buf.writeBoolean(this.showInTooltip); + } + + public static Value read(ByteBuf buf, ProtocolVersion version) { + return new Value(JukeboxSong.read(buf, version), buf.readBoolean()); + } + + public sealed interface JukeboxSong permits JukeboxSong.DirectJukeboxSong, JukeboxSong.ReferenceJukeboxSong { + + void write(ByteBuf buf, ProtocolVersion version); + + static JukeboxSong read(ByteBuf buf, ProtocolVersion version) { + return LimboProtocolUtils.readEither(buf, () -> DirectJukeboxSong.read(buf, version), () -> ReferenceJukeboxSong.read(buf)); + } + + record DirectJukeboxSong(SoundEvent soundEvent, VersionLessComponentHolder description, float lengthInSeconds, int comparatorOutput) implements JukeboxSong { + + @Override + public void write(ByteBuf buf, ProtocolVersion version) { + this.soundEvent.write(buf); + this.description.write(buf, version); + buf.writeFloat(this.lengthInSeconds); + ProtocolUtils.writeVarInt(buf, this.comparatorOutput); + } + + public static DirectJukeboxSong read(ByteBuf buf, ProtocolVersion version) { + return new DirectJukeboxSong(SoundEvent.read(buf), VersionLessComponentHolder.read(buf, version), buf.readFloat(), ProtocolUtils.readVarInt(buf)); + } + } + + record ReferenceJukeboxSong(String id) implements JukeboxSong { + + @Override + public void write(ByteBuf buf, ProtocolVersion version) { + ProtocolUtils.writeString(buf, this.id); + } + + public static ReferenceJukeboxSong read(ByteBuf buf) { + return new ReferenceJukeboxSong(ProtocolUtils.readString(buf)); + } + } + } + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/LodestoneTrackerComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/LodestoneTrackerComponent.java new file mode 100644 index 00000000..486352e1 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/LodestoneTrackerComponent.java @@ -0,0 +1,33 @@ +package net.elytrium.limboapi.server.item.type; + +import com.velocitypowered.api.network.ProtocolVersion; +import io.netty.buffer.ByteBuf; +import net.elytrium.limboapi.api.protocol.packets.data.GlobalPos; +import net.elytrium.limboapi.protocol.util.LimboProtocolUtils; +import net.elytrium.limboapi.server.item.AbstractItemComponent; +import org.checkerframework.checker.nullness.qual.Nullable; + +public class LodestoneTrackerComponent extends AbstractItemComponent { + + @Override + public void read(ByteBuf buf, ProtocolVersion version) { + this.setValue(Value.read(buf, version)); + } + + @Override + public void write(ByteBuf buf, ProtocolVersion version) { + this.getValue().write(buf, version); + } + + public record Value(@Nullable GlobalPos target, boolean tracked) { + + public void write(ByteBuf buf, ProtocolVersion version) { + LimboProtocolUtils.writeOptional(buf, this.target, value -> LimboProtocolUtils.writeGlobalPos(buf, version, value)); + buf.writeBoolean(this.tracked); + } + + public static Value read(ByteBuf buf, ProtocolVersion version) { + return new Value(LimboProtocolUtils.readOptional(buf, () -> LimboProtocolUtils.readGlobalPos(buf, version)), buf.readBoolean()); + } + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/PotionContentsComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/PotionContentsComponent.java new file mode 100644 index 00000000..313530e7 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/PotionContentsComponent.java @@ -0,0 +1,44 @@ +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.Collection; +import net.elytrium.limboapi.protocol.util.LimboProtocolUtils; +import net.elytrium.limboapi.server.item.AbstractItemComponent; +import net.elytrium.limboapi.server.item.type.data.MobEffect; +import org.checkerframework.checker.nullness.qual.Nullable; + +public class PotionContentsComponent extends AbstractItemComponent { + + @Override + public void read(ByteBuf buf, ProtocolVersion version) { + this.setValue(Value.read(buf, version)); + } + + @Override + public void write(ByteBuf buf, ProtocolVersion version) { + this.getValue().write(buf, version); + } + + public record Value(@Nullable Integer potion, @Nullable Integer customColor, Collection customEffects, @Nullable String customName) { + + public void write(ByteBuf buf, ProtocolVersion version) { + LimboProtocolUtils.writeOptional(buf, this.potion, ProtocolUtils::writeVarInt); + LimboProtocolUtils.writeOptional(buf, this.customColor, ByteBuf::writeInt); + LimboProtocolUtils.writeCollection(buf, this.customEffects, MobEffect::write); + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) { + LimboProtocolUtils.writeOptional(buf, this.customName, ProtocolUtils::writeString); + } + } + + public static Value read(ByteBuf buf, ProtocolVersion version) { + return new Value( + LimboProtocolUtils.readOptional(buf, ProtocolUtils::readVarInt), + LimboProtocolUtils.readOptional(buf, ByteBuf::readInt), + LimboProtocolUtils.readCollection(buf, MobEffect::read), + version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2) ? LimboProtocolUtils.readOptional(buf, ProtocolUtils::readString) : null + ); + } + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/ResolvableProfileComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/ResolvableProfileComponent.java new file mode 100644 index 00000000..2f18265c --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/ResolvableProfileComponent.java @@ -0,0 +1,58 @@ +/* + * 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.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.List; +import java.util.UUID; +import net.elytrium.limboapi.protocol.util.LimboProtocolUtils; +import net.elytrium.limboapi.server.item.AbstractItemComponent; +import org.checkerframework.checker.nullness.qual.Nullable; + +public class ResolvableProfileComponent extends AbstractItemComponent { + + @Override + public void read(ByteBuf buf, ProtocolVersion version) { + this.setValue(Value.read(buf)); + } + + @Override + public void write(ByteBuf buf, ProtocolVersion version) { + this.getValue().write(buf); + } + + public record Value(@Nullable String name, @Nullable UUID id, List properties) { + + public void write(ByteBuf buf) { + LimboProtocolUtils.writeOptional(buf, this.name, ProtocolUtils::writeString); + LimboProtocolUtils.writeOptional(buf, this.id, ProtocolUtils::writeUuid); + ProtocolUtils.writeProperties(buf, this.properties); + } + + public static Value read(ByteBuf buf) { + return new Value( + LimboProtocolUtils.readOptional(buf, () -> ProtocolUtils.readString(buf, 16)), + LimboProtocolUtils.readOptional(buf, ProtocolUtils::readUuid), + ProtocolUtils.readProperties(buf) + ); + } + } +} 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/StringComponent.java similarity index 71% rename from plugin/src/main/java/net/elytrium/limboapi/server/item/type/StringItemComponent.java rename to plugin/src/main/java/net/elytrium/limboapi/server/item/type/StringComponent.java index b1679fac..9ed5ec1c 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/StringItemComponent.java +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/StringComponent.java @@ -20,15 +20,17 @@ import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; +import net.elytrium.limboapi.server.item.AbstractItemComponent; -public class StringItemComponent extends WriteableItemComponent { +public class StringComponent extends AbstractItemComponent { - public StringItemComponent(String name) { - super(name); + @Override + public void read(ByteBuf buf, ProtocolVersion version) { + this.setValue(ProtocolUtils.readString(buf)); } @Override - public void write(ProtocolVersion version, ByteBuf buffer) { - ProtocolUtils.writeString(buffer, this.getValue()); + public void write(ByteBuf buf, ProtocolVersion version) { + ProtocolUtils.writeString(buf, this.getValue()); } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/SuspiciousStewEffectsComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/SuspiciousStewEffectsComponent.java new file mode 100644 index 00000000..5123245c --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/SuspiciousStewEffectsComponent.java @@ -0,0 +1,33 @@ +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.Collection; +import net.elytrium.limboapi.protocol.util.LimboProtocolUtils; +import net.elytrium.limboapi.server.item.AbstractItemComponent; + +public class SuspiciousStewEffectsComponent extends AbstractItemComponent> { + + @Override + public void read(ByteBuf buf, ProtocolVersion version) { + this.setValue(LimboProtocolUtils.readCollection(buf, Entry::read)); + } + + @Override + public void write(ByteBuf buf, ProtocolVersion version) { + LimboProtocolUtils.writeCollection(buf, this.getValue(), Entry::write); + } + + public record Entry(int effect, int duration) { + + public void write(ByteBuf buf) { + ProtocolUtils.writeVarInt(buf, this.effect); + ProtocolUtils.writeVarInt(buf, this.duration); + } + + public static Entry read(ByteBuf buf) { + return new Entry(ProtocolUtils.readVarInt(buf), ProtocolUtils.readVarInt(buf)); + } + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/TextComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/TextComponent.java new file mode 100644 index 00000000..5eb78543 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/TextComponent.java @@ -0,0 +1,77 @@ +/* + * 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.type; + +import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; +import io.netty.buffer.ByteBuf; +import net.elytrium.limboapi.server.item.AbstractItemComponent; +import net.kyori.adventure.text.Component; + +public class TextComponent extends AbstractItemComponent { // TODO VersionLessComponentHolder + + @Override + public void read(ByteBuf buf, ProtocolVersion version) { + this.setValue(ComponentHolder.read(buf, version).getComponent()); + //this.setValue(VersionLessComponentHolder.read(buf, version)); + } + + @Override + public void write(ByteBuf buf, ProtocolVersion version) { + new ComponentHolder(version, this.getValue()).write(buf); + //this.getValue().write(buf, version); + } + /* + + static Object readComponent(ByteBuf buf, ProtocolVersion version) { + return version.noLessThan(ProtocolVersion.MINECRAFT_1_20_3) ? ProtocolUtils.readBinaryTag(buf, version, BinaryTagIO.reader()) : ProtocolUtils.readString(buf); + } + + static void writeComponent(ByteBuf buf, ProtocolVersion version, Object value) { + if (value instanceof ComponentHolder holder) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) { + ProtocolUtils.writeBinaryTag(buf, version, holder.getBinaryTag()); + } else { + ProtocolUtils.writeString(buf, holder.getJson()); + } + } else if (value instanceof Component component) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) { + ProtocolUtils.writeBinaryTag(buf, version, ComponentHolder.serialize(GsonComponentSerializer.gson().serializeToTree(component))); + } else { + ProtocolUtils.writeString(buf, ProtocolUtils.getJsonChatSerializer(version).serialize(component)); + } + } else if (value instanceof BinaryTag tag) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) { + ProtocolUtils.writeBinaryTag(buf, version, tag); + } else { + ProtocolUtils.writeString(buf, ComponentHolder.deserialize(tag).toString()); + } + } else if (value instanceof String string) { + if (string.charAt(0) != '{') { + throw new IllegalArgumentException("Json string expected"); + } + + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) { + ProtocolUtils.writeBinaryTag(buf, version, ComponentHolder.serialize(GsonComponentSerializer.gson().serializeToTree(ProtocolUtils.getJsonChatSerializer(version).deserialize(string)))); + } else { + ProtocolUtils.writeString(buf, string); + } + } + } + */ +} 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/TextListComponent.java similarity index 58% rename from plugin/src/main/java/net/elytrium/limboapi/server/item/type/ComponentItemComponent.java rename to plugin/src/main/java/net/elytrium/limboapi/server/item/type/TextListComponent.java index 8bdf4fbc..98fc2054 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/ComponentItemComponent.java +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/TextListComponent.java @@ -20,16 +20,25 @@ 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; +import java.util.Collection; +import net.elytrium.limboapi.protocol.util.LimboProtocolUtils; +import net.elytrium.limboapi.server.item.AbstractItemComponent; -public class ComponentItemComponent extends WriteableItemComponent { +public class TextListComponent extends AbstractItemComponent> { - public ComponentItemComponent(String name) { - super(name); + private final int limit; + + public TextListComponent(int limit) { + this.limit = limit; + } + + @Override + public void read(ByteBuf buf, ProtocolVersion version) { + this.setValue(LimboProtocolUtils.readCollection(buf, this.limit, () -> ComponentHolder.read(buf, version))); } @Override - public void write(ProtocolVersion version, ByteBuf buffer) { - new ComponentHolder(version, this.getValue()).write(buffer); + public void write(ByteBuf buf, ProtocolVersion version) { + LimboProtocolUtils.writeCollection(buf, this.getValue(), component -> component.write(buf)); } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/ToolComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/ToolComponent.java new file mode 100644 index 00000000..e00b2263 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/ToolComponent.java @@ -0,0 +1,49 @@ +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.Collection; +import net.elytrium.limboapi.protocol.util.LimboProtocolUtils; +import net.elytrium.limboapi.server.item.AbstractItemComponent; +import net.elytrium.limboapi.server.item.type.data.HolderSet; +import org.checkerframework.checker.nullness.qual.Nullable; + +public class ToolComponent extends AbstractItemComponent { + + @Override + public void read(ByteBuf buf, ProtocolVersion version) { + this.setValue(Value.read(buf)); + } + + @Override + public void write(ByteBuf buf, ProtocolVersion version) { + this.getValue().write(buf); + } + + public record Value(Collection rules, float defaultMiningSpeed, int damagePerBlock) { + + public void write(ByteBuf buf) { + LimboProtocolUtils.writeCollection(buf, this.rules, Rule::write); + buf.writeFloat(this.defaultMiningSpeed); + ProtocolUtils.writeVarInt(buf, this.damagePerBlock); + } + + public static Value read(ByteBuf buf) { + return new Value(LimboProtocolUtils.readCollection(buf, Rule::read), buf.readFloat(), ProtocolUtils.readVarInt(buf)); + } + + public record Rule(HolderSet blocks, @Nullable Float speed, @Nullable Boolean correctForDrops) { + + public void write(ByteBuf buf) { + this.blocks.write(buf); + LimboProtocolUtils.writeOptional(buf, this.speed, buf::writeFloat); + LimboProtocolUtils.writeOptional(buf, this.correctForDrops, buf::writeBoolean); + } + + public static Rule read(ByteBuf buf) { + return new Rule(HolderSet.read(buf), LimboProtocolUtils.readOptional(buf, buf::readFloat), LimboProtocolUtils.readOptional(buf, buf::readBoolean)); + } + } + } +} 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/UnitComponent.java similarity index 70% rename from plugin/src/main/java/net/elytrium/limboapi/server/item/type/EmptyItemComponent.java rename to plugin/src/main/java/net/elytrium/limboapi/server/item/type/UnitComponent.java index 153dc30e..7e9a1f13 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/EmptyItemComponent.java +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/UnitComponent.java @@ -19,16 +19,23 @@ import com.velocitypowered.api.network.ProtocolVersion; import io.netty.buffer.ByteBuf; -import net.kyori.adventure.nbt.BinaryTag; +import net.elytrium.limboapi.server.item.AbstractItemComponent; -public class EmptyItemComponent extends WriteableItemComponent { +public class UnitComponent extends AbstractItemComponent { + + public static final UnitComponent INSTANCE = new UnitComponent(); + + private UnitComponent() { + + } + + @Override + public void read(ByteBuf buf, ProtocolVersion version) { - public EmptyItemComponent(String name) { - super(name); } @Override - public void write(ProtocolVersion version, ByteBuf buffer) { + public void write(ByteBuf buf, ProtocolVersion version) { } } diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/UseCooldownComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/UseCooldownComponent.java new file mode 100644 index 00000000..7ff11e41 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/UseCooldownComponent.java @@ -0,0 +1,33 @@ +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.elytrium.limboapi.protocol.util.LimboProtocolUtils; +import net.elytrium.limboapi.server.item.AbstractItemComponent; +import org.checkerframework.checker.nullness.qual.Nullable; + +public class UseCooldownComponent extends AbstractItemComponent { + + @Override + public void read(ByteBuf buf, ProtocolVersion version) { + this.setValue(Value.read(buf)); + } + + @Override + public void write(ByteBuf buf, ProtocolVersion version) { + this.getValue().write(buf); + } + + public record Value(float seconds, @Nullable String cooldownGroup) { + + public void write(ByteBuf buf) { + buf.writeFloat(this.seconds); + LimboProtocolUtils.writeOptional(buf, this.cooldownGroup, ProtocolUtils::writeString); + } + + public static Value read(ByteBuf buf) { + return new Value(buf.readFloat(), LimboProtocolUtils.readOptional(buf, ProtocolUtils::readString)); + } + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/VarIntArrayComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/VarIntArrayComponent.java new file mode 100644 index 00000000..98e4be03 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/VarIntArrayComponent.java @@ -0,0 +1,26 @@ +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.elytrium.limboapi.protocol.util.LimboProtocolUtils; +import net.elytrium.limboapi.server.item.AbstractItemComponent; + +public class VarIntArrayComponent extends AbstractItemComponent { + + private final int limit; + + public VarIntArrayComponent(int limit) { + this.limit = limit; + } + + @Override + public void read(ByteBuf buf, ProtocolVersion version) { + this.setValue(LimboProtocolUtils.readVarIntArray(buf, this.limit)); + } + + @Override + public void write(ByteBuf buf, ProtocolVersion version) { + LimboProtocolUtils.writeVarIntArray(buf, this.getValue(), this.limit); + } +} 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/VarIntComponent.java similarity index 71% rename from plugin/src/main/java/net/elytrium/limboapi/server/item/type/TagItemComponent.java rename to plugin/src/main/java/net/elytrium/limboapi/server/item/type/VarIntComponent.java index 903ca392..cd3d0619 100644 --- a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/TagItemComponent.java +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/VarIntComponent.java @@ -20,16 +20,17 @@ import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; -import net.kyori.adventure.nbt.BinaryTag; +import net.elytrium.limboapi.server.item.AbstractItemComponent; -public class TagItemComponent extends WriteableItemComponent { +public class VarIntComponent extends AbstractItemComponent { - public TagItemComponent(String name) { - super(name); + @Override + public void read(ByteBuf buf, ProtocolVersion version) { + this.setValue(ProtocolUtils.readVarInt(buf)); } @Override - public void write(ProtocolVersion version, ByteBuf buffer) { - ProtocolUtils.writeBinaryTag(buffer, version, this.getValue()); + public void write(ByteBuf buf, ProtocolVersion version) { + ProtocolUtils.writeVarInt(buf, 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 18756f53..00000000 --- a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/VarIntItemComponent.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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.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/WritableBookContentComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/WritableBookContentComponent.java new file mode 100644 index 00000000..56c0afbf --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/WritableBookContentComponent.java @@ -0,0 +1,21 @@ +package net.elytrium.limboapi.server.item.type; + +import com.velocitypowered.api.network.ProtocolVersion; +import io.netty.buffer.ByteBuf; +import java.util.Collection; +import net.elytrium.limboapi.protocol.util.LimboProtocolUtils; +import net.elytrium.limboapi.server.item.AbstractItemComponent; +import net.elytrium.limboapi.server.item.type.data.FilterableString; + +public class WritableBookContentComponent extends AbstractItemComponent> { + + @Override + public void read(ByteBuf buf, ProtocolVersion version) { + this.setValue(LimboProtocolUtils.readCollection(buf, 100, FilterableString::read)); + } + + @Override + public void write(ByteBuf buf, ProtocolVersion version) { + LimboProtocolUtils.writeCollection(buf, this.getValue(), 100, FilterableString::write); + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/WrittenBookContentComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/WrittenBookContentComponent.java new file mode 100644 index 00000000..f9e1da45 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/WrittenBookContentComponent.java @@ -0,0 +1,42 @@ +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.Collection; +import net.elytrium.limboapi.protocol.util.LimboProtocolUtils; +import net.elytrium.limboapi.server.item.AbstractItemComponent; +import net.elytrium.limboapi.server.item.type.data.FilterableComponent; +import net.elytrium.limboapi.server.item.type.data.FilterableString; + +public class WrittenBookContentComponent extends AbstractItemComponent { + + @Override + public void read(ByteBuf buf, ProtocolVersion version) { + this.setValue(Value.read(buf, version)); + } + + @Override + public void write(ByteBuf buf, ProtocolVersion version) { + this.getValue().write(buf, version); + } + + public record Value(FilterableString title, String author, int generation, Collection pages, boolean resolved) { + + public void write(ByteBuf buf, ProtocolVersion version) { + this.title.write(buf); + ProtocolUtils.writeString(buf, this.author); + ProtocolUtils.writeVarInt(buf, this.generation); + LimboProtocolUtils.writeCollection(buf, this.pages, page -> page.write(buf, version)); + buf.writeBoolean(this.resolved); + } + + public static Value read(ByteBuf buf, ProtocolVersion version) { + return new Value( + FilterableString.read(buf), ProtocolUtils.readString(buf), ProtocolUtils.readVarInt(buf), + LimboProtocolUtils.readCollection(buf, () -> FilterableComponent.read(buf, version)), + buf.readBoolean() + ); + } + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/data/ConsumeEffect.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/data/ConsumeEffect.java new file mode 100644 index 00000000..03f50327 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/data/ConsumeEffect.java @@ -0,0 +1,86 @@ +package net.elytrium.limboapi.server.item.type.data; + +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import java.util.Collection; +import net.elytrium.limboapi.protocol.util.LimboProtocolUtils; + +public sealed interface ConsumeEffect permits ConsumeEffect.ApplyStatusEffects, ConsumeEffect.RemoveStatusEffects, ConsumeEffect.ClearAllStatusEffects, + ConsumeEffect.TeleportRandomly, ConsumeEffect.PlaySound { + + void write(ByteBuf buf); + + static ConsumeEffect read(ByteBuf buf) { + int id = ProtocolUtils.readVarInt(buf); + return switch (id) { + case 0 -> ApplyStatusEffects.read(buf); + case 1 -> RemoveStatusEffects.read(buf); + case 2 -> ClearAllStatusEffects.read(); + case 3 -> TeleportRandomly.read(buf); + case 4 -> PlaySound.read(buf); + default -> throw new IllegalStateException("Unexpected value: " + id); + }; + } + + record ApplyStatusEffects(Collection effects, float probability) implements ConsumeEffect { + + public void write(ByteBuf buf) { + ProtocolUtils.writeVarInt(buf, 0); + LimboProtocolUtils.writeCollection(buf, this.effects, MobEffect::write); + buf.writeFloat(this.probability); + } + + static ConsumeEffect read(ByteBuf buf) { + return new ApplyStatusEffects(LimboProtocolUtils.readCollection(buf, MobEffect::read), buf.readFloat()); + } + } + + record RemoveStatusEffects(HolderSet effects) implements ConsumeEffect { + + public void write(ByteBuf buf) { + ProtocolUtils.writeVarInt(buf, 1); + this.effects.write(buf); + } + + static ConsumeEffect read(ByteBuf buf) { + return new RemoveStatusEffects(HolderSet.read(buf)); + } + } + + record ClearAllStatusEffects() implements ConsumeEffect { + + public static final ClearAllStatusEffects INSTANCE = new ClearAllStatusEffects(); + + public void write(ByteBuf buf) { + ProtocolUtils.writeVarInt(buf, 2); + } + + static ConsumeEffect read() { + return ClearAllStatusEffects.INSTANCE; + } + } + + record TeleportRandomly(float diameter) implements ConsumeEffect { + + public void write(ByteBuf buf) { + ProtocolUtils.writeVarInt(buf, 3); + buf.writeFloat(this.diameter); + } + + static ConsumeEffect read(ByteBuf buf) { + return new TeleportRandomly(buf.readFloat()); + } + } + + record PlaySound(SoundEvent sound) implements ConsumeEffect { + + public void write(ByteBuf buf) { + ProtocolUtils.writeVarInt(buf, 4); + this.sound.write(buf); + } + + static ConsumeEffect read(ByteBuf buf) { + return new PlaySound(SoundEvent.read(buf)); + } + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/data/FilterableComponent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/data/FilterableComponent.java new file mode 100644 index 00000000..c65cdec6 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/data/FilterableComponent.java @@ -0,0 +1,18 @@ +package net.elytrium.limboapi.server.item.type.data; + +import com.velocitypowered.api.network.ProtocolVersion; +import io.netty.buffer.ByteBuf; +import net.elytrium.limboapi.protocol.util.LimboProtocolUtils; +import org.checkerframework.checker.nullness.qual.Nullable; + +public record FilterableComponent(VersionLessComponentHolder raw, @Nullable VersionLessComponentHolder filtered) { + + public void write(ByteBuf buf, ProtocolVersion version) { + this.raw.write(buf, version); + LimboProtocolUtils.writeOptional(buf, this.filtered, filtered -> filtered.write(buf, version)); + } + + public static FilterableComponent read(ByteBuf buf, ProtocolVersion version) { + return new FilterableComponent(VersionLessComponentHolder.read(buf, version), LimboProtocolUtils.readOptional(buf, () -> VersionLessComponentHolder.read(buf, version))); + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/data/FilterableString.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/data/FilterableString.java new file mode 100644 index 00000000..ac1b8b04 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/data/FilterableString.java @@ -0,0 +1,18 @@ +package net.elytrium.limboapi.server.item.type.data; + +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import net.elytrium.limboapi.protocol.util.LimboProtocolUtils; +import org.checkerframework.checker.nullness.qual.Nullable; + +public record FilterableString(String raw, @Nullable String filtered) { + + public void write(ByteBuf buf) { + ProtocolUtils.writeString(buf, this.raw); + LimboProtocolUtils.writeOptional(buf, this.filtered, ProtocolUtils::writeString); + } + + public static FilterableString read(ByteBuf buf) { + return new FilterableString(ProtocolUtils.readString(buf, 1024), LimboProtocolUtils.readOptional(buf, () -> ProtocolUtils.readString(buf, 1024))); + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/data/HolderSet.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/data/HolderSet.java new file mode 100644 index 00000000..4e191992 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/data/HolderSet.java @@ -0,0 +1,47 @@ +package net.elytrium.limboapi.server.item.type.data; + +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; + +public sealed interface HolderSet permits HolderSet.NamedHolderSet, HolderSet.DirectHolderSet { + + void write(ByteBuf buf); + + static HolderSet read(ByteBuf buf) { + int i = ProtocolUtils.readVarInt(buf); + return i == 0 ? NamedHolderSet.read(buf) : DirectHolderSet.read(buf, i - 1); + } + + record NamedHolderSet(String key) implements HolderSet { + + @Override + public void write(ByteBuf buf) { + ProtocolUtils.writeVarInt(buf, 0); + ProtocolUtils.writeString(buf, this.key); + } + + public static NamedHolderSet read(ByteBuf buf) { + return new NamedHolderSet(ProtocolUtils.readString(buf)); + } + } + + record DirectHolderSet(int[] contents) implements HolderSet { + + @Override + public void write(ByteBuf buf) { + ProtocolUtils.writeVarInt(buf, this.contents.length + 1); + for (int block : this.contents) { + ProtocolUtils.writeVarInt(buf, block); + } + } + + public static DirectHolderSet read(ByteBuf buf, int amount) { + int[] blocks = new int[amount]; + for (int i = 0; i < amount; ++i) { + blocks[i] = ProtocolUtils.readVarInt(buf); + } + + return new DirectHolderSet(blocks); + } + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/data/MobEffect.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/data/MobEffect.java new file mode 100644 index 00000000..17da977a --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/data/MobEffect.java @@ -0,0 +1,39 @@ +package net.elytrium.limboapi.server.item.type.data; + +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import net.elytrium.limboapi.protocol.util.LimboProtocolUtils; +import org.checkerframework.checker.nullness.qual.Nullable; + +public record MobEffect(int effect, Details details) { + + public void write(ByteBuf buf) { + ProtocolUtils.writeVarInt(buf, this.effect); + this.details.write(buf); + } + + public static MobEffect read(ByteBuf buf) { + return new MobEffect(ProtocolUtils.readVarInt(buf), Details.read(buf)); + } + + public record Details(int amplifier, int duration, boolean ambient, boolean visible, boolean showIcon, @Nullable Details hiddenEffect) { + + public void write(ByteBuf buf) { + ProtocolUtils.writeVarInt(buf, this.amplifier); + ProtocolUtils.writeVarInt(buf, this.duration); + buf.writeBoolean(this.ambient); + buf.writeBoolean(this.visible); + buf.writeBoolean(this.showIcon); + LimboProtocolUtils.writeOptional(buf, this.hiddenEffect, Details::write); + } + + public static Details read(ByteBuf buf) { + return new Details( + ProtocolUtils.readVarInt(buf), + ProtocolUtils.readVarInt(buf), + buf.readBoolean(), buf.readBoolean(), buf.readBoolean(), + LimboProtocolUtils.readOptional(buf, Details::read) + ); + } + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/data/SoundEvent.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/data/SoundEvent.java new file mode 100644 index 00000000..392feccd --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/data/SoundEvent.java @@ -0,0 +1,37 @@ +package net.elytrium.limboapi.server.item.type.data; + +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; +import net.elytrium.limboapi.protocol.util.LimboProtocolUtils; +import org.checkerframework.checker.nullness.qual.Nullable; + +public sealed interface SoundEvent permits SoundEvent.ReferenceSoundEvent, SoundEvent.DirectSoundEvent { + + void write(ByteBuf buf); + + static SoundEvent read(ByteBuf buf) { + int i = ProtocolUtils.readVarInt(buf); + return i == 0 ? DirectSoundEvent.read(buf) : new ReferenceSoundEvent(i - 1); + } + + record DirectSoundEvent(String soundId, @Nullable Float range) implements SoundEvent { + + @Override + public void write(ByteBuf buf) { + ProtocolUtils.writeString(buf, this.soundId); + LimboProtocolUtils.writeOptional(buf, this.range, ByteBuf::writeFloat); + } + + public static DirectSoundEvent read(ByteBuf buf) { + return new DirectSoundEvent(ProtocolUtils.readString(buf), LimboProtocolUtils.readOptional(buf, ByteBuf::readFloat)); + } + } + + record ReferenceSoundEvent(int id) implements SoundEvent { + + @Override + public void write(ByteBuf buf) { + ProtocolUtils.writeVarInt(buf, this.id + 1); + } + } +} diff --git a/plugin/src/main/java/net/elytrium/limboapi/server/item/type/data/VersionLessComponentHolder.java b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/data/VersionLessComponentHolder.java new file mode 100644 index 00000000..33ea8864 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/item/type/data/VersionLessComponentHolder.java @@ -0,0 +1,87 @@ +package net.elytrium.limboapi.server.item.type.data; + +import com.google.gson.JsonElement; +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 net.elytrium.limboapi.LimboAPI; +import net.elytrium.limboapi.protocol.util.LimboProtocolUtils; +import net.kyori.adventure.nbt.BinaryTag; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; + +public class VersionLessComponentHolder { + + private final Component component; + private final String json; + private final BinaryTag binaryTag; + + public VersionLessComponentHolder(Component component) { + this.component = component; + this.json = null; + this.binaryTag = null; + } + + public VersionLessComponentHolder(String json) { + this.component = null; + this.json = json; + this.binaryTag = null; + } + + public VersionLessComponentHolder(BinaryTag binaryTag) { + this.component = null; + this.json = null; + this.binaryTag = binaryTag; + } + + public Component getComponent(ProtocolVersion version) { + if (this.component == null) { + if (this.json != null) { + return ProtocolUtils.getJsonChatSerializer(version).deserialize(this.json); + } else if (this.binaryTag != null) { + // TODO: replace this with adventure-text-serializer-nbt when velocity will + JsonElement json = ComponentHolder.deserialize(this.binaryTag); + try { + return ProtocolUtils.getJsonChatSerializer(version).deserializeFromTree(json); + } catch (Exception e) { + LimboAPI.getLogger().error("Error converting binary component to JSON component! Binary: {} JSON: {}", this.binaryTag, json, e); + throw e; + } + } + } + + return this.component; + } + + public String getJson(ProtocolVersion version) { + if (this.json == null) { + return ProtocolUtils.getJsonChatSerializer(version).serialize(this.getComponent(version)); + } + + return this.json; + } + + public BinaryTag getBinaryTag(ProtocolVersion version) { + if (this.binaryTag == null) { + // TODO: replace this with adventure-text-serializer-nbt when velocity will + return ComponentHolder.serialize(GsonComponentSerializer.gson().serializeToTree(this.getComponent(version))); + } + + return this.binaryTag; + } + + public void write(ByteBuf buf, ProtocolVersion version) { + if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) { + ProtocolUtils.writeBinaryTag(buf, version, this.getBinaryTag(version)); + } else { + ProtocolUtils.writeString(buf, this.getJson(version)); + } + } + + public static VersionLessComponentHolder read(ByteBuf buf, ProtocolVersion version) { + return version.noLessThan(ProtocolVersion.MINECRAFT_1_20_3) + ? new VersionLessComponentHolder(ProtocolUtils.readBinaryTag(buf, version, null)) + : new VersionLessComponentHolder(ProtocolUtils.readString(buf)); + } +} 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..ca4ef299 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/server/world/Particle.java @@ -0,0 +1,156 @@ +package net.elytrium.limboapi.server.world; + +public enum Particle { + + /** + * @deprecated Used only in [1.13-1.20.3] + */ + @Deprecated + AMBIENT_ENTITY_EFFECT, + ANGRY_VILLAGER, + ASH, + /** + * @deprecated Used only in [1.13-1.17.1] + */ + @Deprecated + BARRIER, + BLOCK, + BLOCK_CRUMBLE, + BLOCK_MARKER, + BUBBLE, + BUBBLE_COLUMN_UP, + BUBBLE_POP, + CAMPFIRE_COSY_SMOKE, + CAMPFIRE_SIGNAL_SMOKE, + CHERRY_LEAVES, + CLOUD, + COMPOSTER, + CRIMSON_SPORE, + CRIT, + CURRENT_DOWN, + DAMAGE_INDICATOR, + DOLPHIN, + DRAGON_BREATH, + /** + * @deprecated Used only in 1.19.4 + */ + @Deprecated + DRIPPING_CHERRY_LEAVES, + DRIPPING_DRIPSTONE_LAVA, + DRIPPING_DRIPSTONE_WATER, + DRIPPING_HONEY, + DRIPPING_LAVA, + DRIPPING_OBSIDIAN_TEAR, + DRIPPING_WATER, + DUST, + DUST_COLOR_TRANSITION, + DUST_PILLAR, + DUST_PLUME, + EFFECT, + EGG_CRACK, + ELDER_GUARDIAN, + ELECTRIC_SPARK, + ENCHANT, + ENCHANTED_HIT, + END_ROD, + ENTITY_EFFECT, + EXPLOSION, + EXPLOSION_EMITTER, + /** + * @deprecated Used only in 1.19.4 + */ + @Deprecated + FALLING_CHERRY_LEAVES, + FALLING_DRIPSTONE_LAVA, + FALLING_DRIPSTONE_WATER, + FALLING_DUST, + FALLING_HONEY, + FALLING_LAVA, + FALLING_NECTAR, + FALLING_OBSIDIAN_TEAR, + FALLING_SPORE_BLOSSOM, + FALLING_WATER, + FIREWORK, + FISHING, + FLAME, + FLASH, + GLOW, + GLOW_SQUID_INK, + GUST, + /** + * @deprecated Used only in 1.20.3 + */ + @Deprecated + GUST_DUST, + /** + * @deprecated Used only in 1.20.3 + */ + @Deprecated + GUST_EMITTER, + GUST_EMITTER_LARGE, + GUST_EMITTER_SMALL, + HAPPY_VILLAGER, + HEART, + INFESTED, + INSTANT_EFFECT, + ITEM, + ITEM_COBWEB, + ITEM_SLIME, + ITEM_SNOWBALL, + /** + * @deprecated Used only in 1.19.4 + */ + @Deprecated + LANDING_CHERRY_LEAVES, + LANDING_HONEY, + LANDING_LAVA, + LANDING_OBSIDIAN_TEAR, + LARGE_SMOKE, + LAVA, + /** + * @deprecated Used only in [1.17-1.17.1] + */ + @Deprecated + LIGHT, + MYCELIUM, + NAUTILUS, + NOTE, + OMINOUS_SPAWNING, + POOF, + PORTAL, + RAID_OMEN, + RAIN, + REVERSE_PORTAL, + SCRAPE, + SCULK_CHARGE, + SCULK_CHARGE_POP, + SCULK_SOUL, + SHRIEK, + SMALL_FLAME, + SMALL_GUST, + SMOKE, + SNEEZE, + SNOWFLAKE, + SONIC_BOOM, + SOUL, + SOUL_FIRE_FLAME, + SPIT, + SPLASH, + SPORE_BLOSSOM_AIR, + SQUID_INK, + SWEEP_ATTACK, + TOTEM_OF_UNDYING, + TRAIL, + TRIAL_OMEN, + TRIAL_SPAWNER_DETECTION, + TRIAL_SPAWNER_DETECTION_OMINOUS, + UNDERWATER, + VAULT_CONNECTION, + VIBRATION, + WARPED_SPORE, + WAX_OFF, + WAX_ON, + WHITE_ASH, + WHITE_SMOKE, + WITCH, +} 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 b09527c1..04157605 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,196 +17,74 @@ 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 java.util.EnumMap; import java.util.HashMap; import java.util.HashSet; +import java.util.List; 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.utils.JsonParser; 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)); - } - }); +public record SimpleBlock(String modernId, short blockId, short blockStateId, boolean air, boolean solid, boolean motionBlocking/*1.14+*/) implements VirtualBlock { - 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)))); + private static final ShortObjectHashMap MODERN_BLOCK_STATE_PROTOCOL_ID_MAP; + private static final Map, Short>> MODERN_BLOCK_STATE_STRING_MAP; + private static final EnumMap> MODERN_BLOCK_STATE_IDS_MAP; + private static final Map MODERN_BLOCK_STRING_MAP; + private static final ShortObjectHashMap> LEGACY_BLOCK_IDS_MAP; + private static final ShortObjectHashMap LEGACY_BLOCK_STATE_IDS_MAP; + private static final Map> DEFAULT_PROPERTIES_MAP; + private static final Map MODERN_ID_REMAP; - LEGACY_BLOCK_STATE_IDS_MAP.put((short) 0, AIR); + public static final SimpleBlock AIR = new SimpleBlock("minecraft:air", (short) 0, (short) 0, true, false, false); - LinkedTreeMap> modernMap = GSON.fromJson( - new InputStreamReader(Objects.requireNonNull(LimboAPI.class.getResourceAsStream("/mapping/blockstates_mapping.json")), StandardCharsets.UTF_8), - LinkedTreeMap.class - ); - - 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); - } - }); - - 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); - } - - 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(String modernId, Map properties, boolean air, boolean solid, boolean motionBlocking) { + this(modernId, SimpleBlock.transformId(modernId, properties), air, solid, motionBlocking); } - public SimpleBlock(boolean solid, boolean air, boolean motionBlocking, String modernID, short blockStateID) { - this(solid, air, motionBlocking, modernID, blockStateID, MODERN_BLOCK_STRING_MAP.get(modernID.split("\\[")[0])); + 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, short blockID) { - this.solid = solid; - this.air = air; - this.motionBlocking = motionBlocking; - this.modernID = modernID; - this.blockStateID = blockStateID; - this.blockID = blockID; + public SimpleBlock(String modernId, short blockStateId, boolean air, boolean solid, boolean motionBlocking) { + this(modernId, SimpleBlock.MODERN_BLOCK_STRING_MAP.get(modernId.indexOf('[') == -1 ? modernId : modernId.substring(0, modernId.indexOf('['))), blockStateId, air, solid, motionBlocking); } - 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 @@ -214,30 +92,59 @@ 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 { + LinkedTreeMap blockStates = JsonParser.parse(LimboAPI.class.getResourceAsStream("/mappings/block_states.json")); + MODERN_BLOCK_STATE_PROTOCOL_ID_MAP = new ShortObjectHashMap<>(blockStates.size()); + MODERN_BLOCK_STATE_STRING_MAP = new HashMap<>(blockStates.size()); + blockStates.forEach((key, number) -> { + short value = number.shortValue(); + SimpleBlock.MODERN_BLOCK_STATE_PROTOCOL_ID_MAP.put(value, key); + int index = key.indexOf('['); + if (index == -1) { + SimpleBlock.MODERN_BLOCK_STATE_STRING_MAP.computeIfAbsent(key, k -> new HashMap<>()).put(null, value); + } else { + SimpleBlock.MODERN_BLOCK_STATE_STRING_MAP.computeIfAbsent(key.substring(0, index), k -> new HashMap<>()).put(new HashSet<>(List.of(key.substring(index + 1, key.length() - 1).split(","))), value); + } + }); - @Override - public boolean isSolid() { - return this.solid; - } + LinkedTreeMap> blocksMappings = JsonParser.parse(LimboAPI.class.getResourceAsStream("/mappings/blocks_mappings.json")); + MODERN_BLOCK_STRING_MAP = new HashMap<>(blocksMappings.size()); + LEGACY_BLOCK_IDS_MAP = new ShortObjectHashMap<>(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; - } + LinkedTreeMap> blockStatesMappings = JsonParser.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, k -> new ShortObjectHashMap<>()).put(Short.parseShort(modernId), id); + } + }); - @Override - public boolean isMotionBlocking() { - return this.motionBlocking; + LinkedTreeMap> properties = JsonParser.parse(LimboAPI.class.getResourceAsStream("/mappings/default_block_properties.json")); + DEFAULT_PROPERTIES_MAP = new HashMap<>(properties.size()); + properties.forEach((key, value) -> SimpleBlock.DEFAULT_PROPERTIES_MAP.put(key, new HashMap<>(value))); + + MODERN_ID_REMAP = new HashMap<>(JsonParser.parse(LimboAPI.class.getResourceAsStream("/mappings/modern_block_id_remap.json"))); + + LinkedTreeMap legacyBlocks = JsonParser.parse(LimboAPI.class.getResourceAsStream("/mappings/legacy_blocks.json")); + LEGACY_BLOCK_STATE_IDS_MAP = new ShortObjectHashMap<>(legacyBlocks.size() + 1); + legacyBlocks.forEach((legacy, modern) -> SimpleBlock.LEGACY_BLOCK_STATE_IDS_MAP.put(Short.valueOf(legacy), SimpleBlock.solid(modern.shortValue()))); + SimpleBlock.LEGACY_BLOCK_STATE_IDS_MAP.put((short) 0, SimpleBlock.AIR); } - 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, Map.of()); } else { Map properties = new HashMap<>(); for (String property : deserializedModernId[1].split(",")) { @@ -245,19 +152,19 @@ public static VirtualBlock fromModernID(String modernID) { 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) { + Map defaultProperties = SimpleBlock.DEFAULT_PROPERTIES_MAP.get(modernId); if (defaultProperties == null || defaultProperties.isEmpty()) { - return transformID(modernID, (Set) null); + return SimpleBlock.transformId(modernId, (Set) null); } else { Set propertiesSet = new HashSet<>(); defaultProperties.forEach((key, value) -> { @@ -267,100 +174,84 @@ private static short transformID(String modernID, Map properties propertiesSet.add(key + "=" + value.toLowerCase(Locale.ROOT)); }); - return transformID(modernID, propertiesSet); + return 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, Set properties) { + Map, Short> 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(); - } - - Short id; - if (properties == null || properties.isEmpty()) { - id = blockInfo.get(null); - } else { - id = blockInfo.get(properties); + LimboAPI.getLogger().warn("Block {} is not supported, and was replaced with air.", modernId); + return SimpleBlock.AIR.blockStateId; } + Short id = properties == null || properties.isEmpty() ? blockInfo.get(null) : 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(); + 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 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 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 8e6919c6..761584a4 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,299 @@ 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 java.util.EnumMap; import java.util.HashMap; +import java.util.List; +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.VirtualBlock; import net.elytrium.limboapi.api.chunk.VirtualBlockEntity; +import net.elytrium.limboapi.api.chunk.VirtualChunk; +import net.elytrium.limboapi.api.material.Item; +import net.elytrium.limboapi.api.material.WorldVersion; +import net.elytrium.limboapi.utils.JsonParser; +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; public class SimpleBlockEntity implements VirtualBlockEntity { - private static final Gson GSON = new Gson(); - private static final Map MODERN_ID_MAP = new HashMap<>(); + private final Map 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(VirtualChunk chunk, int posX, int posY, int posZ, CompoundBinaryTag nbt) { + return new Entry(chunk, posX, posY, posZ, nbt); + } - 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); + static { + LinkedTreeMap> blockEntitiesMapping = JsonParser.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); + } + } + + SimpleBlockEntity.MODERN_ID_MAP.put(modernId, simpleBlockEntity); }); + + /* + try { + Field field = BlockEntityType.class.getDeclaredField("validBlocks"); + field.setAccessible(true); + BuiltInRegistries.BLOCK_ENTITY_TYPE.asHolderIdMap().forEach(blockEntityTypeReference -> { + try { + String key = blockEntityTypeReference.unwrapKey().orElseThrow().location().toString(); + List validBlocks = ((Set) field.get(blockEntityTypeReference.value())).stream().map(block -> BuiltInRegistries.BLOCK.getKey(block).toString()).filter(block -> !block.equals(key)).toList(); + if (!validBlocks.isEmpty()) { + System.out.println("\"" + key + "\", \"" + String.join("\", \"", validBlocks) + "\""); + } + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + }); + } catch (NoSuchFieldException e) { + throw new RuntimeException(e); + } + */ + 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:brushable_block", "minecraft:suspicious_sand", "minecraft:suspicious_gravel"); } - 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 final StringBinaryTag EMPTY = StringBinaryTag.stringBinaryTag(""); + + private static boolean warned; + + private final VirtualChunk chunk; private final int posX; private final int posY; private final int posZ; private final CompoundBinaryTag nbt; - public Entry(int posX, int posY, int posZ, CompoundBinaryTag nbt) { + public Entry(VirtualChunk chunk, int posX, int posY, int posZ, CompoundBinaryTag nbt) { + this.chunk = chunk; this.posX = posX; this.posY = posY; this.posZ = posZ; @@ -128,28 +337,501 @@ public int getPosZ() { } @Override - public CompoundBinaryTag getNbt() { - return this.nbt; + public CompoundBinaryTag getNbt(ProtocolVersion version) { // adventure-nbt is the worst api I ever used + CompoundBinaryTag.Builder nbt = null; + // TODO fix banners 1.20.5-1.21.2 by adding patterns to the registry (but i'm not sure is it important thing and does someone will need it) + 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); + 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.20.5)."); + Entry.warned = true; + } + + // 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 + { + BinaryTag profile = this.nbt.get("profile"); + if (profile != 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*/); + } + } + + if (version.lessThan(ProtocolVersion.MINECRAFT_1_20)) { + // 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"); + if (frontText != null || this.nbt.get("back_text") != null) { + if (nbt == null) { + nbt = CompoundBinaryTag.builder().put(this.nbt); + } + + 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); + } + } + } + } + } + + // 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) ? SimpleBlockEntity.this.getModernId() : 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()) { + nbt.remove("Item"); + try { + nbt.putInt("Item", Item.valueOf(itemId.substring(10).toUpperCase(Locale.ROOT)).getLegacyId()); + } catch (IllegalArgumentException e) { + //e.printStackTrace(); + } + } + } + } + } + } + } + + return nbt == null ? this.nbt : nbt.build(); } - @Override - public int getID(ProtocolVersion version) { - return SimpleBlockEntity.this.getID(version); + private VirtualBlock getBlock() { + return this.chunk.getBlock(this.posX & 0x0F, this.posY, this.posZ & 0x0F); } - @Override - public int getID(BlockEntityVersion version) { - return SimpleBlockEntity.this.getID(version); + private static CompoundBinaryTag updateProfileTag(CompoundBinaryTag profileTag, ProtocolVersion version) { + CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder(); + { + BinaryTag name = profileTag.get("name"); + if (name instanceof StringBinaryTag) { + builder.put("Name", name); + } + + BinaryTag id = profileTag.get("id"); + BinaryTag propertiesListTag = profileTag.get("properties"); + if (propertiesListTag instanceof ListBinaryTag list) { + CompoundBinaryTag.Builder propertiesTag = CompoundBinaryTag.builder(); + for (BinaryTag propertyTag : list) { + if (propertyTag instanceof CompoundBinaryTag property) { + BinaryTag value = Objects.requireNonNullElse(property.get("value"), Entry.EMPTY); + BinaryTag signature = property.get("signature"); + propertiesTag.put(property.getString("name", ""), ListBinaryTag.listBinaryTag(BinaryTagTypes.COMPOUND, List.of(CompoundBinaryTag.from( + signature instanceof StringBinaryTag ? Map.of("Value", value, "Signature", signature) : Map.of("Value", value) + )))); + } + } + CompoundBinaryTag properties = propertiesTag.build(); + builder.put("Properties", properties); + 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) + && properties.get("textures") instanceof ListBinaryTag textures + && textures.size() > 0 && 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(); } - @Override - public boolean isSupportedOn(ProtocolVersion version) { - return SimpleBlockEntity.this.isSupportedOn(version); + // 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 9c3da701..b237b0af 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,93 @@ 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 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.utils.JsonParser; 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 Map MODERN_ID_MAP; + private static final Map LEGACY_ID_MAP; + private final Map 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) { + return this.versionIds.get(version); } @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 { + LinkedTreeMap> itemsMapping = JsonParser.parse(LimboAPI.class.getResourceAsStream("/mappings/items_mappings.json")); + MODERN_ID_MAP = new HashMap<>(itemsMapping.size()); + LinkedTreeMap modernIdRemap = JsonParser.parse(LimboAPI.class.getResourceAsStream("/mappings/modern_item_id_remap.json")); + 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); + versions.forEach((version, item) -> simpleItem.versionIds.put(WorldVersion.from(ProtocolVersion.getProtocolVersion(Integer.parseInt(version))), item.shortValue())); + SimpleItem.MODERN_ID_MAP.put(modernId, simpleItem); String remapped = modernIdRemap.get(modernId); if (remapped != null) { - if (MODERN_ID_MAP.containsKey(remapped)) { + if (SimpleItem.MODERN_ID_MAP.containsKey(remapped)) { throw new IllegalStateException("Remapped id " + remapped + " (from " + modernId + ") already exists"); } - MODERN_ID_MAP.put(remapped, simpleItem); + SimpleItem.MODERN_ID_MAP.put(remapped, simpleItem); } }); - legacyItems.forEach((legacyProtocolId, modernId) -> LEGACY_ID_MAP.put(Integer.parseInt(legacyProtocolId), MODERN_ID_MAP.get(modernId))); + LinkedTreeMap legacyItems = JsonParser.parse(LimboAPI.class.getResourceAsStream("/mappings/legacy_items.json")); + LEGACY_ID_MAP = new HashMap<>(legacyItems.size()); + legacyItems.forEach((legacyProtocolId, modernId) -> { + int id = Integer.parseInt(legacyProtocolId); + SimpleItem value = SimpleItem.MODERN_ID_MAP.get(modernId); + SimpleItem.LEGACY_ID_MAP.put(id, value); + if (value != null) { + value.versionIds.put(WorldVersion.LEGACY, (short) id); + } + }); } public static SimpleItem fromItem(Item item) { - return LEGACY_ID_MAP.get(item.getLegacyID()); + return SimpleItem.LEGACY_ID_MAP.get(item.getLegacyId()); } - public static SimpleItem fromLegacyID(int id) { - return LEGACY_ID_MAP.get(id); + public static SimpleItem fromLegacyId(int id) { + return SimpleItem.LEGACY_ID_MAP.get(id); } - public static SimpleItem fromModernID(String id) { - return MODERN_ID_MAP.get(id); + public static SimpleItem fromModernId(String id) { + return SimpleItem.MODERN_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..caa1a28c --- /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 java.util.EnumMap; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import net.elytrium.limboapi.LimboAPI; +import net.elytrium.limboapi.utils.JsonParser; + +// TODO legacy (<=1.12.2) + expose into api +public class SimpleParticlesManager { + + private static final EnumMap> PROTOCOL_2_PARTICLE = new EnumMap<>(ProtocolVersion.class); + + static { + LinkedTreeMap> particlesMappings = JsonParser.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)), + k -> new HashMap<>(versions.size()) + ).put(id.intValue(), particle)); + }); + } + + 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 c8f881d6..634a46aa 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,67 @@ 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 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.protocol.packets.s2c.UpdateTagsPacket; +import net.elytrium.limboapi.utils.JsonParser; public class SimpleTagManager { - private static final Map FLUIDS = new HashMap<>(); - private static final Map VERSION_MAP = new EnumMap<>(WorldVersion.class); + private static final Map 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 { + LinkedTreeMap fluids = JsonParser.parse(LimboAPI.class.getResourceAsStream("/mappings/fluids.json")); + FLUIDS = new HashMap<>(fluids.size()); + fluids.forEach((id, protocolId) -> SimpleTagManager.FLUIDS.put(id, protocolId.intValue())); + LinkedTreeMap>> tags = JsonParser.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) { + Map> tags = new LinkedTreeMap<>(); defaultTags.forEach((tagType, defaultTagList) -> { - LinkedTreeMap> tagList = new LinkedTreeMap<>(); + LinkedTreeMap tagList = new LinkedTreeMap<>(); 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, Map.of())) + .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(FLUIDS::get).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 +86,7 @@ private static UpdateTagsPacket localGetTagsForVersion(LinkedTreeMap()); } @@ -210,11 +209,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 +221,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 77895e70..ede54dd1 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 @@ -24,6 +24,7 @@ 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.BlockSection; import net.elytrium.limboapi.api.chunk.data.ChunkSnapshot; import net.elytrium.limboapi.api.chunk.data.LightSection; import net.elytrium.limboapi.material.Biome; @@ -63,17 +64,15 @@ 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); + 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 @@ -96,11 +95,7 @@ private SimpleSection getSection(int posY) { @Override public VirtualBlock getBlock(int posX, int posY, 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 @@ -123,22 +118,22 @@ public VirtualBiome getBiome(int posX, int posY, int posZ) { @Override public void setBlockLight(int posX, int posY, int posZ, byte light) { - this.getLightSection(posY).setBlockLight(posX, posY & 15, posZ, 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); + 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); + 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); + return this.getLightSection(posY).getSkyLight(posX, posY & 0x0F, posZ); } private LightSection getLightSection(int posY) { @@ -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 2de6bb2c..f1701591 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.BlockSection; import net.elytrium.limboapi.api.chunk.data.ChunkSnapshot; import net.elytrium.limboapi.api.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 6799c21d..0b6b7119 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 @@ -28,16 +28,14 @@ public class SimpleLightSection implements LightSection { 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 92525881..448d558b 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 @@ -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/JsonParser.java b/plugin/src/main/java/net/elytrium/limboapi/utils/JsonParser.java new file mode 100644 index 00000000..f684d209 --- /dev/null +++ b/plugin/src/main/java/net/elytrium/limboapi/utils/JsonParser.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 JsonParser { + + 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) JsonParser.GSON.fromJson(reader, JsonParser.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 366ee443..00000000 --- a/plugin/src/main/java/net/elytrium/limboapi/utils/LambdaUtil.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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; - -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/ProtocolTools.java b/plugin/src/main/java/net/elytrium/limboapi/utils/ProtocolTools.java deleted file mode 100644 index 4add52f2..00000000 --- a/plugin/src/main/java/net/elytrium/limboapi/utils/ProtocolTools.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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; - -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/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/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 100% rename from plugin/src/main/resources/mapping/legacyitems.json rename to plugin/src/main/resources/mappings/legacy_items.json diff --git a/plugin/src/main/resources/mapping/modern_block_id_remap.json b/plugin/src/main/resources/mappings/modern_block_id_remap.json similarity index 100% rename from plugin/src/main/resources/mapping/modern_block_id_remap.json rename to plugin/src/main/resources/mappings/modern_block_id_remap.json 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")